Skip to content

Commit d79203a

Browse files
TomAugspurgerjorisvandenbossche
authored andcommitted
Revert change to comparison op with datetime.date objects (#21361)
1 parent ff26632 commit d79203a

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

doc/source/whatsnew/v0.23.1.txt

+41
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,47 @@ and bug fixes. We recommend that all users upgrade to this version.
1515
Fixed Regressions
1616
~~~~~~~~~~~~~~~~~
1717

18+
**Comparing Series with datetime.date**
19+
20+
We've reverted a 0.23.0 change to comparing a :class:`Series` holding datetimes and a ``datetime.date`` object (:issue:`21152`).
21+
In pandas 0.22 and earlier, comparing a Series holding datetimes and ``datetime.date`` objects would coerce the ``datetime.date`` to a datetime before comapring.
22+
This was inconsistent with Python, NumPy, and :class:`DatetimeIndex`, which never consider a datetime and ``datetime.date`` equal.
23+
24+
In 0.23.0, we unified operations between DatetimeIndex and Series, and in the process changed comparisons between a Series of datetimes and ``datetime.date`` without warning.
25+
26+
We've temporarily restored the 0.22.0 behavior, so datetimes and dates may again compare equal, but restore the 0.23.0 behavior in a future release.
27+
28+
To summarize, here's the behavior in 0.22.0, 0.23.0, 0.23.1:
29+
30+
.. code-block:: python
31+
32+
# 0.22.0... Silently coerce the datetime.date
33+
>>> Series(pd.date_range('2017', periods=2)) == datetime.date(2017, 1, 1)
34+
0 True
35+
1 False
36+
dtype: bool
37+
38+
# 0.23.0... Do not coerce the datetime.date
39+
>>> Series(pd.date_range('2017', periods=2)) == datetime.date(2017, 1, 1)
40+
0 False
41+
1 False
42+
dtype: bool
43+
44+
# 0.23.1... Coerce the datetime.date with a warning
45+
>>> Series(pd.date_range('2017', periods=2)) == datetime.date(2017, 1, 1)
46+
/bin/python:1: FutureWarning: Comparing Series of datetimes with 'datetime.date'. Currently, the
47+
'datetime.date' is coerced to a datetime. In the future pandas will
48+
not coerce, and the values not compare equal to the 'datetime.date'.
49+
To retain the current behavior, convert the 'datetime.date' to a
50+
datetime with 'pd.Timestamp'.
51+
#!/bin/python3
52+
0 True
53+
1 False
54+
dtype: bool
55+
56+
In addition, ordering comparisons will raise a ``TypeError`` in the future.
57+
58+
**Other Fixes**
1859

1960
- Reverted the ability of :func:`~DataFrame.to_sql` to perform multivalue
2061
inserts as this caused regression in certain cases (:issue:`21103`).

pandas/core/ops.py

+30
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"""
66
# necessary to enforce truediv in Python 2.X
77
from __future__ import division
8+
import datetime
89
import operator
10+
import textwrap
11+
import warnings
912

1013
import numpy as np
1114
import pandas as pd
@@ -1197,8 +1200,35 @@ def wrapper(self, other, axis=None):
11971200
if is_datetime64_dtype(self) or is_datetime64tz_dtype(self):
11981201
# Dispatch to DatetimeIndex to ensure identical
11991202
# Series/Index behavior
1203+
if (isinstance(other, datetime.date) and
1204+
not isinstance(other, datetime.datetime)):
1205+
# https://github.com/pandas-dev/pandas/issues/21152
1206+
# Compatibility for difference between Series comparison w/
1207+
# datetime and date
1208+
msg = (
1209+
"Comparing Series of datetimes with 'datetime.date'. "
1210+
"Currently, the 'datetime.date' is coerced to a "
1211+
"datetime. In the future pandas will not coerce, "
1212+
"and {future}. "
1213+
"To retain the current behavior, "
1214+
"convert the 'datetime.date' to a datetime with "
1215+
"'pd.Timestamp'."
1216+
)
1217+
1218+
if op in {operator.lt, operator.le, operator.gt, operator.ge}:
1219+
future = "a TypeError will be raised"
1220+
else:
1221+
future = (
1222+
"'the values will not compare equal to the "
1223+
"'datetime.date'"
1224+
)
1225+
msg = '\n'.join(textwrap.wrap(msg.format(future=future)))
1226+
warnings.warn(msg, FutureWarning, stacklevel=2)
1227+
other = pd.Timestamp(other)
1228+
12001229
res_values = dispatch_to_index_op(op, self, other,
12011230
pd.DatetimeIndex)
1231+
12021232
return self._constructor(res_values, index=self.index,
12031233
name=res_name)
12041234

pandas/tests/series/test_arithmetic.py

+40
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,46 @@ def test_ser_cmp_result_names(self, names, op):
8888

8989

9090
class TestTimestampSeriesComparison(object):
91+
def test_dt64_ser_cmp_date_warning(self):
92+
# https://github.com/pandas-dev/pandas/issues/21359
93+
# Remove this test and enble invalid test below
94+
ser = pd.Series(pd.date_range('20010101', periods=10), name='dates')
95+
date = ser.iloc[0].to_pydatetime().date()
96+
97+
with tm.assert_produces_warning(FutureWarning) as m:
98+
result = ser == date
99+
expected = pd.Series([True] + [False] * 9, name='dates')
100+
tm.assert_series_equal(result, expected)
101+
assert "Comparing Series of datetimes " in str(m[0].message)
102+
assert "will not compare equal" in str(m[0].message)
103+
104+
with tm.assert_produces_warning(FutureWarning) as m:
105+
result = ser != date
106+
tm.assert_series_equal(result, ~expected)
107+
assert "will not compare equal" in str(m[0].message)
108+
109+
with tm.assert_produces_warning(FutureWarning) as m:
110+
result = ser <= date
111+
tm.assert_series_equal(result, expected)
112+
assert "a TypeError will be raised" in str(m[0].message)
113+
114+
with tm.assert_produces_warning(FutureWarning) as m:
115+
result = ser < date
116+
tm.assert_series_equal(result, pd.Series([False] * 10, name='dates'))
117+
assert "a TypeError will be raised" in str(m[0].message)
118+
119+
with tm.assert_produces_warning(FutureWarning) as m:
120+
result = ser >= date
121+
tm.assert_series_equal(result, pd.Series([True] * 10, name='dates'))
122+
assert "a TypeError will be raised" in str(m[0].message)
123+
124+
with tm.assert_produces_warning(FutureWarning) as m:
125+
result = ser > date
126+
tm.assert_series_equal(result, pd.Series([False] + [True] * 9,
127+
name='dates'))
128+
assert "a TypeError will be raised" in str(m[0].message)
129+
130+
@pytest.mark.skip(reason="GH-21359")
91131
def test_dt64ser_cmp_date_invalid(self):
92132
# GH#19800 datetime.date comparison raises to
93133
# match DatetimeIndex/Timestamp. This also matches the behavior

0 commit comments

Comments
 (0)