diff --git a/docs/docs/addition_subtraction.md b/docs/docs/addition_subtraction.md index 32cb875d0..c532c3f35 100644 --- a/docs/docs/addition_subtraction.md +++ b/docs/docs/addition_subtraction.md @@ -85,3 +85,16 @@ Each method returns a new `DateTime` instance. Passing negative values to `add()` is also possible and will act exactly like `subtract()` + +## Time arithmetic + +`Time` instances support adding a `datetime.timedelta` on either side of `+`. +As with `Time.add_timedelta()`, the duration must not have a nonzero `days` component. + +```python +>>> from datetime import timedelta +>>> pendulum.time(12, 30) + timedelta(minutes=15) +Time(12, 45, 0) +>>> timedelta(minutes=15) + pendulum.time(12, 30) +Time(12, 45, 0) +``` diff --git a/src/pendulum/time.py b/src/pendulum/time.py index 58ca7e819..4bafad17d 100644 --- a/src/pendulum/time.py +++ b/src/pendulum/time.py @@ -167,6 +167,9 @@ def __add__(self, other: datetime.timedelta) -> Time: return self.add_timedelta(other) + def __radd__(self, other: datetime.timedelta) -> Time: + return self.__add__(other) + @overload def __sub__(self, other: time) -> pendulum.Duration: ... diff --git a/tests/time/test_add.py b/tests/time/test_add.py index 7075ebe22..9b8c4459f 100644 --- a/tests/time/test_add.py +++ b/tests/time/test_add.py @@ -68,6 +68,56 @@ def test_add_timedelta_with_days(): d.add_timedelta(delta) +@pytest.mark.parametrize("duration_type", [timedelta, pendulum.duration]) +@pytest.mark.parametrize( + "time_args, duration_args, expected_args", + [ + ( + (3, 12, 15, 654321), + {"seconds": 45, "microseconds": 123456}, + (3, 13, 0, 777777), + ), + ((12, 0), {}, (12, 0)), + ((23, 59, 59, 999999), {"microseconds": 1}, (0, 0)), + ((12, 0), {"hours": 23}, (11, 0)), + ], +) +def test_reflected_addition(duration_type, time_args, duration_args, expected_args): + value = pendulum.time(*time_args) + delta = duration_type(**duration_args) + expected = pendulum.time(*expected_args) + + assert value + delta == expected + result = delta + value + assert result == expected + assert isinstance(result, pendulum.Time) + + +@pytest.mark.parametrize("days", [-1, 1]) +def test_reflected_addition_rejects_days(days): + value = pendulum.time(12, 0) + delta = timedelta(days=days) + + with pytest.raises(TypeError): + value + delta + with pytest.raises(TypeError): + delta + value + + +def test_reflected_addition_delegates_to_subclass(): + calls = [] + expected = pendulum.time(6, 0) + + class CustomTime(pendulum.Time): + def __add__(self, other): + calls.append(other) + return expected + + delta = timedelta(minutes=5) + assert delta + CustomTime(12, 0) is expected + assert calls == [delta] + + def test_addition_invalid_type(): d = pendulum.time(3, 12, 15, 654321)