mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-30 09:41:57 +00:00
fix: honor DST boundaries in appliance scheduling
Compare elapsed offsets when checking and merging appliance windows so both occurrences of the autumn hour obey the configured boundaries. Preserve calendar durations when finding the earliest valid start. Add optimizer and calendar-day regressions, and consolidate the existing time-window regression files into test_configabc classes.
This commit is contained in:
@@ -431,7 +431,8 @@ class TimeWindow(SettingsBaseModel):
|
||||
|
||||
window_start, window_end = self._window_start_end(reference_date)
|
||||
|
||||
if duration > window_end - window_start:
|
||||
# Add first so calendar durations retain their meaning across DST.
|
||||
if (window_start + duration - window_end).total_seconds() > 0:
|
||||
return None
|
||||
|
||||
return window_start
|
||||
|
||||
@@ -565,12 +565,16 @@ class HomeAppliance:
|
||||
) -> bool:
|
||||
"""Check full coverage including windows anchored on previous local dates.
|
||||
|
||||
Date and weekday restrictions belong to the window's opening day.
|
||||
Date, weekday, and month restrictions belong to the window's opening day.
|
||||
Per-cycle windows retain their existing union semantics; shared windows
|
||||
require one complete containing occurrence.
|
||||
"""
|
||||
intervals: list[tuple[DateTime, DateTime]] = []
|
||||
# Compare elapsed offsets from the run start, including when sorting
|
||||
# and merging intervals. Wall-clock ordering cannot distinguish the
|
||||
# two occurrences of an hour repeated during a DST transition.
|
||||
intervals: list[tuple[float, float]] = []
|
||||
run_end = start + duration
|
||||
run_seconds = (run_end - start).total_seconds()
|
||||
for window in windows:
|
||||
days_back = math.ceil(window.duration.total_seconds() / 86400) + 1
|
||||
for offset in range(days_back + 1):
|
||||
@@ -582,16 +586,18 @@ class HomeAppliance:
|
||||
if window.month is not None and anchor.month != window.month:
|
||||
continue
|
||||
opening, closing = window._window_start_end(anchor)
|
||||
if opening <= start and run_end <= closing:
|
||||
opening_offset = (opening - start).total_seconds()
|
||||
closing_offset = (closing - start).total_seconds()
|
||||
if opening_offset <= 0 and run_seconds <= closing_offset:
|
||||
return True
|
||||
if merge and opening < run_end and closing > start:
|
||||
intervals.append((opening, closing))
|
||||
covered_until = start
|
||||
for opening, closing in sorted(intervals):
|
||||
if opening > covered_until:
|
||||
if merge and opening_offset < run_seconds and closing_offset > 0:
|
||||
intervals.append((opening_offset, closing_offset))
|
||||
covered_until = 0.0
|
||||
for opening_offset, closing_offset in sorted(intervals):
|
||||
if opening_offset > covered_until:
|
||||
break
|
||||
covered_until = max(covered_until, closing)
|
||||
if covered_until >= run_end:
|
||||
covered_until = max(covered_until, closing_offset)
|
||||
if covered_until >= run_seconds:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user