diff --git a/arrow/arrow.py b/arrow/arrow.py index 9d1f5e30..36e6a7c1 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -699,7 +699,20 @@ def span_range( yield r.span(frame, bounds=bounds, exact=exact) for r in _range: + day_is_clipped = False floor, ceil = r.span(frame, bounds=bounds, exact=exact) + + # check that no dates are lost (#1185) + next = ceil.shift(microseconds=+1) + if frame == "month" and next.day < start.day: + day_is_clipped = True + if day_is_clipped and not next._is_last_day_of_month(next): + days_to_shift = ( + min(start.day, calendar.monthrange(next.year, next.month)[1]) + - next.day + ) + ceil = ceil.shift(days=days_to_shift) + if ceil > end: ceil = end if bounds[1] == ")": diff --git a/tests/test_arrow.py b/tests/test_arrow.py index 5afe9baa..caaf9e51 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -1185,6 +1185,42 @@ def test_month(self): (arrow.Arrow(2013, 4, 1), arrow.Arrow(2013, 4, 30, 23, 59, 59, 999999)), ] + def test_month_exact(self): + result = list( + arrow.Arrow.span_range( + "month", datetime(2013, 1, 31), datetime(2014, 1, 31), exact=True + ) + ) + + assert result == [ + (arrow.Arrow(2013, 1, 31), arrow.Arrow(2013, 2, 27, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 2, 28), arrow.Arrow(2013, 3, 30, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 3, 31), arrow.Arrow(2013, 4, 29, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 4, 30), arrow.Arrow(2013, 5, 30, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 5, 31), arrow.Arrow(2013, 6, 29, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 6, 30), arrow.Arrow(2013, 7, 30, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 7, 31), arrow.Arrow(2013, 8, 30, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 8, 31), arrow.Arrow(2013, 9, 29, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 9, 30), arrow.Arrow(2013, 10, 30, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 10, 31), arrow.Arrow(2013, 11, 29, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 11, 30), arrow.Arrow(2013, 12, 30, 23, 59, 59, 999999)), + (arrow.Arrow(2013, 12, 31), arrow.Arrow(2014, 1, 30, 23, 59, 59, 999999)), + ] + + def test_month_exact_leap(self): + result = list( + arrow.Arrow.span_range( + "month", datetime(2012, 1, 31), datetime(2012, 5, 31), exact=True + ) + ) + + assert result == [ + (arrow.Arrow(2012, 1, 31), arrow.Arrow(2012, 2, 28, 23, 59, 59, 999999)), + (arrow.Arrow(2012, 2, 29), arrow.Arrow(2012, 3, 30, 23, 59, 59, 999999)), + (arrow.Arrow(2012, 3, 31), arrow.Arrow(2012, 4, 29, 23, 59, 59, 999999)), + (arrow.Arrow(2012, 4, 30), arrow.Arrow(2012, 5, 30, 23, 59, 59, 999999)), + ] + def test_week(self): result = list( arrow.Arrow.span_range("week", datetime(2013, 2, 2), datetime(2013, 2, 28))