Skip to content

Commit 458ccab

Browse files
jbrockmendelPingviinituutti
authored andcommitted
implement deprecation portion of pandas-dev#23675 (pandas-dev#23937)
1 parent 727e3c9 commit 458ccab

File tree

5 files changed

+157
-28
lines changed

5 files changed

+157
-28
lines changed

doc/source/whatsnew/v0.24.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,7 @@ Deprecations
10431043
`use_threads` to reflect the changes in pyarrow 0.11.0. (:issue:`23053`)
10441044
- :func:`pandas.read_excel` has deprecated accepting ``usecols`` as an integer. Please pass in a list of ints from 0 to ``usecols`` inclusive instead (:issue:`23527`)
10451045
- Constructing a :class:`TimedeltaIndex` from data with ``datetime64``-dtyped data is deprecated, will raise ``TypeError`` in a future version (:issue:`23539`)
1046+
- Constructing a :class:`DatetimeIndex` from data with ``timedelta64``-dtyped data is deprecated, will raise ``TypeError`` in a future version (:issue:`23675`)
10461047
- The ``keep_tz=False`` option (the default) of the ``keep_tz`` keyword of
10471048
:meth:`DatetimeIndex.to_series` is deprecated (:issue:`17832`).
10481049
- Timezone converting a tz-aware ``datetime.datetime`` or :class:`Timestamp` with :class:`Timestamp` and the ``tz`` argument is now deprecated. Instead, use :meth:`Timestamp.tz_convert` (:issue:`23579`)

pandas/core/arrays/datetimes.py

+80-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
from pandas.util._decorators import Appender, cache_readonly
1616

1717
from pandas.core.dtypes.common import (
18-
_NS_DTYPE, is_datetime64_dtype, is_datetime64tz_dtype, is_int64_dtype,
19-
is_object_dtype)
18+
_NS_DTYPE, is_datetime64_dtype, is_datetime64tz_dtype, is_extension_type,
19+
is_float_dtype, is_int64_dtype, is_object_dtype, is_period_dtype,
20+
is_timedelta64_dtype)
2021
from pandas.core.dtypes.dtypes import DatetimeTZDtype
2122
from pandas.core.dtypes.generic import ABCIndexClass, ABCSeries
2223
from pandas.core.dtypes.missing import isna
@@ -1421,6 +1422,83 @@ def to_julian_date(self):
14211422
DatetimeArrayMixin._add_datetimelike_methods()
14221423

14231424

1425+
# -------------------------------------------------------------------
1426+
# Constructor Helpers
1427+
1428+
def maybe_infer_tz(tz, inferred_tz):
1429+
"""
1430+
If a timezone is inferred from data, check that it is compatible with
1431+
the user-provided timezone, if any.
1432+
1433+
Parameters
1434+
----------
1435+
tz : tzinfo or None
1436+
inferred_tz : tzinfo or None
1437+
1438+
Returns
1439+
-------
1440+
tz : tzinfo or None
1441+
1442+
Raises
1443+
------
1444+
TypeError : if both timezones are present but do not match
1445+
"""
1446+
if tz is None:
1447+
tz = inferred_tz
1448+
elif inferred_tz is None:
1449+
pass
1450+
elif not timezones.tz_compare(tz, inferred_tz):
1451+
raise TypeError('data is already tz-aware {inferred_tz}, unable to '
1452+
'set specified tz: {tz}'
1453+
.format(inferred_tz=inferred_tz, tz=tz))
1454+
return tz
1455+
1456+
1457+
def maybe_convert_dtype(data, copy):
1458+
"""
1459+
Convert data based on dtype conventions, issuing deprecation warnings
1460+
or errors where appropriate.
1461+
Parameters
1462+
----------
1463+
data : np.ndarray or pd.Index
1464+
copy : bool
1465+
Returns
1466+
-------
1467+
data : np.ndarray or pd.Index
1468+
copy : bool
1469+
Raises
1470+
------
1471+
TypeError : PeriodDType data is passed
1472+
"""
1473+
if is_float_dtype(data):
1474+
# Note: we must cast to datetime64[ns] here in order to treat these
1475+
# as wall-times instead of UTC timestamps.
1476+
data = data.astype(_NS_DTYPE)
1477+
copy = False
1478+
# TODO: deprecate this behavior to instead treat symmetrically
1479+
# with integer dtypes. See discussion in GH#23675
1480+
1481+
elif is_timedelta64_dtype(data):
1482+
warnings.warn("Passing timedelta64-dtype data is deprecated, will "
1483+
"raise a TypeError in a future version",
1484+
FutureWarning, stacklevel=3)
1485+
data = data.view(_NS_DTYPE)
1486+
1487+
elif is_period_dtype(data):
1488+
# Note: without explicitly raising here, PeriondIndex
1489+
# test_setops.test_join_does_not_recur fails
1490+
raise TypeError("Passing PeriodDtype data is invalid. "
1491+
"Use `data.to_timestamp()` instead")
1492+
1493+
elif is_extension_type(data) and not is_datetime64tz_dtype(data):
1494+
# Includes categorical
1495+
# TODO: We have no tests for these
1496+
data = np.array(data, dtype=np.object_)
1497+
copy = False
1498+
1499+
return data, copy
1500+
1501+
14241502
def _generate_regular_range(cls, start, end, periods, freq):
14251503
"""
14261504
Generate a range of dates with the spans between dates described by

pandas/core/indexes/datetimes.py

+25-25
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@
1616

1717
from pandas.core.dtypes.common import (
1818
_INT64_DTYPE, _NS_DTYPE, ensure_int64, is_datetime64_dtype,
19-
is_datetime64_ns_dtype, is_datetimetz, is_dtype_equal, is_float,
20-
is_integer, is_integer_dtype, is_list_like, is_period_dtype, is_scalar,
21-
is_string_like, pandas_dtype)
19+
is_datetime64_ns_dtype, is_datetime64tz_dtype, is_datetimetz,
20+
is_dtype_equal, is_float, is_integer, is_integer_dtype, is_list_like,
21+
is_period_dtype, is_scalar, is_string_like, pandas_dtype)
2222
import pandas.core.dtypes.concat as _concat
2323
from pandas.core.dtypes.generic import ABCSeries
2424
from pandas.core.dtypes.missing import isna
2525

2626
from pandas.core.arrays import datetimelike as dtl
2727
from pandas.core.arrays.datetimes import (
28-
DatetimeArrayMixin as DatetimeArray, _to_m8)
28+
DatetimeArrayMixin as DatetimeArray, _to_m8, maybe_convert_dtype,
29+
maybe_infer_tz)
2930
from pandas.core.base import _shared_docs
3031
import pandas.core.common as com
3132
from pandas.core.indexes.base import Index, _index_shared_docs
@@ -246,50 +247,49 @@ def __new__(cls, data=None,
246247
name = data.name
247248

248249
freq, freq_infer = dtl.maybe_infer_freq(freq)
250+
if freq is None and hasattr(data, "freq"):
251+
# i.e. DatetimeArray/Index
252+
freq = data.freq
253+
verify_integrity = False
249254

250255
# if dtype has an embedded tz, capture it
251256
tz = dtl.validate_tz_from_dtype(dtype, tz)
252257

253-
if not isinstance(data, (np.ndarray, Index, ABCSeries, DatetimeArray)):
254-
# other iterable of some kind
255-
if not isinstance(data, (list, tuple)):
258+
if not hasattr(data, "dtype"):
259+
# e.g. list, tuple
260+
if np.ndim(data) == 0:
261+
# i.e. generator
256262
data = list(data)
257-
data = np.asarray(data, dtype='O')
263+
data = np.asarray(data)
264+
copy = False
258265
elif isinstance(data, ABCSeries):
259266
data = data._values
260267

261-
# data must be Index or np.ndarray here
268+
# By this point we are assured to have either a numpy array or Index
269+
data, copy = maybe_convert_dtype(data, copy)
270+
262271
if not (is_datetime64_dtype(data) or is_datetimetz(data) or
263272
is_integer_dtype(data) or lib.infer_dtype(data) == 'integer'):
264273
data = tools.to_datetime(data, dayfirst=dayfirst,
265274
yearfirst=yearfirst)
266275

267-
if isinstance(data, DatetimeArray):
268-
if tz is None:
269-
tz = data.tz
270-
elif data.tz is None:
271-
data = data.tz_localize(tz, ambiguous=ambiguous)
272-
else:
273-
# the tz's must match
274-
if not timezones.tz_compare(tz, data.tz):
275-
msg = ('data is already tz-aware {0}, unable to '
276-
'set specified tz: {1}')
277-
raise TypeError(msg.format(data.tz, tz))
278-
276+
if is_datetime64tz_dtype(data):
277+
tz = maybe_infer_tz(tz, data.tz)
279278
subarr = data._data
280279

281-
if freq is None:
282-
freq = data.freq
283-
verify_integrity = False
284-
elif issubclass(data.dtype.type, np.datetime64):
280+
elif is_datetime64_dtype(data):
281+
# tz-naive DatetimeArray/Index or ndarray[datetime64]
282+
data = getattr(data, "_data", data)
285283
if data.dtype != _NS_DTYPE:
286284
data = conversion.ensure_datetime64ns(data)
285+
287286
if tz is not None:
288287
# Convert tz-naive to UTC
289288
tz = timezones.maybe_get_tz(tz)
290289
data = conversion.tz_localize_to_utc(data.view('i8'), tz,
291290
ambiguous=ambiguous)
292291
subarr = data.view(_NS_DTYPE)
292+
293293
else:
294294
# must be integer dtype otherwise
295295
# assume this data are epoch timestamps

pandas/core/tools/datetimes.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None,
171171
- ndarray of Timestamps if box=False
172172
"""
173173
from pandas import DatetimeIndex
174+
from pandas.core.arrays.datetimes import maybe_convert_dtype
175+
174176
if isinstance(arg, (list, tuple)):
175177
arg = np.array(arg, dtype='O')
176178

@@ -208,6 +210,11 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None,
208210
raise TypeError('arg must be a string, datetime, list, tuple, '
209211
'1-d array, or Series')
210212

213+
# warn if passing timedelta64, raise for PeriodDtype
214+
# NB: this must come after unit transformation
215+
orig_arg = arg
216+
arg, _ = maybe_convert_dtype(arg, copy=False)
217+
211218
arg = ensure_object(arg)
212219
require_iso8601 = False
213220

@@ -231,7 +238,10 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None,
231238
# shortcut formatting here
232239
if format == '%Y%m%d':
233240
try:
234-
result = _attempt_YYYYMMDD(arg, errors=errors)
241+
# pass orig_arg as float-dtype may have been converted to
242+
# datetime64[ns]
243+
orig_arg = ensure_object(orig_arg)
244+
result = _attempt_YYYYMMDD(orig_arg, errors=errors)
235245
except (ValueError, TypeError, tslibs.OutOfBoundsDatetime):
236246
raise ValueError("cannot convert the input to "
237247
"'%Y%m%d' date format")

pandas/tests/indexes/datetimes/test_construction.py

+40
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,51 @@
1414
from pandas import (
1515
DatetimeIndex, Index, Timestamp, date_range, datetime, offsets,
1616
to_datetime)
17+
from pandas.core.arrays import period_array
1718
import pandas.util.testing as tm
1819

1920

2021
class TestDatetimeIndex(object):
2122

23+
def test_dti_with_period_data_raises(self):
24+
# GH#23675
25+
data = pd.PeriodIndex(['2016Q1', '2016Q2'], freq='Q')
26+
27+
with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
28+
DatetimeIndex(data)
29+
30+
with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
31+
to_datetime(data)
32+
33+
with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
34+
DatetimeIndex(period_array(data))
35+
36+
with pytest.raises(TypeError, match="PeriodDtype data is invalid"):
37+
to_datetime(period_array(data))
38+
39+
def test_dti_with_timedelta64_data_deprecation(self):
40+
# GH#23675
41+
data = np.array([0], dtype='m8[ns]')
42+
with tm.assert_produces_warning(FutureWarning):
43+
result = DatetimeIndex(data)
44+
45+
assert result[0] == Timestamp('1970-01-01')
46+
47+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
48+
result = to_datetime(data)
49+
50+
assert result[0] == Timestamp('1970-01-01')
51+
52+
with tm.assert_produces_warning(FutureWarning):
53+
result = DatetimeIndex(pd.TimedeltaIndex(data))
54+
55+
assert result[0] == Timestamp('1970-01-01')
56+
57+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
58+
result = to_datetime(pd.TimedeltaIndex(data))
59+
60+
assert result[0] == Timestamp('1970-01-01')
61+
2262
def test_construction_caching(self):
2363

2464
df = pd.DataFrame({'dt': pd.date_range('20130101', periods=3),

0 commit comments

Comments
 (0)