From da83e4e8f73a85850de576d2bb12c3f7e4aa6821 Mon Sep 17 00:00:00 2001 From: Peter Byfield Date: Thu, 30 Jan 2025 11:47:27 +0100 Subject: [PATCH] Reverse order of min in FiniteDatetimeRange.intersection In https://github.com/octoenergy/xocto/pull/187 the implementation of FiniteDatetimeRange.intersection was optimised. This introduced a very subtle change in behaviour. Previously when left.end was equal to right.end then the right.end was favoured. In https://github.com/octoenergy/xocto/pull/187 the implementation was (unintentionally) changed to favour left.end. This change reverses that, to again favour right.end. This really shouldn't matter, but it can be signidicant when the TZ of left and right are different. Eventually we'd like to prevent this kind of mixed TZ behaviour (see https://github.com/octoenergy/xocto/issues/192), but in the short term it seems reasonable to make this tiny change to make things consistent again with how they were before. --- tests/test_ranges.py | 32 ++++++++++++++++++++++++++++++++ xocto/ranges.py | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/test_ranges.py b/tests/test_ranges.py index 3b644a0..690edcd 100644 --- a/tests/test_ranges.py +++ b/tests/test_ranges.py @@ -13,6 +13,10 @@ from xocto import localtime, ranges +TZ_LONDON = zoneinfo.ZoneInfo("Europe/London") +TZ_BERLIN = zoneinfo.ZoneInfo("Europe/Berlin") + + @composite def valid_integer_range(draw): boundaries = draw(sampled_from(ranges.RangeBoundaries)) @@ -1174,6 +1178,20 @@ def test_union_with_continuum(self): assert ranges._is_datetime_range(range | other) assert range | other == other | range == ranges.DatetimeRange.continuum() + def test_preference_on_equal_end(self): + r1 = ranges.FiniteDatetimeRange( + datetime.datetime(2020, 1, 1, tzinfo=TZ_LONDON), + datetime.datetime(2022, 1, 1, tzinfo=TZ_LONDON), + ) + r2 = ranges.FiniteDatetimeRange( + datetime.datetime(2021, 1, 1, hour=1, tzinfo=TZ_BERLIN), + datetime.datetime(2022, 1, 1, hour=1, tzinfo=TZ_BERLIN), + ) + assert r1.end == r2.end + union = r1 | r2 + # r1.end was picked over r2.end. + assert union.start.tzinfo == union.end.tzinfo == TZ_LONDON + class TestIntersection: def test_intersection_of_touching_ranges(self): range = ranges.FiniteDatetimeRange( @@ -1237,6 +1255,20 @@ def test_intersection_with_half_finite_range(self): ) ) + def test_preference_on_equal_end(self): + r1 = ranges.FiniteDatetimeRange( + datetime.datetime(2020, 1, 1, tzinfo=TZ_LONDON), + datetime.datetime(2022, 1, 1, tzinfo=TZ_LONDON), + ) + r2 = ranges.FiniteDatetimeRange( + datetime.datetime(2021, 1, 1, hour=1, tzinfo=TZ_BERLIN), + datetime.datetime(2022, 1, 1, hour=1, tzinfo=TZ_BERLIN), + ) + assert r1.end == r2.end + intersection = r1 & r2 + # r2.end was picked over r1.end. + assert intersection.start.tzinfo == intersection.end.tzinfo == TZ_BERLIN + class TestLocalize: def test_converts_timezone(self): # Create a datetime range in Sydney, which is diff --git a/xocto/ranges.py b/xocto/ranges.py index 60c9f62..5dcb25a 100644 --- a/xocto/ranges.py +++ b/xocto/ranges.py @@ -861,7 +861,7 @@ def intersection( if left.end <= right.start: return None else: - return FiniteDatetimeRange(right.start, min(left.end, right.end)) + return FiniteDatetimeRange(right.start, min(right.end, left.end)) base_intersection = super().intersection(other) if base_intersection is None: