8000 [3.7] bpo-37685: Fixed comparisons of datetime.timedelta and datetime.timezone. (GH-14996) by serhiy-storchaka · Pull Request #15104 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

[3.7] bpo-37685: Fixed comparisons of datetime.timedelta and datetime.timezone. (GH-14996) #15104

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

Merged
merged 1 commit into from
Aug 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,28 @@ The :mod:`test.support` module defines the following constants:

Check for presence of docstrings.


.. data:: TEST_HTTP_URL

Define the URL of a dedicated HTTP server for the network tests.


.. data:: ALWAYS_EQ

Object that is equal to anything. Used to test mixed type comparison.


.. data:: LARGEST

Object that is greater than anything (except itself).
Used to test mixed type comparison.


.. data:: SMALLEST

Object that is less than anything (except itself).
Used to test mixed type comparison.


The :mod:`test.support` module defines the following functions:

Expand Down
22 changes: 11 additions & 11 deletions Lib/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,25 +724,25 @@ def __le__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) <= 0
else:
_cmperror(self, other)
return NotImplemented

def __lt__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) < 0
else:
_cmperror(self, other)
return NotImplemented

def __ge__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) >= 0
else:
_cmperror(self, other)
return NotImplemented

def __gt__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) > 0
else:
_cmperror(self, other)
return NotImplemented

def _cmp(self, other):
assert isinstance(other, timedelta)
Expand Down Expand Up @@ -1267,25 +1267,25 @@ def __le__(self, other):
if isinstance(other, time):
return self._cmp(other) <= 0
else:
_cmperror(self, other)
return NotImplemented

def __lt__(self, other):
if isinstance(other, time):
return self._cmp(other) < 0
else:
_cmperror(self, other)
return NotImplemented

def __ge__(self, other):
10000 if isinstance(other, time):
return self._cmp(other) >= 0
else:
_cmperror(self, other)
return NotImplemented

def __gt__(self, other):
if isinstance(other, time):
return self._cmp(other) > 0
else:
_cmperror(self, other)
return NotImplemented

def _cmp(self, other, allow_mixed=False):
assert isinstance(other, time)
Expand Down Expand Up @@ -2167,9 +2167,9 @@ def __getinitargs__(self):
return (self._offset, self._name)

def __eq__(self, other):
if type(other) != timezone:
return False
return self._offset == other._offset
if isinstance(other, timezone):
return self._offset == other._offset
return NotImplemented

def __hash__(self):
return hash(self._offset)
Expand Down
74 changes: 30 additions & 44 deletions Lib/test/datetimetester.py
1E0A
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
"""
from test.support import is_resource_enabled

import itertools
import bisect

import copy
import decimal
import sys
Expand All @@ -22,6 +19,7 @@
from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod

from test import support
from test.support import is_resource_enabled, ALWAYS_EQ, LARGEST, SMALLEST

import datetime as datetime_module
from datetime import MINYEAR, MAXYEAR
Expand Down Expand Up @@ -54,18 +52,6 @@
NAN = float("nan")


class ComparesEqualClass(object):
"""
A class that is always equal to whatever you compare it to.
"""

def __eq__(self, other):
return True

def __ne__(self, other):
return False


#############################################################################
# module tests

Expand Down Expand Up @@ -353,6 +339,18 @@ def test_comparison(self):
self.assertTrue(timezone(ZERO) != None)
self.assertFalse(timezone(ZERO) == None)

tz = timezone(ZERO)
self.assertTrue(tz == ALWAYS_EQ)
self.assertFalse(tz != ALWAYS_EQ)
self.assertTrue(tz < LARGEST)
self.assertFalse(tz > LARGEST)
self.assertTrue(tz <= LARGEST)
self.assertFalse(tz >= LARGEST)
self.assertFalse(tz < SMALLEST)
self.assertTrue(tz > SMALLEST)
self.assertFalse(tz <= SMALLEST)
self.assertTrue(tz >= SMALLEST)

def test_aware_datetime(self):
# test that timezone instances can be used by datetime
t = datetime(1, 1, 1)
Expand Down Expand Up @@ -414,10 +412,21 @@ def test_harmless_mixed_comparison(self):

# Comparison to objects of unsupported types should return
# NotImplemented which falls back to the right hand side's __eq__
# method. In this case, ComparesEqualClass.__eq__ always returns True.
# ComparesEqualClass.__ne__ always returns False.
self.assertTrue(me == ComparesEqualClass())
self.assertFalse(me != ComparesEqualClass())
# method. In this case, ALWAYS_EQ.__eq__ always returns True.
# ALWAYS_EQ.__ne__ always returns False.
self.assertTrue(me == ALWAYS_EQ)
self.assertFalse(me != ALWAYS_EQ)

# If the other class explicitly defines ordering
# relative to our class, it is allowed to do so
self.assertTrue(me < LARGEST)
self.assertFalse(me > LARGEST)
self.assertTrue(me <= LARGEST)
self.assertFalse(me >= LARGEST)
self.assertFalse(me < SMALLEST)
self.assertTrue(me > SMALLEST)
self.assertFalse(me <= SMALLEST)
self.assertTrue(me >= SMALLEST)

def test_harmful_mixed_comparison(self):
me = self.theclass(1, 1, 1)
Expand Down Expand Up @@ -1544,29 +1553,6 @@ class SomeClass:
self.assertRaises(TypeError, lambda: our < their)
self.assertRaises(TypeError, lambda: their < our)

# However, if the other class explicitly defines ordering
# relative to our class, it is allowed to do so

class LargerThanAnything:
def __lt__(self, other):
return False
def __le__(self, other):
return isinstance(other, LargerThanAnything)
def __eq__(self, other):
return isinstance(other, LargerThanAnything)
def __gt__(self, other):
return not isinstance(other, LargerThanAnything)
def __ge__(self, other):
return True

their = LargerThanAnything()
self.assertEqual(our == their, False)
self.assertEqual(their == our, False)
self.assertEqual(our != their, True)
self.assertEqual(their != our, True)
self.assertEqual(our < their, True)
self.assertEqual(their < our, False)

def test_bool(self):
# All dates are considered true.
self.assertTrue(self.theclass.min)
Expand Down Expand Up @@ -3642,8 +3628,8 @@ def test_replace(self):
self.assertRaises(ValueError, base.replace, microsecond=1000000)

def test_mixed_compare(self):
t1 = time(1, 2, 3)
t2 = time(1, 2, 3)
t1 = self.theclass(1, 2, 3)
t2 = self.theclass(1, 2, 3)
self.assertEqual(t1, t2)
t2 = t2.replace(tzinfo=None)
self.assertEqual(t1, t2)
Expand Down
37 changes: 37 additions & 0 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"run_with_locale", "swap_item",
"swap_attr", "Matcher", "set_memlimit", "SuppressCrashReport", "sortdict",
"run_with_tz", "PGO", "missing_compiler_executable", "fd_count",
"ALWAYS_EQ", "LARGEST", "SMALLEST"
]

class Error(Exception):
Expand Down Expand Up @@ -2919,3 +2920,39 @@ def __fspath__(self):
raise self.path
else:
return self.path


class _ALWAYS_EQ:
"""
Object that is equal to anything.
"""
def __eq__(self, other):
return True
def __ne__(self, other):
return False

ALWAYS_EQ = _ALWAYS_EQ()

@functools.total_ordering
class _LARGEST:
"""
Object that is greater than anything (except itself).
"""
def __eq__(self, other):
return isinstance(other, _LARGEST)
def __lt__(self, other):
return False

LARGEST = _LARGEST()

@functools.total_ordering
class _SMALLEST:
"""
Object that is less than anything (except itself).
"""
def __eq__(self, other):
return isinstance(other, _SMALLEST)
def __gt__(self, other):
return False

SMALLEST = _SMALLEST()
33 changes: 9 additions & 24 deletions Lib/test/test_ipaddress.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pickle
import ipaddress
import weakref
from test.support import LARGEST, SMALLEST


class BaseTestCase(unittest.TestCase):
Expand Down Expand Up @@ -679,20 +680,6 @@ def test_ip_network(self):
self.assertFactoryError(ipaddress.ip_network, "network")


@functools.total_ordering
class LargestObject:
def __eq__(self, other):
return isinstance(other, LargestObject)
def __lt__(self, other):
return False

@functools.total_ordering
class SmallestObject:
def __eq__(self, other):
return isinstance(other, SmallestObject)
def __gt__(self, other):
return False

class ComparisonTests(unittest.TestCase):

v4addr = ipaddress.IPv4Address(1)
Expand Down Expand Up @@ -781,8 +768,6 @@ def test_mixed_type_ordering(self):

def test_foreign_type_ordering(self):
other = object()
smallest = SmallestObject()
largest = LargestObject()
for obj in self.objects:
with self.assertRaises(TypeError):
obj < other
Expand All @@ -792,14 +777,14 @@ def test_foreign_type_ordering(self):
obj <= other
with self.assertRaises(TypeError):
obj >= other
self.assertTrue(obj < largest)
self.assertFalse(obj > largest)
self.assertTrue(obj <= largest)
self.assertFalse(obj >= largest)
self.assertFalse(obj < smallest)
self.assertTrue(obj > smallest)
self.assertFalse(obj <= smallest)
self.assertTrue(obj >= smallest)
self.assertTrue(obj < LARGEST)
self.assertFalse(obj > LARGEST)
self.assertTrue(obj <= LARGEST)
self.assertFalse(obj >= LARGEST)
self.assertFalse(obj < SMALLEST)
self.assertTrue(obj > SMALLEST)
self.assertFalse(obj <= SMALLEST)
self.assertTrue(obj >= SMALLEST)

def test_mixed_type_key(self):
# with get_mixed_type_key, you can sort addresses and network.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed comparisons of :class:`datetime.timedelta` and
:class:`datetime.timezone`.
7 changes: 2 additions & 5 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3644,11 +3644,8 @@ timezone_richcompare(PyDateTime_TimeZone *self,
{
if (op != Py_EQ && op != Py_NE)
Py_RETURN_NOTIMPLEMENTED;
if (Py_TYPE(other) != &PyDateTime_TimeZoneType) {
if (op == Py_EQ)
Py_RETURN_FALSE;
else
Py_RETURN_TRUE;
if (!PyTZInfo_Check(other)) {
Py_RETURN_NOTIMPLEMENTED;
}
return delta_richcompare(self->offset, other->offset, op);
}
Expand Down
0