mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +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