Skip to content

Commit

Permalink
DEPR: disallow tznaive datetimes when indexing tzaware datetimeindex (p…
Browse files Browse the repository at this point in the history
…andas-dev#36148)

* BUG: allowing tznaive datetimes when indexing tzaware datetimeindex

* isort fixup

* deprecate wrong behavior

* whatsnew

Co-authored-by: Jeff Reback <jeff@reback.net>
  • Loading branch information
2 people authored and Kevin D Smith committed Nov 2, 2020
1 parent 3077c3a commit cd27abb
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 20 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ Deprecations
- Deprecated :meth:`Index.is_all_dates` (:issue:`27744`)
- 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`)
- :meth:`Rolling.count` with ``min_periods=None`` will default to the size of the window in a future version (:issue:`31302`)
- Deprecated slice-indexing on timezone-aware :class:`DatetimeIndex` with naive ``datetime`` objects, to match scalar indexing behavior (:issue:`36148`)
- :meth:`Index.ravel` returning a ``np.ndarray`` is deprecated, in the future this will return a view on the same index (:issue:`19956`)

.. ---------------------------------------------------------------------------
Expand Down
25 changes: 25 additions & 0 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,28 @@ def _validate_partial_date_slice(self, reso: Resolution):
# _parsed_string_to_bounds allows it.
raise KeyError

def _deprecate_mismatched_indexing(self, key):
# GH#36148
# we get here with isinstance(key, self._data._recognized_scalars)
try:
self._data._assert_tzawareness_compat(key)
except TypeError:
if self.tz is None:
msg = (
"Indexing a timezone-naive DatetimeIndex with a "
"timezone-aware datetime is deprecated and will "
"raise KeyError in a future version. "
"Use a timezone-naive object instead."
)
else:
msg = (
"Indexing a timezone-aware DatetimeIndex with a "
"timezone-naive datetime is deprecated and will "
"raise KeyError in a future version. "
"Use a timezone-aware object instead."
)
warnings.warn(msg, FutureWarning, stacklevel=5)

def get_loc(self, key, method=None, tolerance=None):
"""
Get integer location for requested label
Expand All @@ -621,6 +643,7 @@ def get_loc(self, key, method=None, tolerance=None):

if isinstance(key, self._data._recognized_scalars):
# needed to localize naive datetimes
self._deprecate_mismatched_indexing(key)
key = self._maybe_cast_for_get_loc(key)

elif isinstance(key, str):
Expand Down Expand Up @@ -702,6 +725,8 @@ def _maybe_cast_slice_bound(self, label, side: str, kind):
if self._is_strictly_monotonic_decreasing and len(self) > 1:
return upper if side == "left" else lower
return lower if side == "left" else upper
elif isinstance(label, (self._data._recognized_scalars, date)):
self._deprecate_mismatched_indexing(label)
return self._maybe_cast_for_get_loc(label)

def _get_string_slice(self, key: str, use_lhs: bool = True, use_rhs: bool = True):
Expand Down
36 changes: 24 additions & 12 deletions pandas/tests/indexes/datetimes/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,10 +675,14 @@ def test_get_slice_bounds_datetime_within(
self, box, kind, side, expected, tz_aware_fixture
):
# GH 35690
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz_aware_fixture)
result = index.get_slice_bound(
box(year=2000, month=1, day=7), kind=kind, side=side
)
tz = tz_aware_fixture
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz)
key = box(year=2000, month=1, day=7)

warn = None if tz is None else FutureWarning
with tm.assert_produces_warning(warn, check_stacklevel=False):
# GH#36148 will require tzawareness-compat
result = index.get_slice_bound(key, kind=kind, side=side)
assert result == expected

@pytest.mark.parametrize("box", [date, datetime, Timestamp])
Expand All @@ -689,19 +693,27 @@ def test_get_slice_bounds_datetime_outside(
self, box, kind, side, year, expected, tz_aware_fixture
):
# GH 35690
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz_aware_fixture)
result = index.get_slice_bound(
box(year=year, month=1, day=7), kind=kind, side=side
)
tz = tz_aware_fixture
index = bdate_range("2000-01-03", "2000-02-11").tz_localize(tz)
key = box(year=year, month=1, day=7)

warn = None if tz is None else FutureWarning
with tm.assert_produces_warning(warn, check_stacklevel=False):
# GH#36148 will require tzawareness-compat
result = index.get_slice_bound(key, kind=kind, side=side)
assert result == expected

@pytest.mark.parametrize("box", [date, datetime, Timestamp])
@pytest.mark.parametrize("kind", ["getitem", "loc", None])
def test_slice_datetime_locs(self, box, kind, tz_aware_fixture):
# GH 34077
index = DatetimeIndex(["2010-01-01", "2010-01-03"]).tz_localize(
tz_aware_fixture
)
result = index.slice_locs(box(2010, 1, 1), box(2010, 1, 2))
tz = tz_aware_fixture
index = DatetimeIndex(["2010-01-01", "2010-01-03"]).tz_localize(tz)
key = box(2010, 1, 1)

warn = None if tz is None else FutureWarning
with tm.assert_produces_warning(warn, check_stacklevel=False):
# GH#36148 will require tzawareness-compat
result = index.slice_locs(key, box(2010, 1, 2))
expected = (0, 1)
assert result == expected
29 changes: 22 additions & 7 deletions pandas/tests/series/indexing/test_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,23 +237,38 @@ def test_getitem_setitem_datetimeindex():
expected = ts[4:8]
tm.assert_series_equal(result, expected)

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

result = ts.copy()
result[datetime(1990, 1, 1, 4)] = 0
result[datetime(1990, 1, 1, 4)] = ts[4]
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# GH#36148 will require tzawareness compat
result[datetime(1990, 1, 1, 4)] = 0
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# GH#36148 will require tzawareness compat
result[datetime(1990, 1, 1, 4)] = ts[4]
tm.assert_series_equal(result, ts)

result = ts[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)]
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# GH#36148 will require tzawareness compat
result = ts[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)]
expected = ts[4:8]
tm.assert_series_equal(result, expected)

result = ts.copy()
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = 0
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = ts[4:8]
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# GH#36148 will require tzawareness compat
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = 0
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# GH#36148 will require tzawareness compat
result[datetime(1990, 1, 1, 4) : datetime(1990, 1, 1, 7)] = ts[4:8]
tm.assert_series_equal(result, ts)

lb = datetime(1990, 1, 1, 4)
Expand Down
8 changes: 7 additions & 1 deletion pandas/tests/series/methods/test_truncate.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@ def test_truncate_datetimeindex_tz(self):
# GH 9243
idx = date_range("4/1/2005", "4/30/2005", freq="D", tz="US/Pacific")
s = Series(range(len(idx)), index=idx)
result = s.truncate(datetime(2005, 4, 2), datetime(2005, 4, 4))
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
# GH#36148 in the future will require tzawareness compat
s.truncate(datetime(2005, 4, 2), datetime(2005, 4, 4))

lb = idx[1]
ub = idx[3]
result = s.truncate(lb.to_pydatetime(), ub.to_pydatetime())
expected = Series([1, 2, 3], index=idx[1:4])
tm.assert_series_equal(result, expected)

Expand Down

0 comments on commit cd27abb

Please sign in to comment.