Skip to content

Commit 3a83b2c

Browse files
DEPR: disallow tznaive datetimes when indexing tzaware datetimeindex (#36148)
* BUG: allowing tznaive datetimes when indexing tzaware datetimeindex * isort fixup * deprecate wrong behavior * whatsnew Co-authored-by: Jeff Reback <[email protected]>
1 parent 257ad4e commit 3a83b2c

File tree

5 files changed

+79
-20
lines changed

5 files changed

+79
-20
lines changed

doc/source/whatsnew/v1.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ Deprecations
289289
- Deprecated :meth:`Index.is_all_dates` (:issue:`27744`)
290290
- Deprecated automatic alignment on comparison operations between :class:`DataFrame` and :class:`Series`, do ``frame, ser = frame.align(ser, axis=1, copy=False)`` before e.g. ``frame == ser`` (:issue:`28759`)
291291
- :meth:`Rolling.count` with ``min_periods=None`` will default to the size of the window in a future version (:issue:`31302`)
292+
- Deprecated slice-indexing on timezone-aware :class:`DatetimeIndex` with naive ``datetime`` objects, to match scalar indexing behavior (:issue:`36148`)
292293
- :meth:`Index.ravel` returning a ``np.ndarray`` is deprecated, in the future this will return a view on the same index (:issue:`19956`)
293294

294295
.. ---------------------------------------------------------------------------

pandas/core/indexes/datetimes.py

+25
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,28 @@ def _validate_partial_date_slice(self, reso: Resolution):
604604
# _parsed_string_to_bounds allows it.
605605
raise KeyError
606606

607+
def _deprecate_mismatched_indexing(self, key):
608+
# GH#36148
609+
# we get here with isinstance(key, self._data._recognized_scalars)
610+
try:
611+
self._data._assert_tzawareness_compat(key)
612+
except TypeError:
613+
if self.tz is None:
614+
msg = (
615+
"Indexing a timezone-naive DatetimeIndex with a "
616+
"timezone-aware datetime is deprecated and will "
617+
"raise KeyError in a future version. "
618+
"Use a timezone-naive object instead."
619+
)
620+
else:
621+
msg = (
622+
"Indexing a timezone-aware DatetimeIndex with a "
623+
"timezone-naive datetime is deprecated and will "
624+
"raise KeyError in a future version. "
625+
"Use a timezone-aware object instead."
626+
)
627+
warnings.warn(msg, FutureWarning, stacklevel=5)
628+
607629
def get_loc(self, key, method=None, tolerance=None):
608630
"""
609631
Get integer location for requested label
@@ -621,6 +643,7 @@ def get_loc(self, key, method=None, tolerance=None):
621643

622644
if isinstance(key, self._data._recognized_scalars):
623645
# needed to localize naive datetimes
646+
self._deprecate_mismatched_indexing(key)
624647
key = self._maybe_cast_for_get_loc(key)
625648

626649
elif isinstance(key, str):
@@ -702,6 +725,8 @@ def _maybe_cast_slice_bound(self, label, side: str, kind):
702725
if self._is_strictly_monotonic_decreasing and len(self) > 1:
703726
return upper if side == "left" else lower
704727
return lower if side == "left" else upper
728+
elif isinstance(label, (self._data._recognized_scalars, date)):
729+
self._deprecate_mismatched_indexing(label)
705730
return self._maybe_cast_for_get_loc(label)
706731

707732
def _get_string_slice(self, key: str, use_lhs: bool = True, use_rhs: bool = True):

pandas/tests/indexes/datetimes/test_indexing.py

+24-12
Original file line numberDiff line numberDiff line change
@@ -675,10 +675,14 @@ def test_get_slice_bounds_datetime_within(
675675
self, box, kind, side, expected, tz_aware_fixture
676676
):
677677
# GH 35690
678-
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz_aware_fixture)
679-
result = index.get_slice_bound(
680-
box(year=2000, month=1, day=7), kind=kind, side=side
681-
)
678+
tz = tz_aware_fixture
679+
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz)
680+
key = box(year=2000, month=1, day=7)
681+
682+
warn = None if tz is None else FutureWarning
683+
with tm.assert_produces_warning(warn, check_stacklevel=False):
684+
# GH#36148 will require tzawareness-compat
685+
result = index.get_slice_bound(key, kind=kind, side=side)
682686
assert result == expected
683687

684688
@pytest.mark.parametrize("box", [date, datetime, Timestamp])
@@ -689,19 +693,27 @@ def test_get_slice_bounds_datetime_outside(
689693
self, box, kind, side, year, expected, tz_aware_fixture
690694
):
691695
# GH 35690
692-
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz_aware_fixture)
693-
result = index.get_slice_bound(
694-
box(year=year, month=1, day=7), kind=kind, side=side
695-
)
696+
tz = tz_aware_fixture
697+
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz)
698+
key = box(year=year, month=1, day=7)
699+
700+
warn = None if tz is None else FutureWarning
701+
with tm.assert_produces_warning(warn, check_stacklevel=False):
702+
# GH#36148 will require tzawareness-compat
703+
result = index.get_slice_bound(key, kind=kind, side=side)
696704
assert result == expected
697705

698706
@pytest.mark.parametrize("box", [date, datetime, Timestamp])
699707
@pytest.mark.parametrize("kind", ["getitem", "loc", None])
700708
def test_slice_datetime_locs(self, box, kind, tz_aware_fixture):
701709
# GH 34077
702-
index = DatetimeIndex(["2010-01-01", "2010-01-03"]).tz_localize(
703-
tz_aware_fixture
704-
)
705-
result = index.slice_locs(box(2010, 1, 1), box(2010, 1, 2))
710+
tz = tz_aware_fixture
711+
index = DatetimeIndex(["2010-01-01", "2010-01-03"]).tz_localize(tz)
712+
key = box(2010, 1, 1)
713+
714+
warn = None if tz is None else FutureWarning
715+
with tm.assert_produces_warning(warn, check_stacklevel=False):
716+
# GH#36148 will require tzawareness-compat
717+
result = index.slice_locs(key, box(2010, 1, 2))
706718
expected = (0, 1)
707719
assert result == expected

pandas/tests/series/indexing/test_datetime.py

+22-7
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,38 @@ def test_getitem_setitem_datetimeindex():
237237
expected = ts[4:8]
238238
tm.assert_series_equal(result, expected)
239239

240-
# repeat all the above with naive datetimes
241-
result = ts[datetime(1990, 1, 1, 4)]
240+
# But we do not give datetimes a pass on tzawareness compat
241+
# TODO: do the same with Timestamps and dt64
242+
msg = "Cannot compare tz-naive and tz-aware datetime-like objects"
243+
naive = datetime(1990, 1, 1, 4)
244+
with tm.assert_produces_warning(FutureWarning):
245+
# GH#36148 will require tzawareness compat
246+
result = ts[naive]
242247
expected = ts[4]
243248
assert result == expected
244249

245250
result = ts.copy()
246-
result[datetime(1990, 1, 1, 4)] = 0
247-
result[datetime(1990, 1, 1, 4)] = ts[4]
251+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
252+
# GH#36148 will require tzawareness compat
253+
result[datetime(1990, 1, 1, 4)] = 0
254+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
255+
# GH#36148 will require tzawareness compat
256+
result[datetime(1990, 1, 1, 4)] = ts[4]
248257
tm.assert_series_equal(result, ts)
249258

250-
result = ts[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)]
259+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
260+
# GH#36148 will require tzawareness compat
261+
result = ts[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)]
251262
expected = ts[4:8]
252263
tm.assert_series_equal(result, expected)
253264

254265
result = ts.copy()
255-
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = 0
256-
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = ts[4:8]
266+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
267+
# GH#36148 will require tzawareness compat
268+
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = 0
269+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
270+
# GH#36148 will require tzawareness compat
271+
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = ts[4:8]
257272
tm.assert_series_equal(result, ts)
258273

259274
lb = datetime(1990, 1, 1, 4)

pandas/tests/series/methods/test_truncate.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,13 @@ def test_truncate_datetimeindex_tz(self):
101101
# GH 9243
102102
idx = date_range("4/1/2005", "4/30/2005", freq="D", tz="US/Pacific")
103103
s = Series(range(len(idx)), index=idx)
104-
result = s.truncate(datetime(2005, 4, 2), datetime(2005, 4, 4))
104+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
105+
# GH#36148 in the future will require tzawareness compat
106+
s.truncate(datetime(2005, 4, 2), datetime(2005, 4, 4))
107+
108+
lb = idx[1]
109+
ub = idx[3]
110+
result = s.truncate(lb.to_pydatetime(), ub.to_pydatetime())
105111
expected = Series([1, 2, 3], index=idx[1:4])
106112
tm.assert_series_equal(result, expected)
107113

0 commit comments

Comments
 (0)