Skip to content

Commit d2ffd98

Browse files
authored
BUG: respect freq=None in DTA constructor (#47296)
* BUG: respect freq=None in DTA constructor * GH ref * mypy fixu[
1 parent 0be24e0 commit d2ffd98

File tree

6 files changed

+44
-29
lines changed

6 files changed

+44
-29
lines changed

doc/source/whatsnew/v1.5.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ Datetimelike
737737
- Bug in :meth:`DatetimeIndex.tz_localize` localizing to UTC failing to make a copy of the underlying data (:issue:`46460`)
738738
- Bug in :meth:`DatetimeIndex.resolution` incorrectly returning "day" instead of "nanosecond" for nanosecond-resolution indexes (:issue:`46903`)
739739
- Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`47266`)
740+
- Bug in :class:`DatetimeArray` construction when passed another :class:`DatetimeArray` and ``freq=None`` incorrectly inferring the freq from the given array (:issue:`47296`)
740741
-
741742

742743
Timedelta

pandas/core/arrays/datetimes.py

+28-19
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import (
1010
TYPE_CHECKING,
1111
Literal,
12+
cast,
1213
)
1314
import warnings
1415

@@ -256,15 +257,26 @@ class DatetimeArray(dtl.TimelikeOps, dtl.DatelikeOps):
256257
_freq = None
257258

258259
def __init__(
259-
self, values, dtype=DT64NS_DTYPE, freq=None, copy: bool = False
260+
self, values, dtype=DT64NS_DTYPE, freq=lib.no_default, copy: bool = False
260261
) -> None:
261262
values = extract_array(values, extract_numpy=True)
262263
if isinstance(values, IntegerArray):
263264
values = values.to_numpy("int64", na_value=iNaT)
264265

265266
inferred_freq = getattr(values, "_freq", None)
267+
explicit_none = freq is None
268+
freq = freq if freq is not lib.no_default else None
266269

267270
if isinstance(values, type(self)):
271+
if explicit_none:
272+
# don't inherit from values
273+
pass
274+
elif freq is None:
275+
freq = values.freq
276+
elif freq and values.freq:
277+
freq = to_offset(freq)
278+
freq, _ = dtl.validate_inferred_freq(freq, values.freq, False)
279+
268280
# validation
269281
dtz = getattr(dtype, "tz", None)
270282
if dtz and values.tz is None:
@@ -279,14 +291,13 @@ def __init__(
279291
elif values.tz:
280292
dtype = values.dtype
281293

282-
if freq is None:
283-
freq = values.freq
284294
values = values._ndarray
285295

286296
if not isinstance(values, np.ndarray):
287297
raise ValueError(
288-
f"Unexpected type '{type(values).__name__}'. 'values' must be "
289-
"a DatetimeArray, ndarray, or Series or Index containing one of those."
298+
f"Unexpected type '{type(values).__name__}'. 'values' must be a "
299+
f"{type(self).__name__}, ndarray, or Series or Index "
300+
"containing one of those."
290301
)
291302
if values.ndim not in [1, 2]:
292303
raise ValueError("Only 1-dimensional input arrays are supported.")
@@ -297,31 +308,19 @@ def __init__(
297308
# nanosecond UTC (or tz-naive) unix timestamps
298309
values = values.view(DT64NS_DTYPE)
299310

300-
if values.dtype != DT64NS_DTYPE:
301-
raise ValueError(
302-
"The dtype of 'values' is incorrect. Must be 'datetime64[ns]'. "
303-
f"Got {values.dtype} instead."
304-
)
305-
311+
_validate_dt64_dtype(values.dtype)
306312
dtype = _validate_dt64_dtype(dtype)
307313

308314
if freq == "infer":
309315
raise ValueError(
310-
"Frequency inference not allowed in DatetimeArray.__init__. "
316+
f"Frequency inference not allowed in {type(self).__name__}.__init__. "
311317
"Use 'pd.array()' instead."
312318
)
313319

314320
if copy:
315321
values = values.copy()
316322
if freq:
317323
freq = to_offset(freq)
318-
if getattr(dtype, "tz", None):
319-
# https://github.com/pandas-dev/pandas/issues/18595
320-
# Ensure that we have a standard timezone for pytz objects.
321-
# Without this, things like adding an array of timedeltas and
322-
# a tz-aware Timestamp (with a tz specific to its datetime) will
323-
# be incorrect(ish?) for the array as a whole
324-
dtype = DatetimeTZDtype(tz=timezones.tz_standardize(dtype.tz))
325324

326325
NDArrayBacked.__init__(self, values=values, dtype=dtype)
327326
self._freq = freq
@@ -2394,6 +2393,16 @@ def _validate_dt64_dtype(dtype):
23942393
f"Unexpected value for 'dtype': '{dtype}'. "
23952394
"Must be 'datetime64[ns]' or DatetimeTZDtype'."
23962395
)
2396+
2397+
if getattr(dtype, "tz", None):
2398+
# https://github.com/pandas-dev/pandas/issues/18595
2399+
# Ensure that we have a standard timezone for pytz objects.
2400+
# Without this, things like adding an array of timedeltas and
2401+
# a tz-aware Timestamp (with a tz specific to its datetime) will
2402+
# be incorrect(ish?) for the array as a whole
2403+
dtype = cast(DatetimeTZDtype, dtype)
2404+
dtype = DatetimeTZDtype(tz=timezones.tz_standardize(dtype.tz))
2405+
23972406
return dtype
23982407

23992408

pandas/core/arrays/timedeltas.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -186,21 +186,22 @@ def __init__(
186186

187187
if isinstance(values, type(self)):
188188
if explicit_none:
189-
# dont inherit from values
189+
# don't inherit from values
190190
pass
191191
elif freq is None:
192192
freq = values.freq
193193
elif freq and values.freq:
194194
freq = to_offset(freq)
195195
freq, _ = dtl.validate_inferred_freq(freq, values.freq, False)
196+
196197
values = values._ndarray
197198

198199
if not isinstance(values, np.ndarray):
199-
msg = (
200+
raise ValueError(
200201
f"Unexpected type '{type(values).__name__}'. 'values' must be a "
201-
"TimedeltaArray, ndarray, or Series or Index containing one of those."
202+
f"{type(self).__name__}, ndarray, or Series or Index "
203+
"containing one of those."
202204
)
203-
raise ValueError(msg)
204205
if values.ndim not in [1, 2]:
205206
raise ValueError("Only 1-dimensional input arrays are supported.")
206207

@@ -214,11 +215,10 @@ def __init__(
214215
dtype = _validate_td64_dtype(dtype)
215216

216217
if freq == "infer":
217-
msg = (
218-
"Frequency inference not allowed in TimedeltaArray.__init__. "
218+
raise ValueError(
219+
f"Frequency inference not allowed in {type(self).__name__}.__init__. "
219220
"Use 'pd.array()' instead."
220221
)
221-
raise ValueError(msg)
222222

223223
if copy:
224224
values = values.copy()

pandas/tests/arrays/datetimes/test_constructors.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,8 @@ def test_non_array_raises(self):
8787
def test_bool_dtype_raises(self):
8888
arr = np.array([1, 2, 3], dtype="bool")
8989

90-
with pytest.raises(
91-
ValueError, match="The dtype of 'values' is incorrect.*bool"
92-
):
90+
msg = "Unexpected value for 'dtype': 'bool'. Must be"
91+
with pytest.raises(ValueError, match=msg):
9392
DatetimeArray(arr)
9493

9594
msg = r"dtype bool cannot be converted to datetime64\[ns\]"

pandas/tests/indexes/datetimes/test_constructors.py

+3
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,9 @@ def test_explicit_none_freq(self):
925925
result = DatetimeIndex(rng._data, freq=None)
926926
assert result.freq is None
927927

928+
dta = DatetimeArray(rng, freq=None)
929+
assert dta.freq is None
930+
928931
def test_dti_constructor_years_only(self, tz_naive_fixture):
929932
tz = tz_naive_fixture
930933
# GH 6961

pandas/tests/indexes/timedeltas/test_constructors.py

+3
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ def test_explicit_none_freq(self):
263263
result = TimedeltaIndex(tdi._data, freq=None)
264264
assert result.freq is None
265265

266+
tda = TimedeltaArray(tdi, freq=None)
267+
assert tda.freq is None
268+
266269
def test_from_categorical(self):
267270
tdi = timedelta_range(1, periods=5)
268271

0 commit comments

Comments
 (0)