Ensure we always fire time pattern changes after microsecond 0 (#39302)

This commit is contained in:
J. Nick Koston 2020-08-28 09:27:25 -05:00 committed by GitHub
parent 7c191388a9
commit f8704a2dfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 33 additions and 6 deletions

View File

@ -34,6 +34,8 @@ from homeassistant.loader import bind_hass
from homeassistant.util import dt as dt_util
from homeassistant.util.async_ import run_callback_threadsafe
MAX_TIME_TRACKING_ERROR = 0.001
TRACK_STATE_CHANGE_CALLBACKS = "track_state_change_callbacks"
TRACK_STATE_CHANGE_LISTENER = "track_state_change_listener"
@ -996,19 +998,40 @@ def async_track_utc_time_change(
calculate_next(now + timedelta(seconds=1))
# We always get time.time() first to avoid time.time()
# ticking forward after fetching hass.loop.time()
# and callback being scheduled a few microseconds early
cancel_callback = hass.loop.call_at(
-time.time() + hass.loop.time() + next_time.timestamp(),
-time.time()
+ hass.loop.time()
+ next_time.timestamp()
+ MAX_TIME_TRACKING_ERROR,
pattern_time_change_listener,
)
# We always get time.time() first to avoid time.time()
# ticking forward after fetching hass.loop.time()
# and callback being scheduled a few microseconds early
# and callback being scheduled a few microseconds early.
#
# Since we loose additional time calling `hass.loop.time()`
# we add MAX_TIME_TRACKING_ERROR to ensure
# we always schedule the call within the time window between
# second and the next second.
#
# For example:
# If the clock ticks forward 30 microseconds when fectching
# `hass.loop.time()` and we want the event to fire at exactly
# 03:00:00.000000, the event would actually fire around
# 02:59:59.999970. To ensure we always fire sometime between
# 03:00:00.000000 and 03:00:00.999999 we add
# MAX_TIME_TRACKING_ERROR to make up for the time
# lost fetching the time. This ensures we do not fire the
# event before the next time pattern match which would result
# in the event being fired again since we would otherwise
# potentially fire early.
#
cancel_callback = hass.loop.call_at(
-time.time() + hass.loop.time() + next_time.timestamp(),
-time.time()
+ hass.loop.time()
+ next_time.timestamp()
+ MAX_TIME_TRACKING_ERROR,
pattern_time_change_listener,
)

View File

@ -219,6 +219,10 @@ def test_find_next_time_expression_time_basic():
datetime(2018, 10, 7, 10, 30, 0), 5, 0, 0
)
assert find(datetime(2018, 10, 7, 10, 30, 0, 999999), "*", "/30", 0) == datetime(
2018, 10, 7, 10, 30, 0
)
def test_find_next_time_expression_time_dst():
"""Test daylight saving time for find_next_time_expression_time."""