Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions tests/test_domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,30 @@ def test_split_into_days_keeps_shifted_boundaries_after_dst(monkeypatch):
time.tzset()


@pytest.mark.skipif(not hasattr(time, "tzset"), reason="tzset is unavailable")
@pytest.mark.parametrize("shift", [0, 2 * HOUR_MS])
def test_split_into_days_handles_dst_fall_back(monkeypatch, shift):
# 25 October 2026 lasts 25 hours in Berlin; 24 hours on never left it (#21).
previous = os.environ.get("TZ")
monkeypatch.setenv("TZ", "Europe/Berlin")
time.tzset()
try:
now = ms(2026, 10, 8, 12)
october = split_into_days(get_range(RangeLength.MONTH, now=now, start_of_day_shift=shift), shift)
year = split_into_days(get_range(RangeLength.YEAR, now=now, start_of_day_shift=shift), shift)

assert len(october) == 31
assert october[24] == day_range(date(2026, 10, 25), shift)
assert october[24].duration == 25 * HOUR_MS
assert len(year) == 365
finally:
if previous is None:
monkeypatch.delenv("TZ", raising=False)
else:
monkeypatch.setenv("TZ", previous)
time.tzset()


# --------------------------------------------------------------------------- #
# Statistics
# --------------------------------------------------------------------------- #
Expand Down
7 changes: 4 additions & 3 deletions timetracker/domain/ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from enum import Enum

from ..models import Range
from .duration import DAY_MS


class RangeLength(str, Enum):
Expand Down Expand Up @@ -143,9 +142,11 @@ def split_into_days(time_range: Range, start_of_day_shift: int = 0) -> list[Rang
return []
result: list[Range] = []
cursor = start_of_day(time_range.time_started, start_of_day_shift)
day = to_datetime(shift_timestamp(cursor, -start_of_day_shift)).date()
while cursor < time_range.time_ended:
# The next boundary is simply the start of the day 24 hours on.
next_day = start_of_day(cursor + DAY_MS, start_of_day_shift)
# Step by calendar date: a fixed 24 hours never leaves a 25-hour DST day.
day += timedelta(days=1)
next_day = day_range(day, start_of_day_shift).time_started
result.append(Range(max(cursor, time_range.time_started), min(next_day, time_range.time_ended)))
cursor = next_day
return result
Expand Down