diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index d32d664aae22b..be20d825b0c80 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -70,7 +70,6 @@ from pandas.errors import ( AbstractMethodError, InvalidComparison, - NullFrequencyError, PerformanceWarning, ) from pandas.util._decorators import ( @@ -1360,44 +1359,6 @@ def _addsub_object_array(self, other: np.ndarray, op): result = result.reshape(self.shape) return result - def _time_shift( - self: DatetimeLikeArrayT, periods: int, freq=None - ) -> DatetimeLikeArrayT: - """ - Shift each value by `periods`. - - Note this is different from ExtensionArray.shift, which - shifts the *position* of each element, padding the end with - missing values. - - Parameters - ---------- - periods : int - Number of periods to shift by. - freq : pandas.DateOffset, pandas.Timedelta, or str - Frequency increment to shift by. - """ - if freq is not None and freq != self.freq: - if isinstance(freq, str): - freq = to_offset(freq) - offset = periods * freq - return self + offset - - if periods == 0 or len(self) == 0: - # GH#14811 empty case - return self.copy() - - if self.freq is None: - raise NullFrequencyError("Cannot shift with no freq") - - start = self[0] + periods * self.freq - end = self[-1] + periods * self.freq - - # Note: in the DatetimeTZ case, _generate_range will infer the - # appropriate timezone from `start` and `end`, so tz does not need - # to be passed explicitly. - return self._generate_range(start=start, end=end, periods=None, freq=self.freq) - @unpack_zerodim_and_defer("__add__") def __add__(self, other): other_dtype = getattr(other, "dtype", None) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 548aacda2f8b9..41ca630e86c10 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -538,28 +538,6 @@ def to_timestamp(self, freq=None, how: str = "start") -> DatetimeArray: # -------------------------------------------------------------------- - def _time_shift(self, periods: int, freq=None) -> PeriodArray: - """ - Shift each value by `periods`. - - Note this is different from ExtensionArray.shift, which - shifts the *position* of each element, padding the end with - missing values. - - Parameters - ---------- - periods : int - Number of periods to shift by. - freq : pandas.DateOffset, pandas.Timedelta, or str - Frequency increment to shift by. - """ - if freq is not None: - raise TypeError( - "`freq` argument is not supported for " - f"{type(self).__name__}._time_shift" - ) - return self + periods - def _box_func(self, x) -> Period | NaTType: return Period._from_ordinal(ordinal=x, freq=self.freq) @@ -726,8 +704,7 @@ def _addsub_int_array_or_scalar( self, other: np.ndarray | int, op: Callable[[Any, Any], Any] ) -> PeriodArray: """ - Add or subtract array of integers; equivalent to applying - `_time_shift` pointwise. + Add or subtract array of integers. Parameters ---------- diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index c6c8695ab01da..b72a6aa83c369 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -33,6 +33,7 @@ npt, ) from pandas.compat.numpy import function as nv +from pandas.errors import NullFrequencyError from pandas.util._decorators import ( Appender, cache_readonly, @@ -353,10 +354,7 @@ def shift(self: _T, periods: int = 1, freq=None) -> _T: Index.shift : Shift values of Index. PeriodIndex.shift : Shift values of PeriodIndex. """ - arr = self._data.view() - arr._freq = self.freq - result = arr._time_shift(periods, freq=freq) - return type(self)._simple_new(result, name=self.name) + raise NotImplementedError # -------------------------------------------------------------------- @@ -400,6 +398,32 @@ def values(self) -> np.ndarray: # NB: For Datetime64TZ this is lossy return self._data._ndarray + @doc(DatetimeIndexOpsMixin.shift) + def shift(self: _TDT, periods: int = 1, freq=None) -> _TDT: + if freq is not None and freq != self.freq: + if isinstance(freq, str): + freq = to_offset(freq) + offset = periods * freq + return self + offset + + if periods == 0 or len(self) == 0: + # GH#14811 empty case + return self.copy() + + if self.freq is None: + raise NullFrequencyError("Cannot shift with no freq") + + start = self[0] + periods * self.freq + end = self[-1] + periods * self.freq + + # Note: in the DatetimeTZ case, _generate_range will infer the + # appropriate timezone from `start` and `end`, so tz does not need + # to be passed explicitly. + result = self._data._generate_range( + start=start, end=end, periods=None, freq=self.freq + ) + return type(self)._simple_new(result, name=self.name) + # -------------------------------------------------------------------- # Set Operation Methods diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index a09b987496a40..e8a734864a9c8 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -481,6 +481,14 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime): iv = Period(parsed, freq=reso.attr_abbrev) return (iv.asfreq(self.freq, how="start"), iv.asfreq(self.freq, how="end")) + @doc(DatetimeIndexOpsMixin.shift) + def shift(self, periods: int = 1, freq=None): + if freq is not None: + raise TypeError( + f"`freq` argument is not supported for {type(self).__name__}.shift" + ) + return self + periods + def period_range( start=None, end=None, periods: int | None = None, freq=None, name=None diff --git a/pandas/tests/indexes/period/methods/test_shift.py b/pandas/tests/indexes/period/methods/test_shift.py index 730172ca56938..48dc5f0e64d08 100644 --- a/pandas/tests/indexes/period/methods/test_shift.py +++ b/pandas/tests/indexes/period/methods/test_shift.py @@ -66,7 +66,7 @@ def test_shift_corner_cases(self): # GH#9903 idx = PeriodIndex([], name="xxx", freq="H") - msg = "`freq` argument is not supported for PeriodArray._time_shift" + msg = "`freq` argument is not supported for PeriodIndex.shift" with pytest.raises(TypeError, match=msg): # period shift doesn't accept freq idx.shift(1, freq="H")