diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 7cf50ff2b88af..473e63dfa7a12 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1129,56 +1129,46 @@ def _sub_period(self, other): def _add_offset(self, offset): raise AbstractMethodError(self) - def _add_delta(self, other): + def _add_timedeltalike_scalar(self, other): """ - Add a timedelta-like, Tick or TimedeltaIndex-like object - to self, yielding an int64 numpy array - - Parameters - ---------- - delta : {timedelta, np.timedelta64, Tick, - TimedeltaIndex, ndarray[timedelta64]} + Add a delta of a timedeltalike Returns ------- - result : ndarray[int64] - - Notes - ----- - The result's name is set outside of _add_delta by the calling - method (__add__ or __sub__), if necessary (i.e. for Indexes). - """ - if isinstance(other, (Tick, timedelta, np.timedelta64)): - new_values = self._add_timedeltalike_scalar(other) - elif is_timedelta64_dtype(other): - # ndarray[timedelta64] or TimedeltaArray/index - new_values = self._add_delta_tdi(other) - - return new_values - - def _add_timedeltalike_scalar(self, other): - """ - Add a delta of a timedeltalike - return the i8 result view + Same type as self """ if isna(other): # i.e np.timedelta64("NaT"), not recognized by delta_to_nanoseconds new_values = np.empty(self.shape, dtype="i8") new_values[:] = iNaT - return new_values + return type(self)(new_values, dtype=self.dtype) inc = delta_to_nanoseconds(other) new_values = checked_add_with_arr(self.asi8, inc, arr_mask=self._isnan).view( "i8" ) new_values = self._maybe_mask_results(new_values) - return new_values.view("i8") - def _add_delta_tdi(self, other): + new_freq = None + if isinstance(self.freq, Tick) or is_period_dtype(self.dtype): + # adding a scalar preserves freq + new_freq = self.freq + + if new_freq is not None: + # fastpath that doesnt require inference + return type(self)(new_values, dtype=self.dtype, freq=new_freq) + return type(self)._from_sequence(new_values, dtype=self.dtype, freq="infer") + + def _add_timedelta_arraylike(self, other): """ Add a delta of a TimedeltaIndex - return the i8 result view + + Returns + ------- + Same type as self """ + # overriden by PeriodArray + if len(self) != len(other): raise ValueError("cannot add indices of unequal length") @@ -1196,7 +1186,8 @@ def _add_delta_tdi(self, other): if self._hasnans or other._hasnans: mask = (self._isnan) | (other._isnan) new_values[mask] = iNaT - return new_values.view("i8") + + return type(self)._from_sequence(new_values, dtype=self.dtype, freq="infer") def _add_nat(self): """ @@ -1338,7 +1329,7 @@ def __add__(self, other): if other is NaT: result = self._add_nat() elif isinstance(other, (Tick, timedelta, np.timedelta64)): - result = self._add_delta(other) + result = self._add_timedeltalike_scalar(other) elif isinstance(other, DateOffset): # specifically _not_ a Tick result = self._add_offset(other) @@ -1354,7 +1345,7 @@ def __add__(self, other): # array-like others elif is_timedelta64_dtype(other): # TimedeltaIndex, ndarray[timedelta64] - result = self._add_delta(other) + result = self._add_timedelta_arraylike(other) elif is_object_dtype(other): # e.g. Array/Index of DateOffset objects result = self._addsub_object_array(other, operator.add) @@ -1390,7 +1381,7 @@ def __sub__(self, other): if other is NaT: result = self._sub_nat() elif isinstance(other, (Tick, timedelta, np.timedelta64)): - result = self._add_delta(-other) + result = self._add_timedeltalike_scalar(-other) elif isinstance(other, DateOffset): # specifically _not_ a Tick result = self._add_offset(-other) @@ -1409,7 +1400,7 @@ def __sub__(self, other): # array-like others elif is_timedelta64_dtype(other): # TimedeltaIndex, ndarray[timedelta64] - result = self._add_delta(-other) + result = self._add_timedelta_arraylike(-other) elif is_object_dtype(other): # e.g. Array/Index of DateOffset objects result = self._addsub_object_array(other, operator.sub) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 2110f782330fb..2d74582b049f7 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -718,23 +718,6 @@ def _sub_datetimelike_scalar(self, other): result = self._maybe_mask_results(result) return result.view("timedelta64[ns]") - def _add_delta(self, delta): - """ - Add a timedelta-like, Tick, or TimedeltaIndex-like object - to self, yielding a new DatetimeArray - - Parameters - ---------- - other : {timedelta, np.timedelta64, Tick, - TimedeltaIndex, ndarray[timedelta64]} - - Returns - ------- - result : DatetimeArray - """ - new_values = super()._add_delta(delta) - return type(self)._from_sequence(new_values, tz=self.tz, freq="infer") - # ----------------------------------------------------------------- # Timezone Conversion and Localization Methods diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 5eeee644b3854..357ab09fbe287 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -657,10 +657,11 @@ def _add_timedeltalike_scalar(self, other): Returns ------- - result : ndarray[int64] + PeriodArray """ - assert isinstance(self.freq, Tick) # checked by calling function - assert isinstance(other, (timedelta, np.timedelta64, Tick)) + if not isinstance(self.freq, Tick): + # We cannot add timedelta-like to non-tick PeriodArray + raise raise_on_incompatible(self, other) if notna(other): # special handling for np.timedelta64("NaT"), avoid calling @@ -670,10 +671,9 @@ def _add_timedeltalike_scalar(self, other): # Note: when calling parent class's _add_timedeltalike_scalar, # it will call delta_to_nanoseconds(delta). Because delta here # is an integer, delta_to_nanoseconds will return it unchanged. - ordinals = super()._add_timedeltalike_scalar(other) - return ordinals + return super()._add_timedeltalike_scalar(other) - def _add_delta_tdi(self, other): + def _add_timedelta_arraylike(self, other): """ Parameters ---------- @@ -683,7 +683,9 @@ def _add_delta_tdi(self, other): ------- result : ndarray[int64] """ - assert isinstance(self.freq, Tick) # checked by calling function + if not isinstance(self.freq, Tick): + # We cannot add timedelta-like to non-tick PeriodArray + raise raise_on_incompatible(self, other) if not np.all(isna(other)): delta = self._check_timedeltalike_freq_compat(other) @@ -691,28 +693,8 @@ def _add_delta_tdi(self, other): # all-NaT TimedeltaIndex is equivalent to a single scalar td64 NaT return self + np.timedelta64("NaT") - return self._addsub_int_array(delta, operator.add).asi8 - - def _add_delta(self, other): - """ - Add a timedelta-like, Tick, or TimedeltaIndex-like object - to self, yielding a new PeriodArray - - Parameters - ---------- - other : {timedelta, np.timedelta64, Tick, - TimedeltaIndex, ndarray[timedelta64]} - - Returns - ------- - result : PeriodArray - """ - if not isinstance(self.freq, Tick): - # We cannot add timedelta-like to non-tick PeriodArray - raise raise_on_incompatible(self, other) - - new_ordinals = super()._add_delta(other) - return type(self)(new_ordinals, freq=self.freq) + ordinals = self._addsub_int_array(delta, operator.add).asi8 + return type(self)(ordinals, dtype=self.dtype) def _check_timedeltalike_freq_compat(self, other): """ diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index dbc0b0b3ccbbf..a25426c5c99cc 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -400,23 +400,6 @@ def _add_offset(self, other): f"cannot add the type {type(other).__name__} to a {type(self).__name__}" ) - def _add_delta(self, delta): - """ - Add a timedelta-like, Tick, or TimedeltaIndex-like object - to self, yielding a new TimedeltaArray. - - Parameters - ---------- - other : {timedelta, np.timedelta64, Tick, - TimedeltaIndex, ndarray[timedelta64]} - - Returns - ------- - result : TimedeltaArray - """ - new_values = super()._add_delta(delta) - return type(self)._from_sequence(new_values, freq="infer") - def _add_datetime_arraylike(self, other): """ Add DatetimeArray/Index or ndarray[datetime64] to TimedeltaArray.