mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Make export date/time respect configured timezone in config (#10750)
* Make export page timezone aware * Fix changeover
This commit is contained in:
parent
4d522be7fb
commit
5b5606cb8a
@ -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) {
|
||||||
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user