diff --git a/flexmeasures/utils/tests/test_time_utils.py b/flexmeasures/utils/tests/test_time_utils.py index 196c7ae6c..961baf729 100644 --- a/flexmeasures/utils/tests/test_time_utils.py +++ b/flexmeasures/utils/tests/test_time_utils.py @@ -97,48 +97,72 @@ def test_naturalized_datetime_str( @pytest.mark.parametrize( - "window_size, now, exp_start, exp_end", + "window_size, now, exp_start, exp_end, grace_period", [ ( 5, datetime(2021, 4, 30, 15, 1), datetime(2021, 4, 30, 14, 55), datetime(2021, 4, 30, 15), + 0, ), ( 15, datetime(2021, 4, 30, 3, 36), datetime(2021, 4, 30, 3, 15), datetime(2021, 4, 30, 3, 30), + 0, + ), + ( + 5, + datetime(2021, 4, 30, 9, 15, 16), + datetime(2021, 4, 30, 9, 10), + datetime(2021, 4, 30, 9, 15), + 0, ), ( 10, datetime(2021, 4, 30, 0, 5), datetime(2021, 4, 29, 23, 50), datetime(2021, 4, 30, 0, 0), + 0, + ), + ( + 5, + datetime( + 2021, 5, 20, 10, 5, 34 + ), # less than grace period into the new window + datetime(2021, 5, 20, 9, 55), + datetime(2021, 5, 20, 10, 0), + 60, ), ( 5, - datetime(2021, 5, 20, 10, 5, 34), # boundary condition + datetime(2021, 5, 20, 10, 7, 4), datetime(2021, 5, 20, 9, 55), datetime(2021, 5, 20, 10, 0), + 130, # grace period > 2 minutes ), ( 60, datetime(2021, 1, 1, 0, 4), # new year datetime(2020, 12, 31, 23, 00), datetime(2021, 1, 1, 0, 0), + 0, ), ( 60, datetime(2021, 3, 28, 3, 10), # DST transition datetime(2021, 3, 28, 2), datetime(2021, 3, 28, 3), + 0, ), ], ) -def test_recent_clocktime_window(window_size, now, exp_start, exp_end): - start, end = get_most_recent_clocktime_window(window_size, now=now) +def test_recent_clocktime_window(window_size, now, exp_start, exp_end, grace_period): + start, end = get_most_recent_clocktime_window( + window_size, now=now, grace_period_in_seconds=grace_period + ) assert start == exp_start assert end == exp_end diff --git a/flexmeasures/utils/time_utils.py b/flexmeasures/utils/time_utils.py index 129e20998..ae3a69234 100644 --- a/flexmeasures/utils/time_utils.py +++ b/flexmeasures/utils/time_utils.py @@ -199,7 +199,9 @@ def get_most_recent_hour() -> datetime: def get_most_recent_clocktime_window( - window_size_in_minutes: int, now: Optional[datetime] = None + window_size_in_minutes: int, + now: Optional[datetime] = None, + grace_period_in_seconds: Optional[int] = 0, ) -> Tuple[datetime, datetime]: """ Calculate a recent time window, returning a start and end minute so that @@ -208,6 +210,9 @@ def get_most_recent_clocktime_window( Calling this function at 15:01:xx with window size 5 -> (14:55:00, 15:00:00) Calling this function at 03:36:xx with window size 15 -> (03:15:00, 03:30:00) + We can demand a grace period (of x seconds) to have passed before we are ready to accept that we're in a new window: + Calling this function at 15:00:16 with window size 5 and grace period of 30 seconds -> (14:50:00, 14:55:00) + window_size_in_minutes is assumed to > 0 and < = 60, and a divisor of 60 (1, 2, ..., 30, 60). If now is not given, the current server time is used. @@ -221,7 +226,13 @@ def get_most_recent_clocktime_window( assert 60 % window_size_in_minutes == 0 if now is None: now = server_now() - last_full_minute = now.replace(second=0, microsecond=0) - timedelta(minutes=1) + last_full_minute = now.replace(second=0, microsecond=0) + if ( + grace_period_in_seconds is not None + and grace_period_in_seconds > 0 + and (now - last_full_minute).seconds < grace_period_in_seconds + ): + last_full_minute -= timedelta(minutes=1 + grace_period_in_seconds // 60) last_round_minute = last_full_minute.minute - ( last_full_minute.minute % window_size_in_minutes )