Skip to content

Keep naive datetimes naive in DateTime.diff() (fixes #880)#968

Open
mokashang wants to merge 1 commit into
python-pendulum:masterfrom
mokashang:fix/naive-diff-stdlib-datetime
Open

Keep naive datetimes naive in DateTime.diff() (fixes #880)#968
mokashang wants to merge 1 commit into
python-pendulum:masterfrom
mokashang:fix/naive-diff-stdlib-datetime

Conversation

@mokashang
Copy link
Copy Markdown

Pull Request Check List

  • Added tests for changed code.
  • Updated documentation for changed code.

Summary

DateTime.diff() raises a TypeError when a naive DateTime is diffed against a naive stdlib datetime:

from datetime import datetime
import pendulum

pendulum.today().naive().diff(datetime.today())
# TypeError: can't compare offset-naive and offset-aware datetimes

Root cause

diff() builds an Interval(self, dt). Interval.__new__ validates that both endpoints have the same awareness (both naive or both aware) and otherwise raises. Interval.__init__, however, re-normalises the endpoints through pendulum.instance() using the default tz=UTC. For a naive stdlib datetime that turns it into a UTC-aware DateTime, so a naive receiver and the (now aware) argument end up mixed, and the subsequent start > end / precise_diff comparison raises.

Fix

Pass each endpoint's own tzinfo to instance() instead of letting it default to UTC:

pendulum.instance(start, tz=start.tzinfo)
pendulum.instance(end, tz=end.tzinfo)

DateTime.instance resolves the timezone as tz = dt.tzinfo or tz, so:

  • naive inputs (tzinfo is None) stay naive — fixing the bug, and
  • aware inputs keep their existing timezone — identical to the previous behaviour.

Because __new__ already guarantees both endpoints share the same awareness, forcing a timezone in __init__ was unnecessary and was the sole source of the mismatch. The mixed naive/aware case still raises in __new__ exactly as before.

Testing

  • Added test_diff_naive_with_naive_stdlib_datetime covering the reported case (both abs=True and abs=False).
  • Full test suite passes locally (1834 passed, 3 skipped).
  • ruff check and ruff format --check pass on the changed files.

Fixes #880

`DateTime.diff()` constructs an `Interval`, whose `__init__` normalised
the start and end values via `pendulum.instance()`. That call used the
default `tz=UTC`, so a naive stdlib `datetime` was turned into a
UTC-aware `DateTime`. When the receiver was itself naive, the subsequent
comparison mixed an offset-naive value with an offset-aware one and
raised:

    TypeError: can't compare offset-naive and offset-aware datetimes

`Interval.__new__` already validates that both endpoints share the same
awareness, so forcing a timezone here was both unnecessary and the
source of the bug. Pass each value's own `tzinfo` to `instance()`
instead: naive inputs stay naive and aware inputs keep their timezone
(`DateTime.instance` resolves `tz` as `dt.tzinfo or tz`), so behaviour
for aware datetimes is unchanged.

Fixes python-pendulum#880
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.

naive DateTime.diff fails when the input is a naive datetime object, as pendulum.instance converts it to a pendulum.DateTime with UTC tz

1 participant