diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index b2bff0b0142e2..098feb9f1290f 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -396,6 +396,34 @@ def floor(self, freq, ambiguous="raise", nonexistent="raise"): def ceil(self, freq, ambiguous="raise", nonexistent="raise"): return self._round(freq, RoundTo.PLUS_INFTY, ambiguous, nonexistent) + def _with_freq(self, freq): + """ + Helper to set our freq in-place, returning self to allow method chaining. + + Parameters + ---------- + freq : DateOffset, None, or "infer" + + Returns + ------- + self + """ + # GH#29843 + if freq is None: + # Always valid + pass + elif len(self) == 0 and isinstance(freq, DateOffset): + # Always valid. In the TimedeltaArray case, we assume this + # is a Tick offset. + pass + else: + # As an internal method, we can ensure this assertion always holds + assert freq == "infer" + freq = frequencies.to_offset(self.inferred_freq) + + self._freq = freq + return self + class DatetimeLikeArrayMixin(ExtensionOpsMixin, AttributesMixin, ExtensionArray): """ @@ -1157,7 +1185,7 @@ def _add_timedeltalike_scalar(self, other): 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") + return type(self)(new_values, dtype=self.dtype)._with_freq("infer") def _add_timedelta_arraylike(self, other): """ @@ -1187,7 +1215,7 @@ def _add_timedelta_arraylike(self, other): mask = (self._isnan) | (other._isnan) new_values[mask] = iNaT - return type(self)._from_sequence(new_values, dtype=self.dtype, freq="infer") + return type(self)(new_values, dtype=self.dtype)._with_freq("infer") def _add_nat(self): """ diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 2d74582b049f7..e2a13df069ae2 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -697,7 +697,7 @@ def _add_offset(self, offset): # GH#30336 _from_sequence won't be able to infer self.tz return type(self)._from_sequence(result).tz_localize(self.tz) - return type(self)._from_sequence(result, freq="infer") + return type(self)._from_sequence(result)._with_freq("infer") def _sub_datetimelike_scalar(self, other): # subtract a datetime from myself, yielding a ndarray[timedelta64[ns]] @@ -1031,7 +1031,7 @@ def normalize(self): new_values[not_null] = new_values[not_null] - adjustment else: new_values = conversion.normalize_i8_timestamps(self.asi8, self.tz) - return type(self)._from_sequence(new_values, freq="infer").tz_localize(self.tz) + return type(self)(new_values)._with_freq("infer").tz_localize(self.tz) def to_period(self, freq=None): """ diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 6ee439de414f1..c24b0b5fa64b8 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -185,6 +185,7 @@ def _from_sequence( validate_dtype_freq(scalars.dtype, freq) if copy: scalars = scalars.copy() + assert isinstance(scalars, PeriodArray) # for mypy return scalars periods = np.asarray(scalars, dtype=object) @@ -452,7 +453,7 @@ def to_timestamp(self, freq=None, how="start"): new_data = self.asfreq(freq, how=how) new_data = libperiod.periodarr_to_dt64arr(new_data.asi8, base) - return DatetimeArray._from_sequence(new_data, freq="infer") + return DatetimeArray(new_data)._with_freq("infer") # -------------------------------------------------------------------- diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 2f641a3d4c111..25333b3a08dce 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -43,7 +43,7 @@ from pandas.core.ops import get_op_result_name from pandas.core.tools.timedeltas import to_timedelta -from pandas.tseries.frequencies import DateOffset, to_offset +from pandas.tseries.frequencies import DateOffset _index_doc_kwargs = dict(ibase._index_doc_kwargs) @@ -623,19 +623,7 @@ def _set_freq(self, freq): freq : DateOffset, None, or "infer" """ # GH#29843 - if freq is None: - # Always valid - pass - elif len(self) == 0 and isinstance(freq, DateOffset): - # Always valid. In the TimedeltaIndex case, we assume this - # is a Tick offset. - pass - else: - # As an internal method, we can ensure this assertion always holds - assert freq == "infer" - freq = to_offset(self.inferred_freq) - - self._data._freq = freq + self._data._with_freq(freq) def _shallow_copy(self, values=None, name: Label = lib.no_default): name = self.name if name is lib.no_default else name