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.
This commit is contained in:
Martin Weinelt
2026-07-06 23:18:21 +02:00
parent f7528937e0
commit 38cabaa6aa
4 changed files with 29 additions and 8 deletions
+4 -1
View File
@@ -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.image import get_image_from_recording, get_image_quality_params
from frigate.util.media import get_keyframe_before from frigate.util.media import get_keyframe_before
from frigate.util.time import get_normalized_tz_name
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -713,7 +714,9 @@ async def vod_hour(
parts = year_month.split("-") parts = year_month.split("-")
start_date = ( start_date = (
datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=UTC) 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) end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
start_ts = start_date.timestamp() start_ts = start_date.timestamp()
+4 -1
View File
@@ -22,6 +22,7 @@ from frigate.api.defs.response.preview_response import (
from frigate.api.defs.tags import Tags from frigate.api.defs.tags import Tags
from frigate.const import BASE_DIR, CACHE_DIR, PREVIEW_FRAME_TYPE from frigate.const import BASE_DIR, CACHE_DIR, PREVIEW_FRAME_TYPE
from frigate.models import Previews from frigate.models import Previews
from frigate.util.time import get_normalized_tz_name
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -126,7 +127,9 @@ def preview_hour(
parts = year_month.split("-") parts = year_month.split("-")
start_date = ( start_date = (
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=UTC) 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) end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
start_ts = start_date.timestamp() start_ts = start_date.timestamp()
+3 -3
View File
@@ -31,7 +31,7 @@ from frigate.ffmpeg_presets import (
) )
from frigate.models import Export, Previews, Recordings, ReviewSegment from frigate.models import Export, Previews, Recordings, ReviewSegment
from frigate.util.ffmpeg import run_ffmpeg_with_progress 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__) logger = logging.getLogger(__name__)
@@ -371,7 +371,7 @@ class RecordingExporter(threading.Thread):
tz_name = self.config.ui.timezone tz_name = self.config.ui.timezone
if tz_name: if tz_name:
try: try:
tz = ZoneInfo(tz_name) tz = ZoneInfo(get_normalized_tz_name(tz_name))
except (ValueError, ZoneInfoNotFoundError): except (ValueError, ZoneInfoNotFoundError):
tz = None tz = None
if tz is not None: if tz is not None:
@@ -533,7 +533,7 @@ class RecordingExporter(threading.Thread):
tz: datetime.tzinfo | None = None tz: datetime.tzinfo | None = None
if tz_name: if tz_name:
try: try:
tz = ZoneInfo(tz_name) tz = ZoneInfo(get_normalized_tz_name(tz_name))
except (ValueError, ZoneInfoNotFoundError): except (ValueError, ZoneInfoNotFoundError):
tz = None tz = None
if tz is None: if tz is None:
+18 -3
View File
@@ -2,16 +2,31 @@
import datetime import datetime
import logging import logging
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from typing import Final
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError, available_timezones
from tzlocal import get_localzone from tzlocal import get_localzone
logger = logging.getLogger(__name__) 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]: def get_tz_modifiers(tz_name: str) -> tuple[str, str, float]:
seconds_offset = ( 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) hours_offset = int(seconds_offset / 60 / 60)
minutes_offset = int(seconds_offset / 60 - hours_offset * 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 continuous periods with the same UTC offset
""" """
try: try:
tz = ZoneInfo(tz_name) tz = ZoneInfo(get_normalized_tz_name(tz_name))
except (ValueError, ZoneInfoNotFoundError): except (ValueError, ZoneInfoNotFoundError):
# If timezone is invalid, return single period with no offset # If timezone is invalid, return single period with no offset
return [(start_time, end_time, 0)] return [(start_time, end_time, 0)]