From 772442b54290157394409d34a65d02b7e3e2ada4 Mon Sep 17 00:00:00 2001 From: Jan Pluskal Date: Fri, 24 Jul 2026 09:26:19 +0200 Subject: [PATCH] fix(time): futurize() must compare in HA-local wall time, not OS timezone 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). --- custom_components/entity_controller/__init__.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/custom_components/entity_controller/__init__.py b/custom_components/entity_controller/__init__.py index 777b86d..7ba331d 100644 --- a/custom_components/entity_controller/__init__.py +++ b/custom_components/entity_controller/__init__.py @@ -1690,12 +1690,24 @@ def futurize(self, timet): # self.log.debug("-------------------- futurize ------------------------") # self.log.debug("Input (naive) %s ", timet) - today = date.today() + # Compare in HA-local wall time, NOT the process timezone: parse_time() + # returns naive times in the HA-configured timezone, while + # datetime.now()/date.today() follow the OS timezone. When the two + # differ (e.g. host on UTC, HA on Europe/Prague), a just-fired callback + # rescheduled itself to a time that is "future" in OS terms but already + # past in HA-local terms — async_track_point_in_time then fires it + # 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) + today = now_local.date() try: t = datetime.combine(today, timet) except TypeError as e: t = timet - x = datetime.now() + if t.tzinfo is not None: + t = dt.as_local(t).replace(tzinfo=None) + x = now_local # self.log.debug("input time: " + str(t)) # self.log.debug("current time: " + str(x))