Skip to content

Commit bfd2891

Browse files
jbrockmendelclaude
andcommitted
REF: move freq handling out of DTA._from_sequence_not_strict, move _with_freq logic to Index
- Remove freq parameter from DTA._from_sequence_not_strict; it no longer reads data.freq or calls _maybe_pin_freq - DTI.__new__ now extracts freq from incoming data and calls _maybe_pin_freq externally, following the pattern established for TDI in pandas-dev#65162 - Move _with_freq freq-resolution logic to DatetimeTimedeltaMixin._with_freq so the Index resolves "infer" before delegating to the array - Relax array _with_freq to accept BaseOffset on non-empty arrays (needed since the Index now resolves "infer" to an offset before passing down) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2343120 commit bfd2891

4 files changed

Lines changed: 34 additions & 15 deletions

File tree

pandas/core/arrays/datetimelike.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,8 +1865,7 @@ def freq(self, value) -> None:
18651865
def _maybe_pin_freq(self, freq, validate_kwds: dict) -> None:
18661866
"""
18671867
Constructor helper to pin the appropriate `freq` attribute. Assumes
1868-
that self._freq is currently set to any freq inferred in
1869-
_from_sequence_not_strict.
1868+
that self._freq is currently set to any freq inferred from input data.
18701869
"""
18711870
if freq is None:
18721871
# user explicitly passed None -> override any inferred_freq
@@ -2466,14 +2465,14 @@ def _with_freq(self, freq) -> Self:
24662465
if freq is None:
24672466
# Always valid
24682467
pass
2469-
elif len(self) == 0 and isinstance(freq, BaseOffset):
2470-
# Always valid. In the TimedeltaArray case, we require a Tick offset
2468+
elif isinstance(freq, BaseOffset):
2469+
# In the TimedeltaArray case, we require a Tick offset
24712470
if self.dtype.kind == "m" and not isinstance(freq, (Tick, Day)):
24722471
raise TypeError("TimedeltaArray/Index freq must be a Tick")
2473-
else:
2474-
# As an internal method, we can ensure this assertion always holds
2475-
assert freq == "infer"
2472+
elif freq == "infer":
24762473
freq = to_offset(self.inferred_freq)
2474+
else:
2475+
raise ValueError(f"Invalid frequency: {freq!r}")
24772476

24782477
arr = self.view()
24792478
arr._freq = freq

pandas/core/arrays/datetimes.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,6 @@ def _from_sequence_not_strict(
332332
dtype=None,
333333
copy: bool = False,
334334
tz=lib.no_default,
335-
freq: str | BaseOffset | lib.NoDefault | None = lib.no_default,
336335
dayfirst: bool = False,
337336
yearfirst: bool = False,
338337
ambiguous: TimeAmbiguous = "raise",
@@ -360,9 +359,6 @@ def _from_sequence_not_strict(
360359
data, copy = dtl.ensure_arraylike_for_datetimelike(
361360
data, copy, cls_name="DatetimeArray"
362361
)
363-
inferred_freq = None
364-
if isinstance(data, DatetimeArray):
365-
inferred_freq = data.freq
366362

367363
subarr, tz = _sequence_to_dt64(
368364
data,
@@ -384,16 +380,14 @@ def _from_sequence_not_strict(
384380
data_unit = np.datetime_data(subarr.dtype)[0]
385381
data_unit = cast("TimeUnit", data_unit)
386382
data_dtype = tz_to_dtype(tz, data_unit)
387-
result = cls._simple_new(subarr, freq=inferred_freq, dtype=data_dtype)
383+
result = cls._simple_new(subarr, dtype=data_dtype)
388384
if unit is not None and unit != result.unit:
389385
# If unit was specified in user-passed dtype, cast to it here
390386
# error: Argument 1 to "as_unit" of "TimelikeOps" has
391387
# incompatible type "str"; expected "Literal['s', 'ms', 'us', 'ns']"
392388
# [arg-type]
393389
result = result.as_unit(unit) # type: ignore[arg-type]
394390

395-
validate_kwds = {"ambiguous": ambiguous}
396-
result._maybe_pin_freq(freq, validate_kwds)
397391
return result
398392

399393
@classmethod

pandas/core/indexes/datetimelike.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,19 @@ def as_unit(self, unit: TimeUnit) -> Self:
849849
return result
850850

851851
def _with_freq(self, freq):
852+
# GH#29843
853+
if freq is None:
854+
# Always valid
855+
pass
856+
elif len(self) == 0 and isinstance(freq, BaseOffset):
857+
# Always valid. In the TimedeltaArray case, we require a Tick offset
858+
if self.dtype.kind == "m" and not isinstance(freq, (Tick, Day)):
859+
raise TypeError("TimedeltaArray/Index freq must be a Tick")
860+
else:
861+
# As an internal method, we can ensure this assertion always holds
862+
assert freq == "infer"
863+
freq = to_offset(self.inferred_freq)
864+
852865
arr = self._data._with_freq(freq)
853866
return type(self)._simple_new(arr, name=self._name)
854867

pandas/core/indexes/datetimes.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,16 +901,29 @@ def __new__(
901901
data = data.copy()
902902
return cls._simple_new(data, name=name)
903903

904+
# Extract freq from incoming data before array conversion strips it
905+
inferred_freq = None
906+
if isinstance(data, DatetimeArray):
907+
inferred_freq = data.freq
908+
elif isinstance(data, (Index, ABCSeries)):
909+
values = data._values
910+
if isinstance(values, DatetimeArray):
911+
inferred_freq = values.freq
912+
904913
dtarr = DatetimeArray._from_sequence_not_strict(
905914
data,
906915
dtype=dtype,
907916
copy=copy,
908917
tz=tz,
909-
freq=freq,
910918
dayfirst=dayfirst,
911919
yearfirst=yearfirst,
912920
ambiguous=ambiguous,
913921
)
922+
923+
if inferred_freq is not None:
924+
dtarr._freq = inferred_freq
925+
dtarr._maybe_pin_freq(freq, {"ambiguous": ambiguous})
926+
914927
refs = None
915928
if not copy and isinstance(data, (Index, ABCSeries)):
916929
refs = data._references

0 commit comments

Comments
 (0)