Skip to content
Open
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
9 changes: 9 additions & 0 deletions arrow/arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,16 @@ def span_range(
for r in _range:
yield r.span(frame, bounds=bounds, exact=exact)

prev_ceil = None
for r in _range:
floor, ceil = r.span(frame, bounds=bounds, exact=exact)
if prev_ceil is not None:
# Ensure consecutive spans are contiguous by using the
# previous span's ceiling (+1 microsecond) as the floor,
# preventing gaps caused by month-length day clipping.
expected_floor = prev_ceil.shift(microseconds=+1)
if floor > expected_floor:
floor = expected_floor
if ceil > end:
ceil = end
if bounds[1] == ")":
Expand All @@ -719,6 +727,7 @@ def span_range(
break
elif floor + relativedelta(microseconds=-1) == end:
break
prev_ceil = ceil
yield floor, ceil

@classmethod
Expand Down
21 changes: 21 additions & 0 deletions tests/test_arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,27 @@ def test_small_interval_exact_open_bounds(self):

assert result == expected

def test_exact_month_no_gaps_when_day_clipped(self):
"""Regression test for #1185: span_range with frame='month' and
exact=True must produce contiguous spans even when the start day
exceeds the number of days in a shorter month (day clipping)."""
result = list(
arrow.Arrow.span_range(
"month",
datetime(2023, 1, 31, tzinfo=ZoneInfo("Europe/Berlin")),
datetime(2023, 4, 1, tzinfo=ZoneInfo("Europe/Berlin")),
exact=True,
)
)

# Spans must be contiguous: each floor == previous ceil + 1 µs
for i in range(1, len(result)):
prev_ceil = result[i - 1][1]
curr_floor = result[i][0]
assert curr_floor == prev_ceil.shift(
microseconds=+1
), f"Gap between span {i - 1} and {i}"


class TestArrowInterval:
def test_incorrect_input(self):
Expand Down
Loading