Make export date/time respect configured timezone in config (#10750)

* Make export page timezone aware

* Fix changeover
This commit is contained in:
Nicolas Mowen 2024-03-30 13:07:30 -06:00 committed by GitHub
parent 4d522be7fb
commit 5b5606cb8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 107 additions and 10 deletions

View File

@ -20,11 +20,12 @@ import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import useSWR from "swr"; import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import ReviewActivityCalendar from "./ReviewActivityCalendar"; import { TimezoneAwareCalendar } from "./ReviewActivityCalendar";
import { SelectSeparator } from "../ui/select"; import { SelectSeparator } from "../ui/select";
import { isDesktop } from "react-device-detect"; import { isDesktop } from "react-device-detect";
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
import SaveExportOverlay from "./SaveExportOverlay"; import SaveExportOverlay from "./SaveExportOverlay";
import { getUTCOffset } from "@/utils/dateUtil";
const EXPORT_OPTIONS = [ const EXPORT_OPTIONS = [
"1", "1",
@ -305,14 +306,42 @@ function CustomTimeSelector({
// times // times
const startTime = useMemo( const timezoneOffset = useMemo(
() => range?.after || latestTime - 3600, () =>
[range, latestTime], config?.ui.timezone
? Math.round(getUTCOffset(new Date(), config.ui.timezone))
: undefined,
[config?.ui.timezone],
); );
const endTime = useMemo( const localTimeOffset = useMemo(
() => range?.before || latestTime, () =>
[range, latestTime], Math.round(
getUTCOffset(
new Date(),
Intl.DateTimeFormat().resolvedOptions().timeZone,
),
),
[],
); );
const startTime = useMemo(() => {
let time = range?.after || latestTime - 3600;
if (timezoneOffset) {
time = time + (timezoneOffset - localTimeOffset) * 60;
}
return time;
}, [range, latestTime, timezoneOffset, localTimeOffset]);
const endTime = useMemo(() => {
let time = range?.before || latestTime;
if (timezoneOffset) {
time = time + (timezoneOffset - localTimeOffset) * 60;
}
return time;
}, [range, latestTime, timezoneOffset, localTimeOffset]);
const formattedStart = useFormattedTimestamp( const formattedStart = useFormattedTimestamp(
startTime, startTime,
config?.ui.time_format == "24hour" config?.ui.time_format == "24hour"
@ -367,7 +396,8 @@ function CustomTimeSelector({
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="flex flex-col items-center"> <PopoverContent className="flex flex-col items-center">
<ReviewActivityCalendar <TimezoneAwareCalendar
timezone={config?.ui.timezone}
selectedDay={new Date(startTime * 1000)} selectedDay={new Date(startTime * 1000)}
onSelect={(day) => { onSelect={(day) => {
if (!day) { if (!day) {
@ -428,7 +458,8 @@ function CustomTimeSelector({
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="flex flex-col items-center"> <PopoverContent className="flex flex-col items-center">
<ReviewActivityCalendar <TimezoneAwareCalendar
timezone={config?.ui.timezone}
selectedDay={new Date(endTime * 1000)} selectedDay={new Date(endTime * 1000)}
onSelect={(day) => { onSelect={(day) => {
if (!day) { if (!day) {

View File

@ -2,6 +2,7 @@ import { ReviewSummary } from "@/types/review";
import { Calendar } from "../ui/calendar"; import { Calendar } from "../ui/calendar";
import { useMemo } from "react"; import { useMemo } from "react";
import { FaCircle } from "react-icons/fa"; import { FaCircle } from "react-icons/fa";
import { getUTCOffset } from "@/utils/dateUtil";
type ReviewActivityCalendarProps = { type ReviewActivityCalendarProps = {
reviewSummary?: ReviewSummary; reviewSummary?: ReviewSummary;
@ -76,3 +77,68 @@ function ReviewActivityDay({ reviewSummary, day }: ReviewActivityDayProps) {
</div> </div>
); );
} }
type TimezoneAwareCalendarProps = {
timezone?: string;
selectedDay?: Date;
onSelect: (day?: Date) => void;
};
export function TimezoneAwareCalendar({
timezone,
selectedDay,
onSelect,
}: TimezoneAwareCalendarProps) {
const timezoneOffset = useMemo(
() =>
timezone ? Math.round(getUTCOffset(new Date(), timezone)) : undefined,
[timezone],
);
const disabledDates = useMemo(() => {
const tomorrow = new Date();
if (timezoneOffset) {
tomorrow.setHours(
tomorrow.getHours() + 24,
tomorrow.getMinutes() + timezoneOffset,
0,
0,
);
} else {
tomorrow.setHours(tomorrow.getHours() + 24, -1, 0, 0);
}
const future = new Date();
future.setFullYear(tomorrow.getFullYear() + 10);
return { from: tomorrow, to: future };
}, [timezoneOffset]);
const today = useMemo(() => {
if (!timezoneOffset) {
return undefined;
}
const date = new Date();
const utc = Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
);
const todayUtc = new Date(utc);
todayUtc.setMinutes(todayUtc.getMinutes() + timezoneOffset, 0, 0);
return todayUtc;
}, [timezoneOffset]);
return (
<Calendar
mode="single"
disabled={disabledDates}
showOutsideDays={false}
today={today}
selected={selectedDay}
onSelect={onSelect}
/>
);
}

View File

@ -235,7 +235,7 @@ export const getDurationFromTimestamps = (
* @param timezone string representation of the timezone the user is requesting * @param timezone string representation of the timezone the user is requesting
* @returns number of minutes offset from UTC * @returns number of minutes offset from UTC
*/ */
const getUTCOffset = (date: Date, timezone: string): number => { export const getUTCOffset = (date: Date, timezone: string): number => {
// If timezone is in UTC±HH:MM format, parse it to get offset // If timezone is in UTC±HH:MM format, parse it to get offset
const utcOffsetMatch = timezone.match(/^UTC([+-])(\d{2}):(\d{2})$/); const utcOffsetMatch = timezone.match(/^UTC([+-])(\d{2}):(\d{2})$/);
if (utcOffsetMatch) { if (utcOffsetMatch) {