From e5b909849d4afe48138f59bb62f1c8966a47869b Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Thu, 15 May 2025 16:37:43 -0500 Subject: [PATCH] Ensure calendar range uses correct timezone --- .../filter/CalendarFilterButton.tsx | 10 ++- web/src/components/ui/calendar-range.tsx | 61 +++++++++++++------ 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/web/src/components/filter/CalendarFilterButton.tsx b/web/src/components/filter/CalendarFilterButton.tsx index f16368d63..0ecb8e95e 100644 --- a/web/src/components/filter/CalendarFilterButton.tsx +++ b/web/src/components/filter/CalendarFilterButton.tsx @@ -1,6 +1,7 @@ import { useFormattedRange, useFormattedTimestamp, + useTimezone, } from "@/hooks/use-date-utils"; import { RecordingsSummary, ReviewSummary } from "@/types/review"; import { Button } from "../ui/button"; @@ -11,15 +12,17 @@ import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer"; import { isMobile } from "react-device-detect"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { DateRangePicker } from "../ui/calendar-range"; -import { DateRange } from "react-day-picker"; +import { DateRange, TZDate } from "react-day-picker"; import { useState } from "react"; import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; import { useTranslation } from "react-i18next"; +import useSWR from "swr"; +import { FrigateConfig } from "@/types/frigateConfig"; type CalendarFilterButtonProps = { reviewSummary?: ReviewSummary; recordingsSummary?: RecordingsSummary; - day?: Date; + day?: TZDate; updateSelectedDay: (day?: Date) => void; }; export default function CalendarFilterButton({ @@ -98,6 +101,8 @@ export function CalendarRangeFilterButton({ updateSelectedRange, }: CalendarRangeFilterButtonProps) { const { t } = useTranslation(["components/filter"]); + const { data: config } = useSWR("config"); + const timezone = useTimezone(config); const [open, setOpen] = useState(false); const selectedDate = useFormattedRange( @@ -128,6 +133,7 @@ export function CalendarRangeFilterButton({ { updateSelectedRange(range.range); diff --git a/web/src/components/ui/calendar-range.tsx b/web/src/components/ui/calendar-range.tsx index 218cc79bb..f439cb082 100644 --- a/web/src/components/ui/calendar-range.tsx +++ b/web/src/components/ui/calendar-range.tsx @@ -12,6 +12,7 @@ import { import { Switch } from "./switch"; import { cn } from "@/lib/utils"; import { LuCheck } from "react-icons/lu"; +import { TZDate } from "react-day-picker"; import { t } from "i18next"; export interface DateRangePickerProps { @@ -32,19 +33,24 @@ export interface DateRangePickerProps { locale?: string; /** Option for showing compare feature */ showCompare?: boolean; + /** timezone */ + timezone?: string; } -const getDateAdjustedForTimezone = (dateInput: Date | string): Date => { +const getDateAdjustedForTimezone = ( + dateInput: Date | string, + timezone?: string, +): Date => { if (typeof dateInput === "string") { // Split the date string to get year, month, and day parts const parts = dateInput.split("-").map((part) => parseInt(part, 10)); // Create a new Date object using the local timezone // Note: Month is 0-indexed, so subtract 1 from the month part - const date = new Date(parts[0], parts[1] - 1, parts[2]); + const date = new TZDate(parts[0], parts[1] - 1, parts[2], timezone); return date; } else { // If dateInput is already a Date object, return it directly - return dateInput; + return new TZDate(dateInput, timezone); } }; @@ -73,7 +79,12 @@ const PRESETS: Preset[] = [ /** The DateRangePicker component allows a user to select a range of dates */ export function DateRangePicker({ - initialDateFrom = new Date(new Date().setHours(0, 0, 0, 0)), + timezone, + initialDateFrom = (() => { + const date = new TZDate(new Date(), timezone); + date.setHours(0, 0, 0, 0); + return date; + })(), initialDateTo, initialCompareFrom, initialCompareTo, @@ -84,18 +95,27 @@ export function DateRangePicker({ const [isOpen, setIsOpen] = useState(false); const [range, setRange] = useState({ - from: getDateAdjustedForTimezone(initialDateFrom), + from: getDateAdjustedForTimezone(initialDateFrom, timezone), to: initialDateTo - ? getDateAdjustedForTimezone(initialDateTo) - : getDateAdjustedForTimezone(initialDateFrom), + ? getDateAdjustedForTimezone(initialDateTo, timezone) + : getDateAdjustedForTimezone(initialDateFrom, timezone), }); const [rangeCompare, setRangeCompare] = useState( initialCompareFrom ? { - from: new Date(new Date(initialCompareFrom).setHours(0, 0, 0, 0)), + from: new TZDate( + new Date(initialCompareFrom).setHours(0, 0, 0, 0), + timezone, + ), to: initialCompareTo - ? new Date(new Date(initialCompareTo).setHours(0, 0, 0, 0)) - : new Date(new Date(initialCompareFrom).setHours(0, 0, 0, 0)), + ? new TZDate( + new Date(initialCompareTo).setHours(0, 0, 0, 0), + timezone, + ) + : new TZDate( + new Date(initialCompareFrom).setHours(0, 0, 0, 0), + timezone, + ), } : undefined, ); @@ -128,8 +148,8 @@ export function DateRangePicker({ const getPresetRange = (presetName: string): DateRange => { const preset = PRESETS.find(({ name }) => name === presetName); if (!preset) throw new Error(`Unknown date range preset: ${presetName}`); - const from = new Date(); - const to = new Date(); + const from = new TZDate(new Date(), timezone); + const to = new TZDate(new Date(), timezone); const first = from.getDate() - from.getDay(); switch (preset.name) { @@ -191,16 +211,18 @@ export function DateRangePicker({ setRange(range); if (rangeCompare) { const rangeCompare = { - from: new Date( + from: new TZDate( range.from.getFullYear() - 1, range.from.getMonth(), range.from.getDate(), + timezone, ), to: range.to - ? new Date( + ? new TZDate( range.to.getFullYear() - 1, range.to.getMonth(), range.to.getDate(), + timezone, ) : undefined, }; @@ -212,16 +234,18 @@ export function DateRangePicker({ for (const preset of PRESETS) { const presetRange = getPresetRange(preset.name); - const normalizedRangeFrom = new Date(range.from); + const normalizedRangeFrom = new TZDate(range.from, timezone); normalizedRangeFrom.setHours(0, 0, 0, 0); - const normalizedPresetFrom = new Date( + const normalizedPresetFrom = new TZDate( presetRange.from.setHours(0, 0, 0, 0), + timezone, ); - const normalizedRangeTo = new Date(range.to ?? 0); + const normalizedRangeTo = new TZDate(range.to ?? new Date(0), timezone); normalizedRangeTo.setHours(0, 0, 0, 0); - const normalizedPresetTo = new Date( + const normalizedPresetTo = new TZDate( presetRange.to?.setHours(0, 0, 0, 0) ?? 0, + timezone, ); if ( @@ -401,6 +425,7 @@ export function DateRangePicker({ ), ) } + timeZone={timezone} />