From 568ad64a03db7c25bb39069011c0f0d02240b1a2 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 20 Jun 2021 11:36:43 -0700 Subject: [PATCH 1/2] REF: share _get_string_slice --- pandas/_libs/tslibs/parsing.pyx | 3 +-- pandas/core/indexes/datetimelike.py | 14 ++++++++++++++ pandas/core/indexes/datetimes.py | 10 +--------- pandas/core/indexes/period.py | 11 +---------- pandas/core/indexes/timedeltas.py | 20 ++++++++++++++++---- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/pandas/_libs/tslibs/parsing.pyx b/pandas/_libs/tslibs/parsing.pyx index 9892671f5c18c..212e40b30848a 100644 --- a/pandas/_libs/tslibs/parsing.pyx +++ b/pandas/_libs/tslibs/parsing.pyx @@ -470,8 +470,7 @@ cdef inline object _parse_dateabbr_string(object date_string, datetime default, except ValueError: pass - if date_len == 6 and (freq == 'M' or - getattr(freq, 'rule_code', None) == 'M'): + if date_len == 6 and freq == 'M': year = int(date_string[:4]) month = int(date_string[4:6]) try: diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 53ab2e45edede..d75440518c6cd 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -25,6 +25,7 @@ NaTType, Resolution, Tick, + parsing, ) from pandas._typing import ( Callable, @@ -403,6 +404,19 @@ def _validate_partial_date_slice(self, reso: Resolution): def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): raise NotImplementedError + def _parse_with_reso(self, label: str): + # overridden by TimedeltaIndex + parsed, reso_str = parsing.parse_time_string(label, self.freq) + reso = Resolution.from_attrname(reso_str) + return parsed, reso + + def _get_string_slice(self, key: str): + parsed, reso = self._parse_with_reso(key) + try: + return self._partial_date_slice(reso, parsed) + except KeyError as err: + raise KeyError(key) from err + @final def _partial_date_slice( self, diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index d9eaaa763fde8..a86c15a5a95c4 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -732,13 +732,11 @@ def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound") if isinstance(label, str): - freq = getattr(self, "freqstr", getattr(self, "inferred_freq", None)) try: - parsed, reso_str = parsing.parse_time_string(label, freq) + parsed, reso = self._parse_with_reso(label) except parsing.DateParseError as err: raise self._invalid_indexer("slice", label) from err - reso = Resolution.from_attrname(reso_str) lower, upper = self._parsed_string_to_bounds(reso, parsed) # lower, upper form the half-open interval: # [parsed, parsed + 1 freq) @@ -756,12 +754,6 @@ def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): return self._maybe_cast_for_get_loc(label) - def _get_string_slice(self, key: str): - freq = getattr(self, "freqstr", getattr(self, "inferred_freq", None)) - parsed, reso_str = parsing.parse_time_string(key, freq) - reso = Resolution.from_attrname(reso_str) - return self._partial_date_slice(reso, parsed) - def slice_indexer(self, start=None, end=None, step=None, kind=None): """ Return indexer for specified label slice. diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 731958e363b40..361b3651e5f90 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -487,12 +487,11 @@ def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): return Period(label, freq=self.freq) elif isinstance(label, str): try: - parsed, reso_str = parse_time_string(label, self.freq) + parsed, reso = self._parse_with_reso(label) except ValueError as err: # string cannot be parsed as datetime-like raise self._invalid_indexer("slice", label) from err - reso = Resolution.from_attrname(reso_str) lower, upper = self._parsed_string_to_bounds(reso, parsed) return lower if side == "left" else upper elif not isinstance(label, self._data._recognized_scalars): @@ -516,14 +515,6 @@ def _validate_partial_date_slice(self, reso: Resolution): # why is that check not needed? raise ValueError - def _get_string_slice(self, key: str): - parsed, reso_str = parse_time_string(key, self.freq) - reso = Resolution.from_attrname(reso_str) - try: - return self._partial_date_slice(reso, parsed) - except KeyError as err: - raise KeyError(key) from err - def period_range( start=None, end=None, periods: int | None = None, freq=None, name=None diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 6599d2d44eee5..b61554d38babf 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -108,6 +108,9 @@ class TimedeltaIndex(DatetimeTimedeltaMixin): _data: TimedeltaArray + # Use base class method instead of DatetimeTimedeltaMixin._get_string_slice + _get_string_slice = Index._get_string_slice + # ------------------------------------------------------------------- # Constructors @@ -197,20 +200,29 @@ def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default): if isinstance(label, str): try: - parsed = Timedelta(label) + parsed, reso = self._parse_with_reso(label) except ValueError as err: # e.g. 'unit abbreviation w/o a number' raise self._invalid_indexer("slice", label) from err - # The next two lines are analogous to DTI/PI._parsed_str_to_bounds - lower = parsed.round(parsed.resolution_string) - upper = lower + to_offset(parsed.resolution_string) - Timedelta(1, "ns") + lower, upper = self._parsed_string_to_bounds(reso, parsed) return lower if side == "left" else upper elif not isinstance(label, self._data._recognized_scalars): raise self._invalid_indexer("slice", label) return label + def _parse_with_reso(self, label: str): + # the "with_reso" is a no-op for TimedeltaIndex + parsed = Timedelta(label) + return parsed, None + + def _parsed_string_to_bounds(self, reso, parsed: Timedelta): + # reso is unused, included to match signature of DTI/PI + lbound = parsed.round(parsed.resolution_string) + rbound = lbound + to_offset(parsed.resolution_string) - Timedelta(1, "ns") + return lbound, rbound + # ------------------------------------------------------------------- @property From 4d3562b558eff62cb3f35e71b15fb564e749ad83 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 20 Jun 2021 16:10:48 -0700 Subject: [PATCH 2/2] mypy fixup --- pandas/core/indexes/datetimelike.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index d75440518c6cd..25d4e870239fd 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -401,7 +401,7 @@ def _summary(self, name=None) -> str: def _validate_partial_date_slice(self, reso: Resolution): raise NotImplementedError - def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): + def _parsed_string_to_bounds(self, reso: Resolution, parsed): raise NotImplementedError def _parse_with_reso(self, label: str):