Issue #15006: Allow equality comparison between naive and aware time

or datetime objects.
This commit is contained in:
Alexander Belopolsky 2012-06-15 20:19:47 -04:00
parent ea0b823940
commit 0831382d69
5 changed files with 56 additions and 19 deletions

View file

@ -901,13 +901,21 @@ Supported operations:
*datetime1* is considered less than *datetime2* when *datetime1* precedes
*datetime2* in time.
If one comparand is naive and the other is aware, :exc:`TypeError` is raised.
If one comparand is naive and the other is aware, :exc:`TypeError`
is raised if an order comparison is attempted. For equality
comparisons, naive instances are never equal to aware instances.
If both comparands are aware, and have the same :attr:`tzinfo` attribute, the
common :attr:`tzinfo` attribute is ignored and the base datetimes are
compared. If both comparands are aware and have different :attr:`tzinfo`
attributes, the comparands are first adjusted by subtracting their UTC
offsets (obtained from ``self.utcoffset()``).
.. versionchanged:: 3.3
Equality comparisons between naive and aware :class:`datetime`
instances don't raise :exc:`TypeError`.
.. note::
In order to stop comparison from falling back to the default scheme of comparing
@ -1316,7 +1324,10 @@ Supported operations:
* comparison of :class:`.time` to :class:`.time`, where *a* is considered less
than *b* when *a* precedes *b* in time. If one comparand is naive and the other
is aware, :exc:`TypeError` is raised. If both comparands are aware, and have
is aware, :exc:`TypeError` is raised if an order comparison is attempted. For equality
comparisons, naive instances are never equal to aware instances.
If both comparands are aware, and have
the same :attr:`tzinfo` attribute, the common :attr:`tzinfo` attribute is
ignored and the base times are compared. If both comparands are aware and
have different :attr:`tzinfo` attributes, the comparands are first adjusted by
@ -1326,6 +1337,11 @@ Supported operations:
different type, :exc:`TypeError` is raised unless the comparison is ``==`` or
``!=``. The latter cases return :const:`False` or :const:`True`, respectively.
.. versionchanged:: 3.3
Equality comparisons between naive and aware :class:`time` instances
don't raise :exc:`TypeError`.
* hash, use as dict key
* efficient pickling

View file

@ -1065,13 +1065,13 @@ def tzinfo(self):
def __eq__(self, other):
if isinstance(other, time):
return self._cmp(other) == 0
return self._cmp(other, allow_mixed=True) == 0
else:
return False
def __ne__(self, other):
if isinstance(other, time):
return self._cmp(other) != 0
return self._cmp(other, allow_mixed=True) != 0
else:
return True
@ -1099,7 +1099,7 @@ def __gt__(self, other):
else:
_cmperror(self, other)
def _cmp(self, other):
def _cmp(self, other, allow_mixed=False):
assert isinstance(other, time)
mytz = self._tzinfo
ottz = other._tzinfo
@ -1118,7 +1118,10 @@ def _cmp(self, other):
(other._hour, other._minute, other._second,
other._microsecond))
if myoff is None or otoff is None:
raise TypeError("cannot compare naive and aware times")
if allow_mixed:
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware times")
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
return _cmp((myhhmm, self._second, self._microsecond),
@ -1615,7 +1618,7 @@ def dst(self):
def __eq__(self, other):
if isinstance(other, datetime):
return self._cmp(other) == 0
return self._cmp(other, allow_mixed=True) == 0
elif not isinstance(other, date):
return NotImplemented
else:
@ -1623,7 +1626,7 @@ def __eq__(self, other):
def __ne__(self, other):
if isinstance(other, datetime):
return self._cmp(other) != 0
return self._cmp(other, allow_mixed=True) != 0
elif not isinstance(other, date):
return NotImplemented
else:
@ -1661,7 +1664,7 @@ def __gt__(self, other):
else:
_cmperror(self, other)
def _cmp(self, other):
def _cmp(self, other, allow_mixed=False):
assert isinstance(other, datetime)
mytz = self._tzinfo
ottz = other._tzinfo
@ -1682,7 +1685,10 @@ def _cmp(self, other):
other._hour, other._minute, other._second,
other._microsecond))
if myoff is None or otoff is None:
raise TypeError("cannot compare naive and aware datetimes")
if allow_mixed:
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware datetimes")
# XXX What follows could be done more efficiently...
diff = self - other # this will take offsets into account
if diff.days < 0:

View file

@ -2544,7 +2544,7 @@ def test_zones(self):
self.assertEqual(t1, t2)
self.assertEqual(t1, t3)
self.assertEqual(t2, t3)
self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
self.assertNotEqual(t4, t5) # mixed tz-aware & naive
self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
@ -2696,7 +2696,7 @@ def test_mixed_compare(self):
t2 = t2.replace(tzinfo=FixedOffset(None, ""))
self.assertEqual(t1, t2)
t2 = t2.replace(tzinfo=FixedOffset(0, ""))
self.assertRaises(TypeError, lambda: t1 == t2)
self.assertNotEqual(t1, t2)
# In time w/ identical tzinfo objects, utcoffset is ignored.
class Varies(tzinfo):
@ -2801,16 +2801,16 @@ def test_even_more_compare(self):
microsecond=1)
self.assertTrue(t1 > t2)
# Make t2 naive and it should fail.
# Make t2 naive and it should differ.
t2 = self.theclass.min
self.assertRaises(TypeError, lambda: t1 == t2)
self.assertNotEqual(t1, t2)
self.assertEqual(t2, t2)
# It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
class Naive(tzinfo):
def utcoffset(self, dt): return None
t2 = self.theclass(5, 6, 7, tzinfo=Naive())
self.assertRaises(TypeError, lambda: t1 == t2)
self.assertNotEqual(t1, t2)
self.assertEqual(t2, t2)
# OTOH, it's OK to compare two of these mixing the two ways of being
@ -3327,7 +3327,7 @@ def test_mixed_compare(self):
t2 = t2.replace(tzinfo=FixedOffset(None, ""))
self.assertEqual(t1, t2)
t2 = t2.replace(tzinfo=FixedOffset(0, ""))
self.assertRaises(TypeError, lambda: t1 == t2)
self.assertNotEqual(t1, t2)
# In datetime w/ identical tzinfo objects, utcoffset is ignored.
class Varies(tzinfo):

View file

@ -24,9 +24,8 @@ Core and Builtins
Library
-------
- Issue #14938: importlib.abc.SourceLoader.is_package() will not consider a
module whose name ends in '__init__' a package (e.g. importing pkg.__init__
directly should be considered a module, not a package).
- Issue #15006: Allow equality comparison between naive and aware
time or datetime objects.
- Issue #14982: Document that pkgutil's iteration functions require the
non-standard iter_modules() method to be defined by an importer (something

View file

@ -3707,6 +3707,14 @@ time_richcompare(PyObject *self, PyObject *other, int op)
TIME_GET_MICROSECOND(other);
result = diff_to_bool(diff, op);
}
else if (op == Py_EQ) {
result = Py_False;
Py_INCREF(result);
}
else if (op == Py_NE) {
result = Py_True;
Py_INCREF(result);
}
else {
PyErr_SetString(PyExc_TypeError,
"can't compare offset-naive and "
@ -4584,6 +4592,14 @@ datetime_richcompare(PyObject *self, PyObject *other, int op)
Py_DECREF(delta);
result = diff_to_bool(diff, op);
}
else if (op == Py_EQ) {
result = Py_False;
Py_INCREF(result);
}
else if (op == Py_NE) {
result = Py_True;
Py_INCREF(result);
}
else {
PyErr_SetString(PyExc_TypeError,
"can't compare offset-naive and "