mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Support timezones (#11434)
* Handle offset timezones * Fix previews loading * Cleanup * remove unused
This commit is contained in:
		
							parent
							
								
									bca01cb43c
								
							
						
					
					
						commit
						4c87ef56c7
					
				| @ -10,7 +10,7 @@ import useSWR from "swr"; | ||||
| import { FrigateConfig } from "@/types/frigateConfig"; | ||||
| import { Preview } from "@/types/preview"; | ||||
| import { PreviewPlayback } from "@/types/playback"; | ||||
| import { isCurrentHour } from "@/utils/dateUtil"; | ||||
| import { getUTCOffset, isCurrentHour } from "@/utils/dateUtil"; | ||||
| import { baseUrl } from "@/api/baseUrl"; | ||||
| import { isAndroid, isChrome, isMobile } from "react-device-detect"; | ||||
| import { TimeRange } from "@/types/timeline"; | ||||
| @ -41,11 +41,14 @@ export default function PreviewPlayer({ | ||||
|   const [currentHourFrame, setCurrentHourFrame] = useState<string>(); | ||||
| 
 | ||||
|   const currentPreview = useMemo(() => { | ||||
|     const timeRangeOffset = | ||||
|       (getUTCOffset(new Date(timeRange.before * 1000)) % 60) * 60; | ||||
| 
 | ||||
|     return cameraPreviews.find( | ||||
|       (preview) => | ||||
|         preview.camera == camera && | ||||
|         Math.round(preview.start) >= timeRange.after && | ||||
|         Math.floor(preview.end) <= timeRange.before, | ||||
|         Math.round(preview.start) >= timeRange.after + timeRangeOffset && | ||||
|         Math.floor(preview.end) <= timeRange.before + timeRangeOffset, | ||||
|     ); | ||||
|   }, [cameraPreviews, camera, timeRange]); | ||||
| 
 | ||||
| @ -230,11 +233,14 @@ function PreviewVideoPlayer({ | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const timeRangeOffset = | ||||
|       getUTCOffset(new Date(timeRange.before * 1000)) % 60; | ||||
| 
 | ||||
|     const preview = cameraPreviews.find( | ||||
|       (preview) => | ||||
|         preview.camera == camera && | ||||
|         Math.round(preview.start) >= timeRange.after && | ||||
|         Math.floor(preview.end) <= timeRange.before, | ||||
|         Math.round(preview.start) >= timeRange.after + timeRangeOffset && | ||||
|         Math.floor(preview.end) <= timeRange.before + timeRangeOffset, | ||||
|     ); | ||||
| 
 | ||||
|     if (preview != currentPreview) { | ||||
|  | ||||
| @ -12,6 +12,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; | ||||
| import { VideoResolutionType } from "@/types/live"; | ||||
| import axios from "axios"; | ||||
| import { cn } from "@/lib/utils"; | ||||
| import { getUTCOffset } from "@/utils/dateUtil"; | ||||
| 
 | ||||
| /** | ||||
|  * Dynamically switches between video playback and scrubbing preview player. | ||||
| @ -148,9 +149,12 @@ export default function DynamicVideoPlayer({ | ||||
|   // state of playback player
 | ||||
| 
 | ||||
|   const recordingParams = useMemo(() => { | ||||
|     const timeRangeOffset = | ||||
|       (getUTCOffset(new Date(timeRange.before * 1000)) % 60) * 60; | ||||
| 
 | ||||
|     return { | ||||
|       before: timeRange.before, | ||||
|       after: timeRange.after, | ||||
|       before: timeRange.before + timeRangeOffset, | ||||
|       after: timeRange.after + timeRangeOffset, | ||||
|     }; | ||||
|   }, [timeRange]); | ||||
|   const { data: recordings } = useSWR<Recording[]>( | ||||
| @ -168,7 +172,7 @@ export default function DynamicVideoPlayer({ | ||||
|     } | ||||
| 
 | ||||
|     setSource( | ||||
|       `${apiHost}vod/${camera}/start/${timeRange.after}/end/${timeRange.before}/master.m3u8`, | ||||
|       `${apiHost}vod/${camera}/start/${recordingParams.after}/end/${recordingParams.before}/master.m3u8`, | ||||
|     ); | ||||
|     setLoadingTimeout(setTimeout(() => setIsLoading(true), 1000)); | ||||
| 
 | ||||
|  | ||||
| @ -11,6 +11,7 @@ import { | ||||
|   ReviewSeverity, | ||||
|   ReviewSummary, | ||||
| } from "@/types/review"; | ||||
| import { getUTCOffset } from "@/utils/dateUtil"; | ||||
| import EventView from "@/views/events/EventView"; | ||||
| import { RecordingView } from "@/views/events/RecordingView"; | ||||
| import axios from "axios"; | ||||
| @ -166,6 +167,8 @@ export default function Events() { | ||||
|       return undefined; | ||||
|     } | ||||
| 
 | ||||
|     const timezoneMinuteOffset = (getUTCOffset(new Date()) % 60) * 60; | ||||
| 
 | ||||
|     const startDate = new Date(); | ||||
|     startDate.setMinutes(0, 0, 0); | ||||
| 
 | ||||
| @ -173,8 +176,8 @@ export default function Events() { | ||||
|     endDate.setHours(0, 0, 0, 0); | ||||
| 
 | ||||
|     return { | ||||
|       after: startDate.getTime() / 1000, | ||||
|       before: endDate.getTime() / 1000, | ||||
|       after: startDate.getTime() / 1000 + timezoneMinuteOffset, | ||||
|       before: endDate.getTime() / 1000 + timezoneMinuteOffset, | ||||
|     }; | ||||
|   }, [reviews]); | ||||
| 
 | ||||
|  | ||||
| @ -235,7 +235,10 @@ export const getDurationFromTimestamps = ( | ||||
|  * @param timezone string representation of the timezone the user is requesting | ||||
|  * @returns number of minutes offset from UTC | ||||
|  */ | ||||
| export const getUTCOffset = (date: Date, timezone: string): number => { | ||||
| export const getUTCOffset = ( | ||||
|   date: Date, | ||||
|   timezone: string = getResolvedTimeZone(), | ||||
| ): 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) { | ||||
| @ -259,10 +262,10 @@ export const getUTCOffset = (date: Date, timezone: string): number => { | ||||
|     target = new Date(`${iso}+000`); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|   return Math.round( | ||||
|     (target.getTime() - utcDate.getTime() - date.getTimezoneOffset()) / | ||||
|     60 / | ||||
|     1000 | ||||
|       60 / | ||||
|       1000, | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,7 @@ import { | ||||
|   MdOutlinePictureInPictureAlt, | ||||
| } from "react-icons/md"; | ||||
| import { FaBicycle } from "react-icons/fa"; | ||||
| import { endOfHourOrCurrentTime } from "./dateUtil"; | ||||
| import { endOfHourOrCurrentTime, getUTCOffset } from "./dateUtil"; | ||||
| import { TimeRange, Timeline } from "@/types/timeline"; | ||||
| 
 | ||||
| export function getTimelineIcon(timelineItem: Timeline) { | ||||
| @ -131,12 +131,25 @@ export function getChunkedTimeDay(timeRange: TimeRange): TimeRange[] { | ||||
|   endOfThisHour.setSeconds(0, 0); | ||||
|   const data: TimeRange[] = []; | ||||
|   const startDay = new Date(timeRange.after * 1000); | ||||
|   startDay.setMinutes(0, 0, 0); | ||||
|   const timezoneMinuteOffset = | ||||
|     getUTCOffset(new Date(timeRange.before * 1000)) % 60; | ||||
| 
 | ||||
|   if (timezoneMinuteOffset == 0) { | ||||
|     startDay.setMinutes(0, 0, 0); | ||||
|   } else { | ||||
|     startDay.setSeconds(0, 0); | ||||
|   } | ||||
| 
 | ||||
|   let start = startDay.getTime() / 1000; | ||||
|   let end = 0; | ||||
| 
 | ||||
|   for (let i = 0; i < 24; i++) { | ||||
|     startDay.setHours(startDay.getHours() + 1, 0, 0, 0); | ||||
|     startDay.setHours( | ||||
|       startDay.getHours() + 1, | ||||
|       Math.abs(timezoneMinuteOffset), | ||||
|       0, | ||||
|       0, | ||||
|     ); | ||||
| 
 | ||||
|     if (startDay > endOfThisHour) { | ||||
|       break; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user