Skip to content

Commit 74c4cd1

Browse files
authored
DEPR: Series(dt64_naive, dtype=dt64tz) (#49242)
* DEPR: Series(dt64_naive, dtype=dt64tz) * mypy fixup
1 parent 6a1ae42 commit 74c4cd1

File tree

6 files changed

+50
-80
lines changed

6 files changed

+50
-80
lines changed

doc/source/whatsnew/v2.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ Removal of prior version deprecations/changes
165165
- Removed deprecated :meth:`Index.is_mixed`, check ``index.inferred_type`` directly instead (:issue:`32922`)
166166
- Removed deprecated :func:`pandas.api.types.is_categorical`; use :func:`pandas.api.types.is_categorical_dtype` instead (:issue:`33385`)
167167
- Removed deprecated :meth:`Index.asi8` (:issue:`37877`)
168+
- Enforced deprecation changing behavior when passing ``datetime64[ns]`` dtype data and timezone-aware dtype to :class:`Series`, interpreting the values as wall-times instead of UTC times, matching :class:`DatetimeIndex` behavior (:issue:`41662`)
168169
- Removed deprecated :meth:`DataFrame._AXIS_NUMBERS`, :meth:`DataFrame._AXIS_NAMES`, :meth:`Series._AXIS_NUMBERS`, :meth:`Series._AXIS_NAMES` (:issue:`33637`)
169170
- Removed deprecated :meth:`Index.to_native_types`, use ``obj.astype(str)`` instead (:issue:`36418`)
170171
- Removed deprecated :meth:`Series.iteritems`, :meth:`DataFrame.iteritems`, use ``obj.items`` instead (:issue:`45321`)

pandas/core/arrays/datetimes.py

+5
Original file line numberDiff line numberDiff line change
@@ -2123,10 +2123,15 @@ def _sequence_to_dt64ns(
21232123
# Convert tz-naive to UTC
21242124
# TODO: if tz is UTC, are there situations where we *don't* want a
21252125
# copy? tz_localize_to_utc always makes one.
2126+
shape = data.shape
2127+
if data.ndim > 1:
2128+
data = data.ravel()
2129+
21262130
data = tzconversion.tz_localize_to_utc(
21272131
data.view("i8"), tz, ambiguous=ambiguous, creso=data_unit
21282132
)
21292133
data = data.view(new_dtype)
2134+
data = data.reshape(shape)
21302135

21312136
assert data.dtype == new_dtype, data.dtype
21322137
result = data

pandas/core/construction.py

+1-14
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,7 @@
5555
is_object_dtype,
5656
is_timedelta64_ns_dtype,
5757
)
58-
from pandas.core.dtypes.dtypes import (
59-
DatetimeTZDtype,
60-
PandasDtype,
61-
)
58+
from pandas.core.dtypes.dtypes import PandasDtype
6259
from pandas.core.dtypes.generic import (
6360
ABCExtensionArray,
6461
ABCIndex,
@@ -800,16 +797,6 @@ def _try_cast(
800797

801798
elif isinstance(dtype, ExtensionDtype):
802799
# create an extension array from its dtype
803-
if isinstance(dtype, DatetimeTZDtype):
804-
# We can't go through _from_sequence because it handles dt64naive
805-
# data differently; _from_sequence treats naive as wall times,
806-
# while maybe_cast_to_datetime treats it as UTC
807-
# see test_maybe_promote_any_numpy_dtype_with_datetimetz
808-
# TODO(2.0): with deprecations enforced, should be able to remove
809-
# special case.
810-
return maybe_cast_to_datetime(arr, dtype)
811-
# TODO: copy?
812-
813800
array_type = dtype.construct_array_type()._from_sequence
814801
subarr = array_type(arr, dtype=dtype, copy=copy)
815802
return subarr

pandas/core/dtypes/cast.py

+27-59
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565
is_complex,
6666
is_complex_dtype,
6767
is_datetime64_dtype,
68-
is_datetime64tz_dtype,
6968
is_dtype_equal,
7069
is_extension_array_dtype,
7170
is_float,
@@ -1314,13 +1313,15 @@ def try_timedelta(v: np.ndarray) -> np.ndarray:
13141313

13151314

13161315
def maybe_cast_to_datetime(
1317-
value: ExtensionArray | np.ndarray | list, dtype: DtypeObj | None
1316+
value: ExtensionArray | np.ndarray | list, dtype: np.dtype | None
13181317
) -> ExtensionArray | np.ndarray:
13191318
"""
13201319
try to cast the array/value to a datetimelike dtype, converting float
13211320
nan to iNaT
13221321
13231322
We allow a list *only* when dtype is not None.
1323+
1324+
Caller is responsible for handling ExtensionDtype cases.
13241325
"""
13251326
from pandas.core.arrays.datetimes import sequence_to_datetimes
13261327
from pandas.core.arrays.timedeltas import TimedeltaArray
@@ -1332,18 +1333,22 @@ def maybe_cast_to_datetime(
13321333
# TODO: _from_sequence would raise ValueError in cases where
13331334
# _ensure_nanosecond_dtype raises TypeError
13341335
dtype = cast(np.dtype, dtype)
1335-
dtype = _ensure_nanosecond_dtype(dtype)
1336+
# Incompatible types in assignment (expression has type "Union[dtype[Any],
1337+
# ExtensionDtype]", variable has type "Optional[dtype[Any]]")
1338+
dtype = _ensure_nanosecond_dtype(dtype) # type: ignore[assignment]
13361339
res = TimedeltaArray._from_sequence(value, dtype=dtype)
13371340
return res
13381341

13391342
if dtype is not None:
13401343
is_datetime64 = is_datetime64_dtype(dtype)
1341-
is_datetime64tz = is_datetime64tz_dtype(dtype)
13421344

13431345
vdtype = getattr(value, "dtype", None)
13441346

1345-
if is_datetime64 or is_datetime64tz:
1346-
dtype = _ensure_nanosecond_dtype(dtype)
1347+
if is_datetime64:
1348+
# Incompatible types in assignment (expression has type
1349+
# "Union[dtype[Any], ExtensionDtype]", variable has type
1350+
# "Optional[dtype[Any]]")
1351+
dtype = _ensure_nanosecond_dtype(dtype) # type: ignore[assignment]
13471352

13481353
value = np.array(value, copy=False)
13491354

@@ -1352,59 +1357,22 @@ def maybe_cast_to_datetime(
13521357
_disallow_mismatched_datetimelike(value, dtype)
13531358

13541359
try:
1355-
if is_datetime64:
1356-
dta = sequence_to_datetimes(value)
1357-
# GH 25843: Remove tz information since the dtype
1358-
# didn't specify one
1359-
1360-
if dta.tz is not None:
1361-
raise ValueError(
1362-
"Cannot convert timezone-aware data to "
1363-
"timezone-naive dtype. Use "
1364-
"pd.Series(values).dt.tz_localize(None) instead."
1365-
)
1366-
1367-
# TODO(2.0): Do this astype in sequence_to_datetimes to
1368-
# avoid potential extra copy?
1369-
dta = dta.astype(dtype, copy=False)
1370-
value = dta
1371-
elif is_datetime64tz:
1372-
dtype = cast(DatetimeTZDtype, dtype)
1373-
# The string check can be removed once issue #13712
1374-
# is solved. String data that is passed with a
1375-
# datetime64tz is assumed to be naive which should
1376-
# be localized to the timezone.
1377-
is_dt_string = is_string_dtype(value.dtype)
1378-
dta = sequence_to_datetimes(value)
1379-
if dta.tz is not None:
1380-
value = dta.astype(dtype, copy=False)
1381-
elif is_dt_string:
1382-
# Strings here are naive, so directly localize
1383-
# equiv: dta.astype(dtype) # though deprecated
1384-
1385-
value = dta.tz_localize(dtype.tz)
1386-
else:
1387-
# Numeric values are UTC at this point,
1388-
# so localize and convert
1389-
# equiv: Series(dta).astype(dtype) # though deprecated
1390-
if getattr(vdtype, "kind", None) == "M":
1391-
# GH#24559, GH#33401 deprecate behavior inconsistent
1392-
# with DatetimeArray/DatetimeIndex
1393-
warnings.warn(
1394-
"In a future version, constructing a Series "
1395-
"from datetime64[ns] data and a "
1396-
"DatetimeTZDtype will interpret the data "
1397-
"as wall-times instead of "
1398-
"UTC times, matching the behavior of "
1399-
"DatetimeIndex. To treat the data as UTC "
1400-
"times, use pd.Series(data).dt"
1401-
".tz_localize('UTC').tz_convert(dtype.tz) "
1402-
"or pd.Series(data.view('int64'), dtype=dtype)",
1403-
FutureWarning,
1404-
stacklevel=find_stack_level(),
1405-
)
1406-
1407-
value = dta.tz_localize("UTC").tz_convert(dtype.tz)
1360+
dta = sequence_to_datetimes(value)
1361+
# GH 25843: Remove tz information since the dtype
1362+
# didn't specify one
1363+
1364+
if dta.tz is not None:
1365+
raise ValueError(
1366+
"Cannot convert timezone-aware data to "
1367+
"timezone-naive dtype. Use "
1368+
"pd.Series(values).dt.tz_localize(None) instead."
1369+
)
1370+
1371+
# TODO(2.0): Do this astype in sequence_to_datetimes to
1372+
# avoid potential extra copy?
1373+
dta = dta.astype(dtype, copy=False)
1374+
value = dta
1375+
14081376
except OutOfBoundsDatetime:
14091377
raise
14101378
except ParserError:

pandas/core/internals/construction.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
is_named_tuple,
4444
is_object_dtype,
4545
)
46+
from pandas.core.dtypes.dtypes import ExtensionDtype
4647
from pandas.core.dtypes.generic import (
4748
ABCDataFrame,
4849
ABCSeries,
@@ -1054,7 +1055,15 @@ def _convert_object_array(
10541055
def convert(arr):
10551056
if dtype != np.dtype("O"):
10561057
arr = lib.maybe_convert_objects(arr)
1057-
arr = maybe_cast_to_datetime(arr, dtype)
1058+
1059+
if isinstance(dtype, ExtensionDtype):
1060+
# TODO: test(s) that get here
1061+
# TODO: try to de-duplicate this convert function with
1062+
# core.construction functions
1063+
cls = dtype.construct_array_type()
1064+
arr = cls._from_sequence(arr, dtype=dtype, copy=False)
1065+
else:
1066+
arr = maybe_cast_to_datetime(arr, dtype)
10581067
return arr
10591068

10601069
arrays = [convert(arr) for arr in content]

pandas/tests/series/test_constructors.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1241,14 +1241,14 @@ def test_construction_consistency(self):
12411241
result = Series(ser.dt.tz_convert("UTC"), dtype=ser.dtype)
12421242
tm.assert_series_equal(result, ser)
12431243

1244-
msg = "will interpret the data as wall-times"
1245-
with tm.assert_produces_warning(FutureWarning, match=msg):
1246-
# deprecate behavior inconsistent with DatetimeIndex GH#33401
1247-
result = Series(ser.values, dtype=ser.dtype)
1248-
tm.assert_series_equal(result, ser)
1244+
# Pre-2.0 dt64 values were treated as utc, which was inconsistent
1245+
# with DatetimeIndex, which treats them as wall times, see GH#33401
1246+
result = Series(ser.values, dtype=ser.dtype)
1247+
expected = Series(ser.values).dt.tz_localize(ser.dtype.tz)
1248+
tm.assert_series_equal(result, expected)
12491249

12501250
with tm.assert_produces_warning(None):
1251-
# one suggested alternative to the deprecated usage
1251+
# one suggested alternative to the deprecated (changed in 2.0) usage
12521252
middle = Series(ser.values).dt.tz_localize("UTC")
12531253
result = middle.dt.tz_convert(ser.dtype.tz)
12541254
tm.assert_series_equal(result, ser)

0 commit comments

Comments
 (0)