|
|
@@ -7,7 +7,7 @@ on a configurable schedule with retention management.
|
|
|
import asyncio
|
|
|
import logging
|
|
|
import os
|
|
|
-from datetime import datetime, timedelta, timezone
|
|
|
+from datetime import datetime, timedelta, timezone, tzinfo
|
|
|
from pathlib import Path
|
|
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
|
|
|
|
@@ -20,21 +20,31 @@ from backend.app.models.settings import Settings
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
-def _local_zone() -> ZoneInfo:
|
|
|
+def _local_zone() -> tzinfo:
|
|
|
"""Resolve the local timezone for scheduled-backup HH:MM interpretation.
|
|
|
|
|
|
Uses the container's ``TZ`` env var (the same value the support package
|
|
|
surfaces); falls back to UTC when unset or unrecognised so a missing TZ
|
|
|
keeps the legacy behaviour rather than crashing. See #1602 follow-up.
|
|
|
+
|
|
|
+ On Windows the embedded Python in our installer doesn't carry an IANA
|
|
|
+ tz database, so ``ZoneInfo(...)`` — including ``ZoneInfo("UTC")`` —
|
|
|
+ raises ``ZoneInfoNotFoundError`` unless the ``tzdata`` PyPI package is
|
|
|
+ installed. requirements.txt now pins ``tzdata`` on win32, but to keep
|
|
|
+ this resilient on installs that haven't refreshed deps we fall through
|
|
|
+ to the stdlib ``datetime.timezone.utc`` as a last resort; it satisfies
|
|
|
+ every ``astimezone`` / ``str()`` call site without needing the IANA DB.
|
|
|
"""
|
|
|
tz_name = os.environ.get("TZ", "").strip()
|
|
|
- if not tz_name:
|
|
|
- return ZoneInfo("UTC")
|
|
|
+ if tz_name:
|
|
|
+ try:
|
|
|
+ return ZoneInfo(tz_name)
|
|
|
+ except ZoneInfoNotFoundError:
|
|
|
+ logger.warning("Unrecognised TZ env value %r, scheduling in UTC", tz_name)
|
|
|
try:
|
|
|
- return ZoneInfo(tz_name)
|
|
|
- except ZoneInfoNotFoundError:
|
|
|
- logger.warning("Unrecognised TZ env value %r, scheduling in UTC", tz_name)
|
|
|
return ZoneInfo("UTC")
|
|
|
+ except ZoneInfoNotFoundError:
|
|
|
+ return timezone.utc
|
|
|
|
|
|
|
|
|
SCHEDULE_INTERVALS = {
|