Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0831382

Browse files
committed
Issue #15006: Allow equality comparison between naive and aware time
or datetime objects.
1 parent ea0b823 commit 0831382

5 files changed

Lines changed: 56 additions & 19 deletions

File tree

Doc/library/datetime.rst

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -901,13 +901,21 @@ Supported operations:
901901
*datetime1* is considered less than *datetime2* when *datetime1* precedes
902902
*datetime2* in time.
903903

904-
If one comparand is naive and the other is aware, :exc:`TypeError` is raised.
904+
If one comparand is naive and the other is aware, :exc:`TypeError`
905+
is raised if an order comparison is attempted. For equality
906+
comparisons, naive instances are never equal to aware instances.
907+
905908
If both comparands are aware, and have the same :attr:`tzinfo` attribute, the
906909
common :attr:`tzinfo` attribute is ignored and the base datetimes are
907910
compared. If both comparands are aware and have different :attr:`tzinfo`
908911
attributes, the comparands are first adjusted by subtracting their UTC
909912
offsets (obtained from ``self.utcoffset()``).
910913

914+
.. versionchanged:: 3.3
915+
916+
Equality comparisons between naive and aware :class:`datetime`
917+
instances don't raise :exc:`TypeError`.
918+
911919
.. note::
912920

913921
In order to stop comparison from falling back to the default scheme of comparing
@@ -1316,7 +1324,10 @@ Supported operations:
13161324

13171325
* comparison of :class:`.time` to :class:`.time`, where *a* is considered less
13181326
than *b* when *a* precedes *b* in time. If one comparand is naive and the other
1319-
is aware, :exc:`TypeError` is raised. If both comparands are aware, and have
1327+
is aware, :exc:`TypeError` is raised if an order comparison is attempted. For equality
1328+
comparisons, naive instances are never equal to aware instances.
1329+
1330+
If both comparands are aware, and have
13201331
the same :attr:`tzinfo` attribute, the common :attr:`tzinfo` attribute is
13211332
ignored and the base times are compared. If both comparands are aware and
13221333
have different :attr:`tzinfo` attributes, the comparands are first adjusted by
@@ -1326,6 +1337,11 @@ Supported operations:
13261337
different type, :exc:`TypeError` is raised unless the comparison is ``==`` or
13271338
``!=``. The latter cases return :const:`False` or :const:`True`, respectively.
13281339

1340+
.. versionchanged:: 3.3
1341+
1342+
Equality comparisons between naive and aware :class:`time` instances
1343+
don't raise :exc:`TypeError`.
1344+
13291345
* hash, use as dict key
13301346

13311347
* efficient pickling

Lib/datetime.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,13 +1065,13 @@ def tzinfo(self):
10651065

10661066
def __eq__(self, other):
10671067
if isinstance(other, time):
1068-
return self._cmp(other) == 0
1068+
return self._cmp(other, allow_mixed=True) == 0
10691069
else:
10701070
return False
10711071

10721072
def __ne__(self, other):
10731073
if isinstance(other, time):
1074-
return self._cmp(other) != 0
1074+
return self._cmp(other, allow_mixed=True) != 0
10751075
else:
10761076
return True
10771077

@@ -1099,7 +1099,7 @@ def __gt__(self, other):
10991099
else:
11001100
_cmperror(self, other)
11011101

1102-
def _cmp(self, other):
1102+
def _cmp(self, other, allow_mixed=False):
11031103
assert isinstance(other, time)
11041104
mytz = self._tzinfo
11051105
ottz = other._tzinfo
@@ -1118,7 +1118,10 @@ def _cmp(self, other):
11181118
(other._hour, other._minute, other._second,
11191119
other._microsecond))
11201120
if myoff is None or otoff is None:
1121-
raise TypeError("cannot compare naive and aware times")
1121+
if allow_mixed:
1122+
return 2 # arbitrary non-zero value
1123+
else:
1124+
raise TypeError("cannot compare naive and aware times")
11221125
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
11231126
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
11241127
return _cmp((myhhmm, self._second, self._microsecond),
@@ -1615,15 +1618,15 @@ def dst(self):
16151618

16161619
def __eq__(self, other):
16171620
if isinstance(other, datetime):
1618-
return self._cmp(other) == 0
1621+
return self._cmp(other, allow_mixed=True) == 0
16191622
elif not isinstance(other, date):
16201623
return NotImplemented
16211624
else:
16221625
return False
16231626

16241627
def __ne__(self, other):
16251628
if isinstance(other, datetime):
1626-
return self._cmp(other) != 0
1629+
return self._cmp(other, allow_mixed=True) != 0
16271630
elif not isinstance(other, date):
16281631
return NotImplemented
16291632
else:
@@ -1661,7 +1664,7 @@ def __gt__(self, other):
16611664
else:
16621665
_cmperror(self, other)
16631666

1664-
def _cmp(self, other):
1667+
def _cmp(self, other, allow_mixed=False):
16651668
assert isinstance(other, datetime)
16661669
mytz = self._tzinfo
16671670
ottz = other._tzinfo
@@ -1682,7 +1685,10 @@ def _cmp(self, other):
16821685
other._hour, other._minute, other._second,
16831686
other._microsecond))
16841687
if myoff is None or otoff is None:
1685-
raise TypeError("cannot compare naive and aware datetimes")
1688+
if allow_mixed:
1689+
return 2 # arbitrary non-zero value
1690+
else:
1691+
raise TypeError("cannot compare naive and aware datetimes")
16861692
# XXX What follows could be done more efficiently...
16871693
diff = self - other # this will take offsets into account
16881694
if diff.days < 0:

Lib/test/datetimetester.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2544,7 +2544,7 @@ def test_zones(self):
25442544
self.assertEqual(t1, t2)
25452545
self.assertEqual(t1, t3)
25462546
self.assertEqual(t2, t3)
2547-
self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2547+
self.assertNotEqual(t4, t5) # mixed tz-aware & naive
25482548
self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
25492549
self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
25502550

@@ -2696,7 +2696,7 @@ def test_mixed_compare(self):
26962696
t2 = t2.replace(tzinfo=FixedOffset(None, ""))
26972697
self.assertEqual(t1, t2)
26982698
t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2699-
self.assertRaises(TypeError, lambda: t1 == t2)
2699+
self.assertNotEqual(t1, t2)
27002700

27012701
# In time w/ identical tzinfo objects, utcoffset is ignored.
27022702
class Varies(tzinfo):
@@ -2801,16 +2801,16 @@ def test_even_more_compare(self):
28012801
microsecond=1)
28022802
self.assertTrue(t1 > t2)
28032803

2804-
# Make t2 naive and it should fail.
2804+
# Make t2 naive and it should differ.
28052805
t2 = self.theclass.min
2806-
self.assertRaises(TypeError, lambda: t1 == t2)
2806+
self.assertNotEqual(t1, t2)
28072807
self.assertEqual(t2, t2)
28082808

28092809
# It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
28102810
class Naive(tzinfo):
28112811
def utcoffset(self, dt): return None
28122812
t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2813-
self.assertRaises(TypeError, lambda: t1 == t2)
2813+
self.assertNotEqual(t1, t2)
28142814
self.assertEqual(t2, t2)
28152815

28162816
# OTOH, it's OK to compare two of these mixing the two ways of being
@@ -3327,7 +3327,7 @@ def test_mixed_compare(self):
33273327
t2 = t2.replace(tzinfo=FixedOffset(None, ""))
33283328
self.assertEqual(t1, t2)
33293329
t2 = t2.replace(tzinfo=FixedOffset(0, ""))
3330-
self.assertRaises(TypeError, lambda: t1 == t2)
3330+
self.assertNotEqual(t1, t2)
33313331

33323332
# In datetime w/ identical tzinfo objects, utcoffset is ignored.
33333333
class Varies(tzinfo):

Misc/NEWS

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ Core and Builtins
2424
Library
2525
-------
2626

27-
- Issue #14938: importlib.abc.SourceLoader.is_package() will not consider a
28-
module whose name ends in '__init__' a package (e.g. importing pkg.__init__
29-
directly should be considered a module, not a package).
27+
- Issue #15006: Allow equality comparison between naive and aware
28+
time or datetime objects.
3029

3130
- Issue #14982: Document that pkgutil's iteration functions require the
3231
non-standard iter_modules() method to be defined by an importer (something

Modules/_datetimemodule.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3707,6 +3707,14 @@ time_richcompare(PyObject *self, PyObject *other, int op)
37073707
TIME_GET_MICROSECOND(other);
37083708
result = diff_to_bool(diff, op);
37093709
}
3710+
else if (op == Py_EQ) {
3711+
result = Py_False;
3712+
Py_INCREF(result);
3713+
}
3714+
else if (op == Py_NE) {
3715+
result = Py_True;
3716+
Py_INCREF(result);
3717+
}
37103718
else {
37113719
PyErr_SetString(PyExc_TypeError,
37123720
"can't compare offset-naive and "
@@ -4584,6 +4592,14 @@ datetime_richcompare(PyObject *self, PyObject *other, int op)
45844592
Py_DECREF(delta);
45854593
result = diff_to_bool(diff, op);
45864594
}
4595+
else if (op == Py_EQ) {
4596+
result = Py_False;
4597+
Py_INCREF(result);
4598+
}
4599+
else if (op == Py_NE) {
4600+
result = Py_True;
4601+
Py_INCREF(result);
4602+
}
45874603
else {
45884604
PyErr_SetString(PyExc_TypeError,
45894605
"can't compare offset-naive and "

0 commit comments

Comments
 (0)