mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 21:06:19 +00:00
Use event loop scheduling for tracking time patterns (#38021)
* Use event loop scheduling for tracking time patterns * make patching of time targetable * patch time tests since time can tick to match during the test * fix more tests * time can only move forward * time can only move forward * back to 100% coverage * simplify since the event loop time cannot move backwards * simplify some more * revert simplify * Revert "revert simplify" This reverts commitbd42f232f6. * Revert "simplify some more" This reverts commit2a6c57d514. * Revert "simplify since the event loop time cannot move backwards" This reverts commit3b13714ef4. * Attempt another simplify * time does not move backwards in the last two * remove next_time <= now check * fix previous merge error
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Set up some common test helper things."""
|
||||
import asyncio
|
||||
import datetime
|
||||
import functools
|
||||
import logging
|
||||
import threading
|
||||
@@ -387,8 +388,71 @@ def legacy_patchable_time():
|
||||
|
||||
return async_unsub
|
||||
|
||||
@ha.callback
|
||||
@loader.bind_hass
|
||||
def async_track_utc_time_change(
|
||||
hass, action, hour=None, minute=None, second=None, local=False
|
||||
):
|
||||
"""Add a listener that will fire if time matches a pattern."""
|
||||
# We do not have to wrap the function with time pattern matching logic
|
||||
# if no pattern given
|
||||
if all(val is None for val in (hour, minute, second)):
|
||||
|
||||
@ha.callback
|
||||
def time_change_listener(ev) -> None:
|
||||
"""Fire every time event that comes in."""
|
||||
hass.async_run_job(action, ev.data[ATTR_NOW])
|
||||
|
||||
return hass.bus.async_listen(EVENT_TIME_CHANGED, time_change_listener)
|
||||
|
||||
matching_seconds = event.dt_util.parse_time_expression(second, 0, 59)
|
||||
matching_minutes = event.dt_util.parse_time_expression(minute, 0, 59)
|
||||
matching_hours = event.dt_util.parse_time_expression(hour, 0, 23)
|
||||
|
||||
next_time = None
|
||||
|
||||
def calculate_next(now) -> None:
|
||||
"""Calculate and set the next time the trigger should fire."""
|
||||
nonlocal next_time
|
||||
|
||||
localized_now = event.dt_util.as_local(now) if local else now
|
||||
next_time = event.dt_util.find_next_time_expression_time(
|
||||
localized_now, matching_seconds, matching_minutes, matching_hours
|
||||
)
|
||||
|
||||
# Make sure rolling back the clock doesn't prevent the timer from
|
||||
# triggering.
|
||||
last_now = None
|
||||
|
||||
@ha.callback
|
||||
def pattern_time_change_listener(ev) -> None:
|
||||
"""Listen for matching time_changed events."""
|
||||
nonlocal next_time, last_now
|
||||
|
||||
now = ev.data[ATTR_NOW]
|
||||
|
||||
if last_now is None or now < last_now:
|
||||
# Time rolled back or next time not yet calculated
|
||||
calculate_next(now)
|
||||
|
||||
last_now = now
|
||||
|
||||
if next_time <= now:
|
||||
hass.async_run_job(
|
||||
action, event.dt_util.as_local(now) if local else now
|
||||
)
|
||||
calculate_next(now + datetime.timedelta(seconds=1))
|
||||
|
||||
# We can't use async_track_point_in_utc_time here because it would
|
||||
# break in the case that the system time abruptly jumps backwards.
|
||||
# Our custom last_now logic takes care of resolving that scenario.
|
||||
return hass.bus.async_listen(EVENT_TIME_CHANGED, pattern_time_change_listener)
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.event.async_track_point_in_utc_time",
|
||||
async_track_point_in_utc_time,
|
||||
), patch(
|
||||
"homeassistant.helpers.event.async_track_utc_time_change",
|
||||
async_track_utc_time_change,
|
||||
):
|
||||
yield
|
||||
|
||||
Reference in New Issue
Block a user