Skip to content

fix(time): futurize() must compare in HA-local wall time, not OS timezone - #335

Open
pluskal wants to merge 1 commit into
danobot:masterfrom
pluskal:fix/futurize-ha-local-timezone
Open

fix(time): futurize() must compare in HA-local wall time, not OS timezone#335
pluskal wants to merge 1 commit into
danobot:masterfrom
pluskal:fix/futurize-ha-local-timezone

Conversation

@pluskal

@pluskal pluskal commented Jul 24, 2026

Copy link
Copy Markdown

Problem

Model.futurize() builds the reference "today"/"now" from date.today() / datetime.now(), which follow the OS/process timezone. The times it compares against come from parse_time() and are naive values in the HA-configured timezone. When the two differ (e.g. host on UTC, Home Assistant on Europe/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_time then 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 repeated turn_off commands 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):

now_local = dt.as_local(dt.now()).replace(tzinfo=None)
today = now_local.date()
...
if t.tzinfo is not None:
    t = dt.as_local(t).replace(tzinfo=None)
x = now_local

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.

…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).
Copilot AI review requested due to automatic review settings July 24, 2026 07:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.dt to 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants