Skip to content

Commit

Permalink
Add date_range_for_midnight_range utility function
Browse files Browse the repository at this point in the history
This will be used to DRY-up converting datetime ranges.

Before:

    ranges.FiniteDateRange(
        midnight_aligned_range.start.date(),
        midnight_aligned_range.end.date() - timedelta(days=1),
    )

After:

    ranges.date_range_for_midnight_range(midnight_aligned_range)

Pull Request in Kraken Core to demo usage [^1].

[^1]: octoenergy/kraken-core#166268
  • Loading branch information
benthorner committed Dec 5, 2024
1 parent 7eb3a98 commit 2363944
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 0 deletions.
48 changes: 48 additions & 0 deletions tests/test_ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,54 @@ def test_yields_correct_ranges(self, row):
assert result == row["expected"]


class TestDateRangeForMidnightRange:
def test_returns_date_range(self):
dt_range = ranges.FiniteDatetimeRange(
datetime.datetime(2020, 1, 1),
datetime.datetime(2020, 1, 10),
)

assert ranges.date_range_for_midnight_range(dt_range) == ranges.FiniteDateRange(
datetime.date(2020, 1, 1),
datetime.date(2020, 1, 9),
)

def test_errors_if_different_timezones(self):
dt_range = ranges.FiniteDatetimeRange(
datetime.datetime(2020, 1, 1, tzinfo=zoneinfo.ZoneInfo("Asia/Dubai")),
datetime.datetime(
2020, 1, 10, tzinfo=zoneinfo.ZoneInfo("Australia/Sydney")
),
)

with pytest.raises(ValueError) as exc_info:
ranges.date_range_for_midnight_range(dt_range)

assert "Start and end in different timezones" in str(exc_info.value)

def test_errors_if_start_not_midnight(self):
dt_range = ranges.FiniteDatetimeRange(
datetime.datetime(2020, 1, 1, hour=1),
datetime.datetime(2020, 1, 10),
)

with pytest.raises(ValueError) as exc_info:
ranges.date_range_for_midnight_range(dt_range)

assert "Start of range is not midnight-aligned" in str(exc_info.value)

def test_errors_if_end_not_midnight(self):
dt_range = ranges.FiniteDatetimeRange(
datetime.datetime(2020, 1, 1),
datetime.datetime(2020, 1, 10, hour=1),
)

with pytest.raises(ValueError) as exc_info:
ranges.date_range_for_midnight_range(dt_range)

assert "End of range is not midnight-aligned" in str(exc_info.value)


def _rangeset_from_string(rangeset_str: str) -> ranges.RangeSet[int]:
"""
Convenience method to make test declarations clearer.
Expand Down
31 changes: 31 additions & 0 deletions xocto/ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -1085,3 +1085,34 @@ def iterate_over_months(

yield FiniteDatetimeRange(start_at, this_end)
start_at = next_start


def date_range_for_midnight_range(
range: FiniteDatetimeRange,
) -> FiniteDateRange:
"""
Returns the date range of a midnight-aligned datetime range.
This can be useful where a range is available at datetime granularity,
but is used in functions that operate at date granularity.
Raises:
ValueError:
If the range boundaries are in different timezeones.
If the range boundaries are not midnight-aligned.
"""
# First check range timezone is uniform.
if range.start.tzinfo != range.end.tzinfo:
raise ValueError("Start and end in different timezones")

# Check datetimes are both midnight-aligned.
if range.start.time() != datetime.time(0, 0):
raise ValueError("Start of range is not midnight-aligned")

if range.end.time() != datetime.time(0, 0):
raise ValueError("End of range is not midnight-aligned")

return FiniteDateRange(
range.start.date(),
localtime.day_before(range.end.date()),
)

0 comments on commit 2363944

Please sign in to comment.