Skip to content

Fix full-day Durations collapsing to zero on PyPy (#876)#980

Open
uttam12331 wants to merge 1 commit into
python-pendulum:masterfrom
uttam12331:fix/duration-total-seconds-pypy-876
Open

Fix full-day Durations collapsing to zero on PyPy (#876)#980
uttam12331 wants to merge 1 commit into
python-pendulum:masterfrom
uttam12331:fix/duration-total-seconds-pypy-876

Conversation

@uttam12331

Copy link
Copy Markdown

Summary

Closes #876.

On PyPy, duration(days=1) (and any whole-day / full-value duration) returned an empty Duration() with total_seconds() == 0.

Root cause

On PyPy, Duration defines its own total_seconds() (the if PYPY: branch) that computes the total from self._years, self._months, self._weeks/self._remaining_days/self._days, self._seconds and self._microseconds.

But Duration.__new__ used self.total_seconds() to derive self._total:

total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY

at a point before those _days/_seconds/… attributes are assigned (a few lines later). During construction they are still at their class defaults of 0, so on PyPy total_seconds() returns 0, _total becomes 0, and the subsequent normalization zeroes out the whole duration. On CPython the method isn't overridden, so timedelta.total_seconds() runs and the value is correct — which is why the bug is PyPy-only.

Fix

Derive the total from timedelta.total_seconds(self), which reads the already-initialized underlying timedelta value and is independent of the PyPy override. On CPython this is equivalent to the previous call, so behavior there is unchanged.

Tests

Added test_new_does_not_depend_on_total_seconds_override, which runs on CPython: it monkeypatches Duration.total_seconds to return 0.0 (mimicking the PyPy override reading not-yet-set attributes) and asserts duration(days=1)._total == 86400.0. It fails on master (_total == 0.0) and passes with this change. Full tests/duration/ suite passes (48 passed); ruff check/ruff format clean.

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 python-pendulum#876
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Durations of full days have 0 total_seconds (PyPy)

1 participant