From 3a718ae517c1c425fe88f709b53d914e3607816b Mon Sep 17 00:00:00 2001 From: "duanjialing.777" Date: Sat, 12 Sep 2026 13:11:38 +0800 Subject: [PATCH] fix(periodic-report): accept UTC without tzdata Signed-off-by: duanjialing.777 --- .../periodic_report/goal_configuration.py | 9 ++++---- .../periodic_report/machine_defaults.py | 21 +++++++++++-------- .../test_periodic_report_machine_defaults.py | 16 ++++++++++++++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/loopx/capabilities/periodic_report/goal_configuration.py b/loopx/capabilities/periodic_report/goal_configuration.py index 9e55bc8f3d..f00214b3ca 100644 --- a/loopx/capabilities/periodic_report/goal_configuration.py +++ b/loopx/capabilities/periodic_report/goal_configuration.py @@ -30,10 +30,11 @@ def normalize_configuration(value: Mapping[str, Any]) -> dict[str, Any]: if not isinstance(enabled, bool): raise TypeError("periodic_report.enabled must be a boolean") timezone = str(value.get("timezone") or "UTC").strip() - try: - ZoneInfo(timezone) - except ZoneInfoNotFoundError as exc: - raise ValueError("periodic_report.timezone is unknown") from exc + if timezone != "UTC": + try: + ZoneInfo(timezone) + except ZoneInfoNotFoundError as exc: + raise ValueError("periodic_report.timezone is unknown") from exc profile_preset = str(value.get("profile_preset") or "").strip() route_ref = str(value.get("route_ref") or "").strip() if enabled and (not profile_preset or not route_ref): diff --git a/loopx/capabilities/periodic_report/machine_defaults.py b/loopx/capabilities/periodic_report/machine_defaults.py index 4c0a047375..4fa242b3fc 100644 --- a/loopx/capabilities/periodic_report/machine_defaults.py +++ b/loopx/capabilities/periodic_report/machine_defaults.py @@ -128,10 +128,11 @@ def normalize_periodic_report_machine_defaults( periodic.get("timezone", "UTC"), "periodic_report.timezone", ) - try: - ZoneInfo(timezone) - except ZoneInfoNotFoundError as exc: - raise ValueError("periodic_report.timezone is unknown") from exc + if timezone != "UTC": + try: + ZoneInfo(timezone) + except ZoneInfoNotFoundError as exc: + raise ValueError("periodic_report.timezone is unknown") from exc normalized_periodic: dict[str, Any] = { "schema_version": PERIODIC_REPORT_MACHINE_DEFAULTS_SCHEMA, "enabled": enabled, @@ -258,10 +259,11 @@ def _normalized_goal_subscription( timezone_name = _text( config.get("timezone", "UTC"), "goal periodic_report.timezone" ) - try: - ZoneInfo(timezone_name) - except ZoneInfoNotFoundError as exc: - raise ValueError("goal periodic_report.timezone is unknown") from exc + if timezone_name != "UTC": + try: + ZoneInfo(timezone_name) + except ZoneInfoNotFoundError as exc: + raise ValueError("goal periodic_report.timezone is unknown") from exc profile_preset = str(config.get("profile_preset") or "").strip() or None route_ref = str(config.get("route_ref") or "").strip() or None if enabled: @@ -292,7 +294,8 @@ def _invalid_goal_subscription_fields(config: Mapping[str, Any]) -> tuple[str, . timezone_name = _text( config.get("timezone", "UTC"), "goal periodic_report.timezone" ) - ZoneInfo(timezone_name) + if timezone_name != "UTC": + ZoneInfo(timezone_name) except (TypeError, ValueError, ZoneInfoNotFoundError): invalid.append("timezone") if enabled is True: diff --git a/tests/capabilities/test_periodic_report_machine_defaults.py b/tests/capabilities/test_periodic_report_machine_defaults.py index 319883bf79..00b2241148 100644 --- a/tests/capabilities/test_periodic_report_machine_defaults.py +++ b/tests/capabilities/test_periodic_report_machine_defaults.py @@ -2,9 +2,11 @@ import json from pathlib import Path +from zoneinfo import ZoneInfoNotFoundError import pytest +from loopx.capabilities.periodic_report import machine_defaults as periodic_defaults from loopx.capabilities.machine_configuration.builtins import ( build_builtin_machine_configuration_registry, ) @@ -88,6 +90,20 @@ def _remove_defaults(runtime_root: Path) -> None: ) +def test_utc_defaults_do_not_require_an_external_timezone_database( + monkeypatch: pytest.MonkeyPatch, +) -> None: + def missing_timezone(_name: str) -> None: + raise ZoneInfoNotFoundError + + monkeypatch.setattr(periodic_defaults, "ZoneInfo", missing_timezone) + + build_builtin_machine_configuration_registry() + subscription = resolve_goal_periodic_report_subscription(_goal("research"), None) + + assert subscription["timezone"] == "UTC" + + def test_machine_defaults_require_a_route_when_weekly_reports_are_enabled() -> None: payload = _defaults() payload["namespaces"]["periodic_report"].pop("route_ref")