From 47b0fa80a87101c0f5dd8ae93addc08ea35d5e86 Mon Sep 17 00:00:00 2001 From: mokashang Date: Sat, 23 May 2026 09:16:53 -0700 Subject: [PATCH] fix: keep naive datetimes naive when building an Interval `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 #880 --- src/pendulum/interval.py | 4 ++-- tests/datetime/test_diff.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pendulum/interval.py b/src/pendulum/interval.py index c5e0713a..e3430e05 100644 --- a/src/pendulum/interval.py +++ b/src/pendulum/interval.py @@ -122,7 +122,7 @@ def __init__(self, start: _T, end: _T, absolute: bool = False) -> None: _start: _T if not isinstance(start, pendulum.Date): if isinstance(start, datetime): - start = cast("_T", pendulum.instance(start)) + start = cast("_T", pendulum.instance(start, tz=start.tzinfo)) else: start = cast("_T", pendulum.date(start.year, start.month, start.day)) @@ -148,7 +148,7 @@ def __init__(self, start: _T, end: _T, absolute: bool = False) -> None: _end: _T if not isinstance(end, pendulum.Date): if isinstance(end, datetime): - end = cast("_T", pendulum.instance(end)) + end = cast("_T", pendulum.instance(end, tz=end.tzinfo)) else: end = cast("_T", pendulum.date(end.year, end.month, end.day)) diff --git a/tests/datetime/test_diff.py b/tests/datetime/test_diff.py index 7a315070..0eb7db5f 100644 --- a/tests/datetime/test_diff.py +++ b/tests/datetime/test_diff.py @@ -198,6 +198,18 @@ def test_diff_in_seconds_with_timezones(): assert dt_ottawa.diff(dt_vancouver).in_seconds() == 3 * 60 * 60 +def test_diff_naive_with_naive_stdlib_datetime(): + # A naive DateTime diffed against a naive stdlib datetime should not + # force the latter to UTC, which used to make it offset-aware and raise + # "can't compare offset-naive and offset-aware datetimes". See #880. + dt = pendulum.naive(2016, 7, 5, 12, 0, 0) + other = datetime(2016, 7, 5, 13, 0, 0) + + assert dt.diff(other).in_seconds() == 3600 + assert dt.diff(other, False).in_seconds() == 3600 + assert other.tzinfo is None + + def test_diff_for_humans_now_and_second(): with pendulum.travel_to(pendulum.datetime(2012, 1, 1, 1, 2, 3), freeze=True): assert pendulum.now().diff_for_humans() == "a few seconds ago"