From 38cabaa6aafb87ba81b2135d399afafe875a419e Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 6 Jul 2026 16:21:01 +0200 Subject: [PATCH] Provide lookup for normalized timezone names This provides a helper to look up the correct timezone name independent of the casing of the given timezone name. --- frigate/api/media.py | 5 ++++- frigate/api/preview.py | 5 ++++- frigate/record/export.py | 6 +++--- frigate/util/time.py | 21 ++++++++++++++++++--- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/frigate/api/media.py b/frigate/api/media.py index 50bb53271a..8ce83ab8a8 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -53,6 +53,7 @@ from frigate.util.file import ( ) from frigate.util.image import get_image_from_recording, get_image_quality_params from frigate.util.media import get_keyframe_before +from frigate.util.time import get_normalized_tz_name logger = logging.getLogger(__name__) @@ -713,7 +714,9 @@ async def vod_hour( parts = year_month.split("-") start_date = ( datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=UTC) - - datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset() + - datetime.now( + ZoneInfo(get_normalized_tz_name(tz_name.replace(",", "/"))) + ).utcoffset() ) end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1) start_ts = start_date.timestamp() diff --git a/frigate/api/preview.py b/frigate/api/preview.py index af84117fd6..81de23d003 100644 --- a/frigate/api/preview.py +++ b/frigate/api/preview.py @@ -22,6 +22,7 @@ from frigate.api.defs.response.preview_response import ( from frigate.api.defs.tags import Tags from frigate.const import BASE_DIR, CACHE_DIR, PREVIEW_FRAME_TYPE from frigate.models import Previews +from frigate.util.time import get_normalized_tz_name logger = logging.getLogger(__name__) @@ -126,7 +127,9 @@ def preview_hour( parts = year_month.split("-") start_date = ( datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=UTC) - - datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset() + - datetime.now( + ZoneInfo(get_normalized_tz_name(tz_name.replace(",", "/"))) + ).utcoffset() ) end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1) start_ts = start_date.timestamp() diff --git a/frigate/record/export.py b/frigate/record/export.py index 0564dee04b..ff33063bc3 100644 --- a/frigate/record/export.py +++ b/frigate/record/export.py @@ -31,7 +31,7 @@ from frigate.ffmpeg_presets import ( ) from frigate.models import Export, Previews, Recordings, ReviewSegment from frigate.util.ffmpeg import run_ffmpeg_with_progress -from frigate.util.time import is_current_hour +from frigate.util.time import get_normalized_tz_name, is_current_hour logger = logging.getLogger(__name__) @@ -371,7 +371,7 @@ class RecordingExporter(threading.Thread): tz_name = self.config.ui.timezone if tz_name: try: - tz = ZoneInfo(tz_name) + tz = ZoneInfo(get_normalized_tz_name(tz_name)) except (ValueError, ZoneInfoNotFoundError): tz = None if tz is not None: @@ -533,7 +533,7 @@ class RecordingExporter(threading.Thread): tz: datetime.tzinfo | None = None if tz_name: try: - tz = ZoneInfo(tz_name) + tz = ZoneInfo(get_normalized_tz_name(tz_name)) except (ValueError, ZoneInfoNotFoundError): tz = None if tz is None: diff --git a/frigate/util/time.py b/frigate/util/time.py index 777ae9cc11..ea6fadd2b9 100644 --- a/frigate/util/time.py +++ b/frigate/util/time.py @@ -2,16 +2,31 @@ import datetime import logging -from zoneinfo import ZoneInfo, ZoneInfoNotFoundError +from typing import Final +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError, available_timezones from tzlocal import get_localzone logger = logging.getLogger(__name__) +TIMEZONE_CASEFOLD_INDEX: Final = { + timezone.casefold(): timezone for timezone in available_timezones() +} + + +def get_normalized_tz_name(tz_name: str) -> str: + tz = TIMEZONE_CASEFOLD_INDEX.get(tz_name.casefold()) + if tz: + return tz + + raise ValueError(f"Unknown timezone: {tz_name}") + def get_tz_modifiers(tz_name: str) -> tuple[str, str, float]: seconds_offset = ( - datetime.datetime.now(ZoneInfo(tz_name)).utcoffset().total_seconds() + datetime.datetime.now(ZoneInfo(get_normalized_tz_name(tz_name))) + .utcoffset() + .total_seconds() ) hours_offset = int(seconds_offset / 60 / 60) minutes_offset = int(seconds_offset / 60 - hours_offset * 60) @@ -58,7 +73,7 @@ def get_dst_transitions( continuous periods with the same UTC offset """ try: - tz = ZoneInfo(tz_name) + tz = ZoneInfo(get_normalized_tz_name(tz_name)) except (ValueError, ZoneInfoNotFoundError): # If timezone is invalid, return single period with no offset return [(start_time, end_time, 0)]