fix(time): futurize() must compare in HA-local wall time, not OS timezone - #335
Open
pluskal wants to merge 1 commit into
Open
fix(time): futurize() must compare in HA-local wall time, not OS timezone#335pluskal wants to merge 1 commit into
pluskal wants to merge 1 commit into
Conversation
…zone futurize() built 'today'/'now' from date.today()/datetime.now() (OS timezone) while parse_time()-derived times are naive in the HA-configured timezone. When the host TZ != HA TZ (e.g. host UTC, HA Europe/Prague), a just-fired time callback rescheduled itself to an instant that is 'future' in OS terms but already past in HA-local terms; async_track_point_in_time then re-fired it immediately in a tight loop. Observed as sunrise end_time callbacks re-firing for exactly the UTC-offset window and flooding MQTT with repeated turn_off commands. Anchor both sides of the comparison to HA-local wall time via homeassistant.util.dt (already imported).
There was a problem hiding this comment.
Pull request overview
This PR fixes a timezone mismatch in Model.futurize() by anchoring “now/today” to Home Assistant’s configured local wall time instead of the host/process timezone, preventing immediate re-fire loops when scheduling time-based callbacks.
Changes:
- Switch reference “now”/“today” to HA-local wall time via
homeassistant.util.dtto avoid OS-tz vs HA-tz drift. - Normalize timezone-aware inputs to HA-local naive datetimes before comparing/scheduling.
Comments suppressed due to low confidence (1)
custom_components/entity_controller/init.py:1709
- For consistency with the rest of the module (e.g., parse_time/make_naive), consider using make_naive() for the aware->HA-local-naive conversion instead of open-coding dt.as_local(...).replace(tzinfo=None).
if t.tzinfo is not None:
t = dt.as_local(t).replace(tzinfo=None)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # again immediately, in an endless loop (2026-07-09 incident: sunrise | ||
| # end_time callbacks re-fired for exactly the UTC-offset window, | ||
| # flooding MQTT with turn_off commands). | ||
| now_local = dt.as_local(dt.now()).replace(tzinfo=None) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Model.futurize()builds the reference "today"/"now" fromdate.today()/datetime.now(), which follow the OS/process timezone. The times it compares against come fromparse_time()and are naive values in the HA-configured timezone. When the two differ (e.g. host on UTC, Home Assistant onEurope/Prague), a time-based callback that has just fired reschedules itself to an instant that is still "in the future" in OS-local terms but already in the past in HA-local terms.async_track_point_in_timethen fires it again immediately, and the callback re-arms itself in a tight loop.In practice this showed up as
end_time/sunrise callbacks re-firing continuously for exactly the UTC-offset-sized window, flooding MQTT with repeatedturn_offcommands until the offset window elapsed.This bites any deployment where the container/host timezone is not the same as the HA timezone (common on NixOS/containerized installs that keep the host on UTC).
Fix
Anchor both sides of the comparison to HA-local wall time using
homeassistant.util.dt(already imported at module top):No behavior change when host TZ == HA TZ.
Testing
Deployed on an installation with host=UTC / HA=Europe/Prague; the callback re-fire loops / MQTT command floods around sunrise are gone, and normal time-restriction scheduling is unaffected.