Make async_track_time_change smarter (#17199)

* Make async_track_time_change smarter

* Move to util/dt

* Remove unnecessary check

* Lint

* Remove tzinfo check

* Remove check

* Add comment about async_track_point_in_utc_time

* Fix typing check

* Lint
This commit is contained in:
Otto Winter 2018-10-09 10:14:18 +02:00 committed by Paulus Schoutsen
parent 9190fe1c21
commit 26cf5acd5b
5 changed files with 502 additions and 119 deletions

View file

@ -322,13 +322,13 @@ track_sunset = threaded_listener_factory(async_track_sunset)
@callback
@bind_hass
def async_track_utc_time_change(hass, action, year=None, month=None, day=None,
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 (year, month, day, hour, minute, second)):
if all(val is None for val in (hour, minute, second)):
@callback
def time_change_listener(event):
"""Fire every time event that comes in."""
@ -336,24 +336,45 @@ def async_track_utc_time_change(hass, action, year=None, month=None, day=None,
return hass.bus.async_listen(EVENT_TIME_CHANGED, time_change_listener)
pmp = _process_time_match
year, month, day = pmp(year), pmp(month), pmp(day)
hour, minute, second = pmp(hour), pmp(minute), pmp(second)
matching_seconds = dt_util.parse_time_expression(second, 0, 59)
matching_minutes = dt_util.parse_time_expression(minute, 0, 59)
matching_hours = dt_util.parse_time_expression(hour, 0, 23)
next_time = None
def calculate_next(now):
"""Calculate and set the next time the trigger should fire."""
nonlocal next_time
localized_now = dt_util.as_local(now) if local else now
next_time = 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
@callback
def pattern_time_change_listener(event):
"""Listen for matching time_changed events."""
nonlocal next_time, last_now
now = event.data[ATTR_NOW]
if local:
now = dt_util.as_local(now)
if last_now is None or now < last_now:
# Time rolled back or next time not yet calculated
calculate_next(now)
# pylint: disable=too-many-boolean-expressions
if second(now.second) and minute(now.minute) and hour(now.hour) and \
day(now.day) and month(now.month) and year(now.year):
last_now = now
hass.async_run_job(action, now)
if next_time <= now:
hass.async_run_job(action, event.data[ATTR_NOW])
calculate_next(now + 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)
@ -363,11 +384,10 @@ track_utc_time_change = threaded_listener_factory(async_track_utc_time_change)
@callback
@bind_hass
def async_track_time_change(hass, action, year=None, month=None, day=None,
hour=None, minute=None, second=None):
def async_track_time_change(hass, action, hour=None, minute=None, second=None):
"""Add a listener that will fire if UTC time matches a pattern."""
return async_track_utc_time_change(hass, action, year, month, day, hour,
minute, second, local=True)
return async_track_utc_time_change(hass, action, hour, minute, second,
local=True)
track_time_change = threaded_listener_factory(async_track_time_change)
@ -383,19 +403,3 @@ def _process_state_match(parameter):
parameter = tuple(parameter)
return lambda state: state in parameter
def _process_time_match(parameter):
"""Wrap parameter in a tuple if it is not one and returns it."""
if parameter is None or parameter == MATCH_ALL:
return lambda _: True
if isinstance(parameter, str) and parameter.startswith('/'):
parameter = float(parameter[1:])
return lambda time: time % parameter == 0
if isinstance(parameter, str) or not hasattr(parameter, '__iter__'):
return lambda time: time == parameter
parameter = tuple(parameter)
return lambda time: time in parameter