Repository navigation
Pass the configured timezone to zigpy for Time cluster DST support - #974
Conversation
28919dc to
3115e51
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #974 +/- ##
=======================================
Coverage 97.23% 97.23%
=======================================
Files 61 61
Lines 10704 10710 +6
=======================================
+ Hits 10408 10414 +6
Misses 296 296 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Looks good. The change is small and does what the description says: local_timezone now reaches ControllerApplication.new(), which zigpy 2.4.0 (the pinned version) accepts as a keyword-only argument and stores on the controller, where the Time cluster reads it.
Verified (6 checks)
- The
local_timezonekwarg onControllerApplication.newexists in the pinnedzigpy==2.4.0. The Time cluster readsapplication.local_timezonein_handle_read_attributes, andNonemeans no zone authority (Master_for_Zone_and_DSTis not set). - The only runtime consumer of
ZHAData.local_timezonein zha is the Sinopé_async_update_time. TheNoneto UTC fallback keeps its behaviour identical to the olddt.UTCdefault, anddatetime.now(None)would indeed have returned a naive local time. - Home Assistant core already passes a
ZoneInfowhen it buildsZHAData(homeassistant/components/zha/helpers.py), so HA setups get the zone at startup with this change. The runtimeEVENT_CORE_CONFIG_UPDATEpath in__init__.pystill writesconfig.local_timezonedirectly, which is the companion core change the description already calls out. update_local_timezoneis safe before init and after shutdown, sinceapplication_controllerisNonein both cases.tests/test_gateway.pyandtests/test_climate.pypass (118 passed).ruff check,ruff format --checkandmypy zha/are clean in the worktree venv, and CI is green on all three Python versions.- The earlier docstring thread from puddly is resolved and the docstring is now a single line.
One optional nit is inline.
There was a problem hiding this comment.
🟡 Changes recommended
A timezone update during asynchronous controller creation can leave the controller reporting the previous timezone.
1 open issue
What changed in this PR
This PR forwards ZHA’s configured timezone to zigpy so its Time cluster can report local offsets and daylight-saving attributes, addressing #967.
Changes:
- Pass the configured timezone at controller creation and add a runtime update method.
- Represent an unconfigured timezone as
None, preserving the Sinopé thermostat’s UTC fallback. - Add timezone forwarding and lifecycle tests, and update climate tests.
| File | Description |
|---|---|
zha/application/platforms/climate/__init__.py |
Preserve UTC fallback when no timezone is configured. |
zha/application/helpers.py |
Default the optional timezone to None. |
zha/application/gateway.py |
Forward timezone configuration and support runtime updates. |
tests/test_gateway.py |
Test timezone forwarding, synchronization, and lifecycle handling. |
tests/test_climate.py |
Verify the fallback and use the runtime update method. |
Review effort: Balanced

Closes #967
Passes the gateway's configured timezone to
ControllerApplication.new()so zigpy's ZCL Time cluster server can report the correct UTC offset and DST attributes (dst_start,dst_end,dst_shift) to Zigbee devices. Without this, zigpy reported a zero UTC offset and no DST, causing devices to display UTC rather than local time.Changes
zha/application/helpers.pyZHAData.local_timezonedefault changed fromdatetime.UTCtoNone.Nonemeans "no timezone configured" and is passed through to zigpy as-is, correctly suppressingMaster_for_Zone_and_DSTclaims for a timezone the user never set. The previousdatetime.UTCdefault was only ever used by the Sinopé climate entity — it was never forwarded to zigpy — so there is no change in existing behavior.zha/application/gateway.pylocal_timezone=self.config.local_timezoneadded to theControllerApplication.new()call in_async_initialize, so zigpy's Time cluster receives the correct timezone at startup.Gateway.update_local_timezone(tz)added as the canonical way to update the timezone at runtime. Direct writes togateway.config.local_timezoneonly update theZHADatacopy; this method keepsControllerApplication.local_timezonein sync as well. If called before initialization or after shutdown,ZHADatais still updated and the controller will receive the correct value on the nextasync_initialize.zha/application/platforms/climate/__init__.pySinopeTechnologiesThermostat._async_update_timenow falls back todatetime.UTCwhenlocal_timezoneisNone, preserving existing behavior for unconfigured setups.datetime.now(None)returns a naive system-local datetime and would silently produce wrongsecs_since_2kvalues on non-UTC hosts.tests/test_gateway.pytest_configured_timezone_passed_to_controller— parametrized overNoneand a realZoneInfo; verifies both that the kwarg is forwarded toControllerApplication.new()and that it lands on the controller instance.test_update_local_timezone_syncs_controller— verifies bothZHADataandapplication_controllerare updated together, including clearing back toNone.test_update_local_timezone_before_init— verifies no crash when called beforeasync_initializeor aftershutdown, and thatZHADatais still updated.tests/test_climate.pylocal_timezoneisNonebefore the UTC fallback case, making the precondition clear.gateway.update_local_timezone()for the runtime timezone change, consistent with the documented API.Notes
A companion change is recommended in
homeassistant-core: theEVENT_CORE_CONFIG_UPDATEhandler currently writeszha_gateway.config.local_timezone = ZoneInfo(hass.config.time_zone)directly, which bypassesControllerApplication.local_timezone. Swapping that tozha_gateway.update_local_timezone(ZoneInfo(hass.config.time_zone))will keep both copies in sync on runtime timezone changes without requiring a ZHA restart.