Skip to content

Commit 1879016

Browse files
committed
Fix full-day Durations collapsing to zero on PyPy
On PyPy, `Duration.total_seconds()` is overridden to compute the total from `_days`/`_seconds`/`_microseconds`/etc. But `Duration.__new__` called `self.total_seconds()` to derive `_total` *before* those attributes were populated (they are still at their class defaults of 0 at that point), so the computed total was 0 and the whole duration collapsed to `Duration()`. As a result `duration(days=1).total_seconds()` returned 0 on PyPy. Derive the total from `timedelta.total_seconds(self)` instead, which reads the already-initialized underlying `timedelta` value and is independent of the PyPy override. Behavior on CPython is unchanged (there the method is not overridden, so both calls are equivalent). Closes #876
1 parent cbab60c commit 1879016

2 files changed

Lines changed: 21 additions & 1 deletion

File tree

src/pendulum/duration.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,14 @@ def __new__(
9595
)
9696

9797
# Intuitive normalization
98-
total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY
98+
# Use timedelta's own total_seconds() rather than self.total_seconds():
99+
# on PyPy the latter is overridden to read _days/_seconds/... which are
100+
# not populated yet at this point, which would zero out the duration
101+
# (see issue #876).
102+
total = (
103+
timedelta.total_seconds(self)
104+
- (years * 365 + months * 30) * SECONDS_PER_DAY
105+
)
99106
self._total = total
100107

101108
m = 1

tests/duration/test_construct.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,16 @@ def test_float_years_and_months():
9797

9898
with pytest.raises(ValueError):
9999
pendulum.duration(months=1.5)
100+
101+
102+
def test_new_does_not_depend_on_total_seconds_override(monkeypatch):
103+
# __new__ must derive the duration's total from the underlying timedelta,
104+
# not from a (possibly overridden) total_seconds(). On PyPy, total_seconds()
105+
# is overridden to read _days/_seconds/... which are not populated yet during
106+
# __new__, which zeroed out full-day durations (issue #876).
107+
from pendulum.duration import Duration
108+
109+
monkeypatch.setattr(Duration, "total_seconds", lambda self: 0.0)
110+
111+
assert pendulum.duration(days=1)._total == 86400.0
112+
assert pendulum.duration(hours=23)._total == 82800.0

0 commit comments

Comments
 (0)