diff --git a/docs/docs/interval.md b/docs/docs/interval.md index fc70fb3c4..933ee51ce 100644 --- a/docs/docs/interval.md +++ b/docs/docs/interval.md @@ -151,6 +151,10 @@ You can pass an amount for the passed unit to control the length of the gap: '2000-01-09T00:00:00+00:00' ``` +`amount` must be a non-zero integer. A zero amount raises `ValueError`. +A negative amount whose sign does not match the interval direction +yields an empty iterator (the same way Python's `range()` does). + You can also directly iterate over the `Interval` instance, the unit will be `days` in this case: diff --git a/src/pendulum/interval.py b/src/pendulum/interval.py index c5e0713a7..59819137c 100644 --- a/src/pendulum/interval.py +++ b/src/pendulum/interval.py @@ -288,12 +288,23 @@ def in_words(self, locale: str | None = None, separator: str = " ") -> str: return separator.join(parts) def range(self, unit: str, amount: int = 1) -> Iterator[_T]: + if amount == 0: + raise ValueError("range() requires a non-zero amount") + method = "add" op = operator.le if not self._absolute and self.invert: method = "subtract" op = operator.ge + # A negative amount walks the opposite direction of the interval. + # Flip the step so the loop either iterates toward the end (matching + # sign) or is empty immediately (wrong sign), instead of hanging. + if amount < 0: + amount = -amount + method = "subtract" if method == "add" else "add" + op = operator.ge if op is operator.le else operator.le + start, end = self.start, self.end i = amount diff --git a/tests/interval/test_range.py b/tests/interval/test_range.py index 0b883e583..2650b183f 100644 --- a/tests/interval/test_range.py +++ b/tests/interval/test_range.py @@ -1,5 +1,7 @@ from __future__ import annotations +import pytest + import pendulum from pendulum.interval import Interval @@ -117,3 +119,23 @@ def test_range_amount(): assert_datetime(r[1], 2016, 10, 16, 1, 0, 0) assert_datetime(r[2], 2016, 10, 18, 0, 0, 0) assert_datetime(r[3], 2016, 10, 20, 0, 0, 0) + + +def test_range_zero_amount_raises(): + dt1 = pendulum.datetime(2000, 1, 1) + dt2 = pendulum.datetime(2000, 1, 10) + p = pendulum.interval(dt1, dt2) + + with pytest.raises(ValueError, match="non-zero amount"): + list(p.range("days", 0)) + + +def test_range_wrong_sign_amount_is_empty(): + dt1 = pendulum.datetime(2000, 1, 1) + dt2 = pendulum.datetime(2000, 1, 10) + + forward = pendulum.interval(dt1, dt2) + assert list(forward.range("days", -1)) == [] + + inverted = pendulum.interval(dt2, dt1) + assert list(inverted.range("days", -1)) == []