mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
fix(interval_pool): fix DST spring-forward causing missing tomorrow intervals
_get_cached_intervals() used fixed-offset datetimes from fromisoformat() for iteration. When start and end boundaries span a DST transition (e.g., +01:00 CET → +02:00 CEST), the loop's end check compared UTC values, stopping 1 hour early on spring-forward days. This caused the last 4 quarter-hourly intervals of "tomorrow" to be missing, making the binary sensor "Tomorrow data available" show Off even when full data was present. Changed iteration to use naive local timestamps, matching the index key format (timezone stripped via [:19]). The end boundary comparison now works correctly regardless of DST transitions. Impact: Binary sensor "Tomorrow data available" now correctly shows On on DST spring-forward days. Affects all European users on the last Sunday of March each year.
This commit is contained in:
parent
00a653396c
commit
0381749e6f
1 changed files with 20 additions and 7 deletions
|
|
@ -464,17 +464,30 @@ class TibberPricesIntervalPool:
|
|||
start_time_dt = datetime.fromisoformat(start_time_iso)
|
||||
end_time_dt = datetime.fromisoformat(end_time_iso)
|
||||
|
||||
# CRITICAL: Use NAIVE local timestamps for iteration.
|
||||
#
|
||||
# Index keys are naive local timestamps (timezone stripped via [:19]).
|
||||
# When start and end span a DST transition, they have different UTC offsets
|
||||
# (e.g., start=+01:00 CET, end=+02:00 CEST). Using fixed-offset datetimes
|
||||
# from fromisoformat() causes the loop to compare UTC values for the end
|
||||
# boundary, ending 1 hour early on spring-forward days (or 1 hour late on
|
||||
# fall-back days).
|
||||
#
|
||||
# By iterating in naive local time, we match the index key format exactly
|
||||
# and the end boundary comparison works correctly regardless of DST.
|
||||
current_naive = start_time_dt.replace(tzinfo=None)
|
||||
end_naive = end_time_dt.replace(tzinfo=None)
|
||||
|
||||
# Use index to find intervals: iterate through expected timestamps
|
||||
result = []
|
||||
current_dt = start_time_dt
|
||||
|
||||
# Determine interval step (15 min post-2025-10-01, 60 min pre)
|
||||
resolution_change_dt = datetime(2025, 10, 1, tzinfo=start_time_dt.tzinfo)
|
||||
interval_minutes = INTERVAL_QUARTER_HOURLY if current_dt >= resolution_change_dt else INTERVAL_HOURLY
|
||||
resolution_change_naive = datetime(2025, 10, 1) # noqa: DTZ001
|
||||
interval_minutes = INTERVAL_QUARTER_HOURLY if current_naive >= resolution_change_naive else INTERVAL_HOURLY
|
||||
|
||||
while current_dt < end_time_dt:
|
||||
while current_naive < end_naive:
|
||||
# Check if this timestamp exists in index (O(1) lookup)
|
||||
current_dt_key = current_dt.isoformat()[:19]
|
||||
current_dt_key = current_naive.isoformat()[:19]
|
||||
location = self._index.get(current_dt_key)
|
||||
|
||||
if location is not None:
|
||||
|
|
@ -487,10 +500,10 @@ class TibberPricesIntervalPool:
|
|||
result.append(dict(interval))
|
||||
|
||||
# Move to next expected interval
|
||||
current_dt += timedelta(minutes=interval_minutes)
|
||||
current_naive += timedelta(minutes=interval_minutes)
|
||||
|
||||
# Handle resolution change boundary
|
||||
if interval_minutes == INTERVAL_HOURLY and current_dt >= resolution_change_dt:
|
||||
if interval_minutes == INTERVAL_HOURLY and current_naive >= resolution_change_naive:
|
||||
interval_minutes = INTERVAL_QUARTER_HOURLY
|
||||
|
||||
_LOGGER_DETAILS.debug(
|
||||
|
|
|
|||
Loading…
Reference in a new issue