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 { FrigateConfig } from "@/types/frigateConfig";
 | 
			
		||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
 | 
			
		||||
import ReviewActivityCalendar from "./ReviewActivityCalendar";
 | 
			
		||||
import { TimezoneAwareCalendar } from "./ReviewActivityCalendar";
 | 
			
		||||
import { SelectSeparator } from "../ui/select";
 | 
			
		||||
import { isDesktop } from "react-device-detect";
 | 
			
		||||
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
 | 
			
		||||
import SaveExportOverlay from "./SaveExportOverlay";
 | 
			
		||||
import { getUTCOffset } from "@/utils/dateUtil";
 | 
			
		||||
 | 
			
		||||
const EXPORT_OPTIONS = [
 | 
			
		||||
  "1",
 | 
			
		||||
@ -305,14 +306,42 @@ function CustomTimeSelector({
 | 
			
		||||
 | 
			
		||||
  // times
 | 
			
		||||
 | 
			
		||||
  const startTime = useMemo(
 | 
			
		||||
    () => range?.after || latestTime - 3600,
 | 
			
		||||
    [range, latestTime],
 | 
			
		||||
  const timezoneOffset = useMemo(
 | 
			
		||||
    () =>
 | 
			
		||||
      config?.ui.timezone
 | 
			
		||||
        ? Math.round(getUTCOffset(new Date(), config.ui.timezone))
 | 
			
		||||
        : undefined,
 | 
			
		||||
    [config?.ui.timezone],
 | 
			
		||||
  );
 | 
			
		||||
  const endTime = useMemo(
 | 
			
		||||
    () => range?.before || latestTime,
 | 
			
		||||
    [range, latestTime],
 | 
			
		||||
  const localTimeOffset = useMemo(
 | 
			
		||||
    () =>
 | 
			
		||||
      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(
 | 
			
		||||
    startTime,
 | 
			
		||||
    config?.ui.time_format == "24hour"
 | 
			
		||||
@ -367,7 +396,8 @@ function CustomTimeSelector({
 | 
			
		||||
          </Button>
 | 
			
		||||
        </PopoverTrigger>
 | 
			
		||||
        <PopoverContent className="flex flex-col items-center">
 | 
			
		||||
          <ReviewActivityCalendar
 | 
			
		||||
          <TimezoneAwareCalendar
 | 
			
		||||
            timezone={config?.ui.timezone}
 | 
			
		||||
            selectedDay={new Date(startTime * 1000)}
 | 
			
		||||
            onSelect={(day) => {
 | 
			
		||||
              if (!day) {
 | 
			
		||||
@ -428,7 +458,8 @@ function CustomTimeSelector({
 | 
			
		||||
          </Button>
 | 
			
		||||
        </PopoverTrigger>
 | 
			
		||||
        <PopoverContent className="flex flex-col items-center">
 | 
			
		||||
          <ReviewActivityCalendar
 | 
			
		||||
          <TimezoneAwareCalendar
 | 
			
		||||
            timezone={config?.ui.timezone}
 | 
			
		||||
            selectedDay={new Date(endTime * 1000)}
 | 
			
		||||
            onSelect={(day) => {
 | 
			
		||||
              if (!day) {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { ReviewSummary } from "@/types/review";
 | 
			
		||||
import { Calendar } from "../ui/calendar";
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
import { FaCircle } from "react-icons/fa";
 | 
			
		||||
import { getUTCOffset } from "@/utils/dateUtil";
 | 
			
		||||
 | 
			
		||||
type ReviewActivityCalendarProps = {
 | 
			
		||||
  reviewSummary?: ReviewSummary;
 | 
			
		||||
@ -76,3 +77,68 @@ function ReviewActivityDay({ reviewSummary, day }: ReviewActivityDayProps) {
 | 
			
		||||
    </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
 | 
			
		||||
 * @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
 | 
			
		||||
  const utcOffsetMatch = timezone.match(/^UTC([+-])(\d{2}):(\d{2})$/);
 | 
			
		||||
  if (utcOffsetMatch) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user