Skip to content

Fix comparison across Daylight Savings Time boundary #596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
18 changes: 18 additions & 0 deletions pendulum/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,24 @@ def __reduce_ex__( # type: ignore[override]
]:
return self.__class__, self._getstate(protocol)

def __le__(self, other: datetime.date) -> bool:
if isinstance(other, DateTime):
return self._cmp(other) <= 0
return super().__le__(other.date())

def __lt__(self, other: datetime.date) -> bool:
if isinstance(other, DateTime):
return self._cmp(other) < 0
return super().__lt__(other.date())

def __ge__(self, other: datetime.date) -> bool:
# Will default to the negative of its reflection
return NotImplemented

def __gt__(self, other: datetime.date) -> bool:
# Will default to the negative of its reflection
return NotImplemented

def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int:
# Fix for pypy which compares using this method
# which would lead to infinite recursion if we didn't override
Expand Down
93 changes: 93 additions & 0 deletions tests/datetime/test_comparison.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from __future__ import annotations

from datetime import datetime
from datetime import timedelta

import pytest
import pytz

from pytz import timezone

import pendulum

from tests.conftest import assert_datetime
Expand Down Expand Up @@ -195,6 +199,95 @@ def test_less_than_with_timezone_false():
assert not d1 < d3


@pytest.mark.parametrize(
"truth_fun",
(
lambda earlier, later: earlier < later,
lambda earlier, later: earlier <= later,
lambda earlier, later: later > earlier,
lambda earlier, later: later >= earlier,
),
)
def test_comparison_crossing_dst_transitioning_off_pendulum_pendulum(truth_fun):
# We only need to test turning off DST, since that's when the time
# component goes backwards.
# We start with 2019-11-03T01:30:00-0700
earlier = pendulum.datetime(2019, 11, 3, 8, 30).in_tz("US/Pacific")
# Adding 55 minutes to it, we turn off DST, but the time component is
# slightly less than before, i.e. we get 2019-11-03T01:25:00-0800
later = earlier.add(minutes=55)
# Run through all inequality-comparison functions
assert truth_fun(earlier, later)


@pytest.mark.parametrize(
"truth_fun",
(
lambda earlier, later: earlier < later,
lambda earlier, later: earlier <= later,
lambda earlier, later: later > earlier,
lambda earlier, later: later >= earlier,
),
)
def test_comparison_crossing_dst_transitioning_off_pendulum_datetime(truth_fun):
# We only need to test turning off DST, since that's when the time
# component goes backwards.
# We start with 2019-11-03T01:30:00-0700
earlier_pendulum = pendulum.datetime(2019, 11, 3, 8, 30).in_tz("US/Pacific")
us_pacific = timezone("US/Pacific")
earlier_datetime = datetime(2019, 11, 3, 1, 30, tzinfo=us_pacific)
# Adding 55 minutes to it, we turn off DST, but the time component is
# slightly less than before, i.e. we get 2019-11-03T01:25:00-0800
later_datetime = earlier_datetime + timedelta(minutes=55)
# Run through all inequality-comparison functions
assert truth_fun(earlier_pendulum, later_datetime)


@pytest.mark.parametrize(
"truth_fun",
(
lambda earlier, later: earlier < later,
lambda earlier, later: earlier <= later,
lambda earlier, later: later > earlier,
lambda earlier, later: later >= earlier,
),
)
def test_comparison_crossing_dst_transitioning_off_datetime_pendulum(truth_fun):
# We only need to test turning off DST, since that's when the time
# component goes backwards.
# We start with 2019-11-03T01:30:00-0700
earlier_pendulum = pendulum.datetime(2019, 11, 3, 8, 30).in_tz("US/Pacific")
us_pacific = timezone("US/Pacific")
earlier_datetime = datetime(2019, 11, 3, 1, 30, tzinfo=us_pacific)
# Adding 55 minutes to it, we turn off DST, but the time component is
# slightly less than before, i.e. we get 2019-11-03T01:25:00-0800
later_pendulum = earlier_pendulum.add(minutes=55)
# Run through all inequality-comparison functions
assert truth_fun(earlier_datetime, later_pendulum)


@pytest.mark.parametrize(
"truth_fun",
(
lambda earlier, later: earlier < later,
lambda earlier, later: earlier <= later,
lambda earlier, later: later > earlier,
lambda earlier, later: later >= earlier,
),
)
def test_comparison_crossing_dst_transitioning_off_datetime_datetime(truth_fun):
# We only need to test turning off DST, since that's when the time
# component goes backwards.
# We start with 2019-11-03T01:30:00-0700
us_pacific = timezone("US/Pacific")
earlier = datetime(2019, 11, 3, 1, 30, tzinfo=us_pacific)
# Adding 55 minutes to it, we turn off DST, but the time component is
# slightly less than before, i.e. we get 2019-11-03T01:25:00-0800
later = earlier + timedelta(minutes=55)
# Run through all inequality-comparison functions
assert truth_fun(earlier, later)


def test_less_than_or_equal_true():
d1 = pendulum.datetime(2000, 1, 1)
d2 = pendulum.datetime(2000, 1, 2)
Expand Down