Skip to content

Commit 857a9b1

Browse files
authored
REF: _validate_partial_date_slice -> _can_partial_date_slice (#42225)
1 parent d61ace5 commit 857a9b1

File tree

3 files changed

+51
-34
lines changed

3 files changed

+51
-34
lines changed

pandas/core/indexes/datetimelike.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ def _summary(self, name=None) -> str:
282282
# --------------------------------------------------------------------
283283
# Indexing Methods
284284

285-
def _validate_partial_date_slice(self, reso: Resolution):
285+
def _can_partial_date_slice(self, reso: Resolution) -> bool:
286286
raise NotImplementedError
287287

288288
def _parsed_string_to_bounds(self, reso: Resolution, parsed):
@@ -317,7 +317,8 @@ def _partial_date_slice(
317317
-------
318318
slice or ndarray[intp]
319319
"""
320-
self._validate_partial_date_slice(reso)
320+
if not self._can_partial_date_slice(reso):
321+
raise ValueError
321322

322323
t1, t2 = self._parsed_string_to_bounds(reso, parsed)
323324
vals = self._data._ndarray

pandas/core/indexes/datetimes.py

+15-6
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
603603
end = end.tz_localize(self.tz)
604604
return start, end
605605

606-
def _validate_partial_date_slice(self, reso: Resolution):
606+
def _can_partial_date_slice(self, reso: Resolution) -> bool:
607607
assert isinstance(reso, Resolution), (type(reso), reso)
608608
if (
609609
self.is_monotonic
@@ -614,12 +614,13 @@ def _validate_partial_date_slice(self, reso: Resolution):
614614
# GH3452 and GH2369.
615615

616616
# See also GH14826
617-
raise KeyError
617+
return False
618618

619619
if reso.attrname == "microsecond":
620620
# _partial_date_slice doesn't allow microsecond resolution, but
621621
# _parsed_string_to_bounds allows it.
622-
raise KeyError
622+
return False
623+
return True
623624

624625
def _deprecate_mismatched_indexing(self, key) -> None:
625626
# GH#36148
@@ -663,14 +664,22 @@ def get_loc(self, key, method=None, tolerance=None):
663664
key = self._maybe_cast_for_get_loc(key)
664665

665666
elif isinstance(key, str):
667+
666668
try:
667-
return self._get_string_slice(key)
668-
except (TypeError, KeyError, ValueError, OverflowError):
669-
pass
669+
parsed, reso = self._parse_with_reso(key)
670+
except ValueError as err:
671+
raise KeyError(key) from err
670672

673+
if self._can_partial_date_slice(reso):
674+
try:
675+
return self._partial_date_slice(reso, parsed)
676+
except KeyError as err:
677+
if method is None:
678+
raise KeyError(key) from err
671679
try:
672680
key = self._maybe_cast_for_get_loc(key)
673681
except ValueError as err:
682+
# FIXME: we get here because parse_with_reso doesn't raise on "t2m"
674683
raise KeyError(key) from err
675684

676685
elif isinstance(key, timedelta):

pandas/core/indexes/period.py

+33-26
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,11 @@
1515
)
1616
from pandas._libs.tslibs import (
1717
BaseOffset,
18+
NaT,
1819
Period,
1920
Resolution,
2021
Tick,
2122
)
22-
from pandas._libs.tslibs.parsing import (
23-
DateParseError,
24-
parse_time_string,
25-
)
2623
from pandas._typing import (
2724
Dtype,
2825
DtypeObj,
@@ -35,6 +32,7 @@
3532
pandas_dtype,
3633
)
3734
from pandas.core.dtypes.dtypes import PeriodDtype
35+
from pandas.core.dtypes.missing import is_valid_na_for_dtype
3836

3937
from pandas.core.arrays.period import (
4038
PeriodArray,
@@ -409,43 +407,50 @@ def get_loc(self, key, method=None, tolerance=None):
409407
orig_key = key
410408

411409
self._check_indexing_error(key)
412-
if isinstance(key, str):
413410

414-
try:
415-
loc = self._get_string_slice(key)
416-
return loc
417-
except (TypeError, ValueError):
418-
pass
411+
if is_valid_na_for_dtype(key, self.dtype):
412+
key = NaT
413+
414+
elif isinstance(key, str):
419415

420416
try:
421-
asdt, reso_str = parse_time_string(key, self.freq)
422-
except (ValueError, DateParseError) as err:
417+
parsed, reso = self._parse_with_reso(key)
418+
except ValueError as err:
423419
# A string with invalid format
424420
raise KeyError(f"Cannot interpret '{key}' as period") from err
425421

426-
reso = Resolution.from_attrname(reso_str)
422+
if self._can_partial_date_slice(reso):
423+
try:
424+
return self._partial_date_slice(reso, parsed)
425+
except KeyError as err:
426+
# TODO: pass if method is not None, like DTI does?
427+
raise KeyError(key) from err
427428

428429
if reso == self.dtype.resolution:
429430
# the reso < self.dtype.resolution case goes through _get_string_slice
430-
key = Period(asdt, freq=self.freq)
431+
key = Period(parsed, freq=self.freq)
431432
loc = self.get_loc(key, method=method, tolerance=tolerance)
433+
# Recursing instead of falling through matters for the exception
434+
# message in test_get_loc3 (though not clear if that really matters)
432435
return loc
433436
elif method is None:
434437
raise KeyError(key)
435438
else:
436-
key = asdt
439+
key = Period(parsed, freq=self.freq)
437440

438-
elif is_integer(key):
439-
# Period constructor will cast to string, which we dont want
440-
raise KeyError(key)
441441
elif isinstance(key, Period) and key.freq != self.freq:
442442
raise KeyError(key)
443-
444-
try:
445-
key = Period(key, freq=self.freq)
446-
except ValueError as err:
447-
# we cannot construct the Period
448-
raise KeyError(orig_key) from err
443+
elif isinstance(key, Period):
444+
pass
445+
elif isinstance(key, datetime):
446+
try:
447+
key = Period(key, freq=self.freq)
448+
except ValueError as err:
449+
# we cannot construct the Period
450+
raise KeyError(orig_key) from err
451+
else:
452+
# in particular integer, which Period constructor would cast to string
453+
raise KeyError(key)
449454

450455
try:
451456
return Index.get_loc(self, key, method, tolerance)
@@ -496,7 +501,7 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
496501
iv = Period(parsed, freq=grp.value)
497502
return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end"))
498503

499-
def _validate_partial_date_slice(self, reso: Resolution):
504+
def _can_partial_date_slice(self, reso: Resolution) -> bool:
500505
assert isinstance(reso, Resolution), (type(reso), reso)
501506
grp = reso.freq_group
502507
freqn = self.dtype.freq_group_code
@@ -505,7 +510,9 @@ def _validate_partial_date_slice(self, reso: Resolution):
505510
# TODO: we used to also check for
506511
# reso in ["day", "hour", "minute", "second"]
507512
# why is that check not needed?
508-
raise ValueError
513+
return False
514+
515+
return True
509516

510517

511518
def period_range(

0 commit comments

Comments
 (0)