Skip to content

Commit 25be42d

Browse files
committed
Fix Duration multiplication by a float dropping years and months
Duration.__mul__ with a float built the result only from _to_microseconds(), which excludes the years and months components, so Duration(years=1) * 2.0 returned an empty Duration even though Duration(years=1) * 2 (int) and Duration(years=1) / 2.0 (float) both keep them. Scale years and months by the float ratio as well, mirroring the existing __truediv__ implementation.
1 parent 1d9e31a commit 25be42d

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

src/pendulum/duration.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,13 @@ def __mul__(self, other: int | float) -> Self:
368368
usec = self._to_microseconds()
369369
a, b = other.as_integer_ratio()
370370

371-
return self.__class__(0, 0, _divide_and_round(usec * a, b))
371+
return self.__class__(
372+
0,
373+
0,
374+
_divide_and_round(usec * a, b),
375+
years=_divide_and_round(self._years * a, b),
376+
months=_divide_and_round(self._months * a, b),
377+
)
372378

373379
return NotImplemented
374380

tests/duration/test_arithmetic.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,40 @@ def test_multiply():
2727
assert_duration(mul, 4, 6, 9, 5, 0, 1, 9, 44444)
2828

2929

30+
def test_multiply_float():
31+
# A float factor must scale years and months just like an int factor (and
32+
# like float division already does). Regression: Duration.__mul__ with a
33+
# float dropped the years/months components entirely.
34+
it = pendulum.duration(
35+
years=2, months=3, weeks=4, days=6, seconds=34, microseconds=522222
36+
)
37+
mul = it * 2.0
38+
39+
assert isinstance(mul, pendulum.Duration)
40+
assert_duration(mul, 4, 6, 9, 5, 0, 1, 9, 44444)
41+
42+
mul = 2.0 * it
43+
44+
assert isinstance(mul, pendulum.Duration)
45+
assert_duration(mul, 4, 6, 9, 5, 0, 1, 9, 44444)
46+
47+
# A whole-valued float must match the integer result exactly.
48+
it = pendulum.duration(years=1, months=6, days=2, seconds=35, microseconds=522222)
49+
by_int = it * 3
50+
by_float = it * 3.0
51+
52+
assert (by_float.years, by_float.months) == (by_int.years, by_int.months)
53+
assert by_float.total_seconds() == by_int.total_seconds()
54+
55+
# A non-integer factor rounds years/months, mirroring float division.
56+
it = pendulum.duration(years=2, months=4, days=2, seconds=35, microseconds=522222)
57+
mul = it * 1.5
58+
59+
assert isinstance(mul, pendulum.Duration)
60+
assert mul.years == 3
61+
assert mul.months == 6
62+
63+
3064
def test_divide():
3165
it = pendulum.duration(days=2, seconds=34, microseconds=522222)
3266
mul = it / 2

0 commit comments

Comments
 (0)