From 4e9bc9eaffd464f192d187a01771a86699b2f932 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 May 2022 15:13:32 -0400 Subject: [PATCH] Small cleanups to find_next_time_expression and addition of tests (#71845) --- homeassistant/util/dt.py | 8 +++---- tests/util/test_dt.py | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index 7c0a4923e71..c7073c0306f 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -339,8 +339,8 @@ def find_next_time_expression_time( now += dt.timedelta(seconds=1) continue - now_is_ambiguous = _datetime_ambiguous(now) - result_is_ambiguous = _datetime_ambiguous(result) + if not _datetime_ambiguous(now): + return result # When leaving DST and clocks are turned backward. # Then there are wall clock times that are ambiguous i.e. exist with DST and without DST @@ -348,7 +348,7 @@ def find_next_time_expression_time( # in a day. # Example: on 2021.10.31 02:00:00 in CET timezone clocks are turned backward an hour - if now_is_ambiguous and result_is_ambiguous: + if _datetime_ambiguous(result): # `now` and `result` are both ambiguous, so the next match happens # _within_ the current fold. @@ -357,7 +357,7 @@ def find_next_time_expression_time( # 2. 2021.10.31 02:00:00+01:00 with pattern 02:30 -> 2021.10.31 02:30:00+01:00 return result.replace(fold=now.fold) - if now_is_ambiguous and now.fold == 0 and not result_is_ambiguous: + if now.fold == 0: # `now` is in the first fold, but result is not ambiguous (meaning it no longer matches # within the fold). # -> Check if result matches in the next fold. If so, emit that match diff --git a/tests/util/test_dt.py b/tests/util/test_dt.py index 6e499e6e6f1..c7992e05068 100644 --- a/tests/util/test_dt.py +++ b/tests/util/test_dt.py @@ -641,3 +641,53 @@ def test_find_next_time_expression_time_leave_dst_chicago_past_the_fold_ahead_2_ assert dt_util.as_utc(next_time) == datetime( 2021, 11, 7, 8, 20, 1, tzinfo=dt_util.UTC ) + + +def test_find_next_time_expression_microseconds(): + """Test finding next time expression with microsecond clock drift.""" + hour_minute_second = (None, "5", "10") + test_time = datetime(2022, 5, 13, 0, 5, 9, tzinfo=dt_util.UTC) + matching_hours, matching_minutes, matching_seconds = _get_matches( + *hour_minute_second + ) + next_time = dt_util.find_next_time_expression_time( + test_time, matching_seconds, matching_minutes, matching_hours + ) + assert next_time == datetime(2022, 5, 13, 0, 5, 10, tzinfo=dt_util.UTC) + next_time_last_microsecond_plus_one = next_time.replace( + microsecond=999999 + ) + timedelta(seconds=1) + time_after = dt_util.find_next_time_expression_time( + next_time_last_microsecond_plus_one, + matching_seconds, + matching_minutes, + matching_hours, + ) + assert time_after == datetime(2022, 5, 13, 1, 5, 10, tzinfo=dt_util.UTC) + + +def test_find_next_time_expression_tenth_second_pattern_does_not_drift_entering_dst(): + """Test finding next time expression tenth second pattern does not drift entering dst.""" + tz = dt_util.get_time_zone("America/Chicago") + dt_util.set_default_time_zone(tz) + tenth_second_pattern = (None, None, "10") + # Entering DST, clocks go forward + test_time = datetime(2021, 3, 15, 2, 30, 0, tzinfo=tz, fold=0) + matching_hours, matching_minutes, matching_seconds = _get_matches( + *tenth_second_pattern + ) + next_time = dt_util.find_next_time_expression_time( + test_time, matching_seconds, matching_minutes, matching_hours + ) + assert next_time == datetime(2021, 3, 15, 2, 30, 10, tzinfo=tz) + prev_target = next_time + for i in range(1000): + next_target = dt_util.find_next_time_expression_time( + prev_target.replace(microsecond=999999) + timedelta(seconds=1), + matching_seconds, + matching_minutes, + matching_hours, + ) + assert (next_target - prev_target).total_seconds() == 60 + assert next_target.second == 10 + prev_target = next_target