diff --git a/src/pendulum/date.py b/src/pendulum/date.py index cd703d2e..a225a2c6 100644 --- a/src/pendulum/date.py +++ b/src/pendulum/date.py @@ -260,6 +260,9 @@ def __add__(self, other: timedelta) -> Self: return self._add_timedelta(other) + def __radd__(self, other: timedelta) -> Self: + return self.__add__(other) + @overload # type: ignore[override] # this is only needed because of Python 3.7 def __sub__(self, __delta: timedelta) -> Self: ... diff --git a/tests/date/test_add.py b/tests/date/test_add.py index a435f465..d0bfb8b6 100644 --- a/tests/date/test_add.py +++ b/tests/date/test_add.py @@ -78,6 +78,53 @@ def test_add_duration(): assert_date(new, 2017, 7, 2) +@pytest.mark.parametrize( + "date, duration, expected", + [ + ((2024, 1, 31), {"months": 1}, (2024, 2, 29)), + ((2023, 1, 31), {"months": 1}, (2023, 2, 28)), + ((2024, 3, 31), {"months": -1}, (2024, 2, 29)), + ((2023, 3, 31), {"months": -1}, (2023, 2, 28)), + ((2023, 3, 1), {"years": 1}, (2024, 3, 1)), + ((2024, 2, 29), {"years": -1}, (2023, 2, 28)), + ((2023, 1, 31), {"years": 2, "months": 3, "days": 18}, (2025, 5, 18)), + ], +) +def test_add_duration_reflected(date, duration, expected): + d = pendulum.Date(*date) + delta = pendulum.duration(**duration) + + result = delta + d + + assert isinstance(result, pendulum.Date) + assert_date(result, *expected) + assert result == d + delta + + +@pytest.mark.parametrize( + "delta", + [timedelta(days=18), timedelta(seconds=-1), timedelta(), timedelta(seconds=1)], +) +def test_add_timedelta_reflected(delta): + d = pendulum.Date(2024, 1, 31) + + result = delta + d + + assert isinstance(result, pendulum.Date) + assert result == d + delta + + +def test_add_duration_reflected_preserves_date_subclass(): + class CustomDate(pendulum.Date): + pass + + d = CustomDate(2024, 1, 31) + result = pendulum.duration(months=1) + d + + assert isinstance(result, CustomDate) + assert_date(result, 2024, 2, 29) + + def test_addition_invalid_type(): d = pendulum.date(2015, 3, 14)