Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,7 @@ Indexing
- Bug in :meth:`Index.get_indexer` and similar methods when ``NaN`` is located at or after position 128 (:issue:`58924`)
- Bug in :meth:`MultiIndex.insert` when a new value inserted to a datetime-like level gets cast to ``NaT`` and fails indexing (:issue:`60388`)
- Bug in :meth:`Series.__setitem__` when assigning boolean series with boolean indexer will raise ``LossySetitemError`` (:issue:`57338`)
- Bug in indexing ``obj.loc[start:stop]`` with a :class:`DatetimeIndex` and :class:`Timestamp` endpoints with higher resolution than the index (:issue:`63262`)
- Bug in printing :attr:`Index.names` and :attr:`MultiIndex.levels` would not escape single quotes (:issue:`60190`)
- Bug in reindexing of :class:`DataFrame` with :class:`PeriodDtype` columns in case of consolidated block (:issue:`60980`, :issue:`60273`)
- Bug in :meth:`DataFrame.loc.__getitem__` and :meth:`DataFrame.iloc.__getitem__` with a :class:`CategoricalDtype` column with integer categories raising when trying to index a row containing a ``NaN`` entry (:issue:`58954`)
Expand Down
21 changes: 20 additions & 1 deletion pandas/core/indexes/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import (
TYPE_CHECKING,
Any,
Literal,
Self,
cast,
final,
Expand All @@ -20,16 +21,18 @@

from pandas._libs import (
NaT,
Timedelta,
lib,
)
from pandas._libs.tslibs import (
BaseOffset,
Resolution,
Tick,
Timedelta,
Timestamp,
parsing,
to_offset,
)
from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
from pandas.compat.numpy import function as nv
from pandas.errors import (
InvalidIndexError,
Expand Down Expand Up @@ -933,6 +936,22 @@ def _from_join_target(self, result: np.ndarray):
result = result.view(self._data._ndarray.dtype)
return self._data._from_backing_data(result)

def _searchsorted_monotonic(self, label, side: Literal["left", "right"] = "left"):
if (
self.is_monotonic_increasing
and isinstance(label, (Timestamp, Timedelta))
and abbrev_to_npy_unit(label.unit) > abbrev_to_npy_unit(self.unit)
):
# For non-matching units we can safely round down (with side=right)
# This is needed for GH#63262
if side == "right":
label = label.as_unit(self.unit) # this should always be a round-down
else:
# round up
label = label.ceil(self.unit).as_unit(self.unit)

return super()._searchsorted_monotonic(label, side)

# --------------------------------------------------------------------
# List-like Methods

Expand Down
26 changes: 26 additions & 0 deletions pandas/tests/indexes/datetimes/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,32 @@ def test_maybe_cast_slice_duplicate_monotonic(self):


class TestGetSliceBounds:
@pytest.mark.parametrize("as_td", [True, False])
def test_get_slice_bound_mismatched_unit(self, as_td):
# GH#63262
index = date_range(start="2000-01-01", freq="h", periods=8)

td = pd.Timedelta(1)
ts = Timestamp("2000-01-01 01:00:00")
start = ts - td
stop = ts + td
if as_td:
index = index - Timestamp(0).as_unit("us")
start = start - Timestamp(0).as_unit("us")
stop = stop - Timestamp(0).as_unit("us")

left = index.get_slice_bound(start, side="left")
assert left == 1
right = index.get_slice_bound(stop, side="right")
assert right == 2

# The user-facing behavior is slicing with .loc, so let's test that
# explicitly while we're here.
ser = pd.Series(1, index=index)
result = ser.loc[start:stop]
expected = ser.iloc[1:2]
tm.assert_series_equal(result, expected)

@pytest.mark.parametrize("box", [date, datetime, Timestamp])
@pytest.mark.parametrize("side, expected", [("left", 4), ("right", 5)])
def test_get_slice_bounds_datetime_within(
Expand Down
Loading