Skip to content

Commit e7991b3

Browse files
jbrockmendeljreback
authored andcommitted
REF/DEPR: DatetimeIndex constructor (#23675)
1 parent 367efb2 commit e7991b3

File tree

3 files changed

+114
-28
lines changed

3 files changed

+114
-28
lines changed

pandas/core/arrays/datetimes.py

+77
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,83 @@ def maybe_convert_dtype(data, copy):
15031503
return data, copy
15041504

15051505

1506+
def objects_to_datetime64ns(data, dayfirst, yearfirst,
1507+
utc=False, errors="raise",
1508+
require_iso8601=False, allow_object=False):
1509+
"""
1510+
Convert data to array of timestamps.
1511+
1512+
Parameters
1513+
----------
1514+
data : np.ndarray[object]
1515+
dayfirst : bool
1516+
yearfirst : bool
1517+
utc : bool, default False
1518+
Whether to convert timezone-aware timestamps to UTC
1519+
errors : {'raise', 'ignore', 'coerce'}
1520+
allow_object : bool
1521+
Whether to return an object-dtype ndarray instead of raising if the
1522+
data contains more than one timezone.
1523+
1524+
Returns
1525+
-------
1526+
result : ndarray
1527+
np.int64 dtype if returned values represent UTC timestamps
1528+
np.datetime64[ns] if returned values represent wall times
1529+
object if mixed timezones
1530+
inferred_tz : tzinfo or None
1531+
1532+
Raises
1533+
------
1534+
ValueError : if data cannot be converted to datetimes
1535+
"""
1536+
assert errors in ["raise", "ignore", "coerce"]
1537+
1538+
# if str-dtype, convert
1539+
data = np.array(data, copy=False, dtype=np.object_)
1540+
1541+
try:
1542+
result, tz_parsed = tslib.array_to_datetime(
1543+
data,
1544+
errors=errors,
1545+
utc=utc,
1546+
dayfirst=dayfirst,
1547+
yearfirst=yearfirst,
1548+
require_iso8601=require_iso8601
1549+
)
1550+
except ValueError as e:
1551+
try:
1552+
values, tz_parsed = conversion.datetime_to_datetime64(data)
1553+
# If tzaware, these values represent unix timestamps, so we
1554+
# return them as i8 to distinguish from wall times
1555+
return values.view('i8'), tz_parsed
1556+
except (ValueError, TypeError):
1557+
raise e
1558+
1559+
if tz_parsed is not None:
1560+
# We can take a shortcut since the datetime64 numpy array
1561+
# is in UTC
1562+
# Return i8 values to denote unix timestamps
1563+
return result.view('i8'), tz_parsed
1564+
elif is_datetime64_dtype(result):
1565+
# returning M8[ns] denotes wall-times; since tz is None
1566+
# the distinction is a thin one
1567+
return result, tz_parsed
1568+
elif is_object_dtype(result):
1569+
# GH#23675 when called via `pd.to_datetime`, returning an object-dtype
1570+
# array is allowed. When called via `pd.DatetimeIndex`, we can
1571+
# only accept datetime64 dtype, so raise TypeError if object-dtype
1572+
# is returned, as that indicates the values can be recognized as
1573+
# datetimes but they have conflicting timezones/awareness
1574+
if allow_object:
1575+
return result, tz_parsed
1576+
raise TypeError(result)
1577+
else: # pragma: no cover
1578+
# GH#23675 this TypeError should never be hit, whereas the TypeError
1579+
# in the object-dtype branch above is reachable.
1580+
raise TypeError(result)
1581+
1582+
15061583
def _generate_regular_range(cls, start, end, periods, freq):
15071584
"""
15081585
Generate a range of dates with the spans between dates described by

pandas/core/indexes/datetimes.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717
from pandas.core.dtypes.common import (
1818
_INT64_DTYPE, _NS_DTYPE, ensure_int64, is_datetime64_dtype,
1919
is_datetime64_ns_dtype, is_datetime64tz_dtype, 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)
20+
is_integer, is_list_like, is_object_dtype, is_period_dtype, is_scalar,
21+
is_string_dtype, 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 (
2828
DatetimeArrayMixin as DatetimeArray, _to_m8, maybe_convert_dtype,
29-
maybe_infer_tz)
29+
maybe_infer_tz, objects_to_datetime64ns)
3030
from pandas.core.base import _shared_docs
3131
import pandas.core.common as com
3232
from pandas.core.indexes.base import Index, _index_shared_docs
@@ -281,10 +281,19 @@ def __new__(cls, data=None,
281281

282282
# By this point we are assured to have either a numpy array or Index
283283
data, copy = maybe_convert_dtype(data, copy)
284-
if not (is_datetime64_dtype(data) or is_datetime64tz_dtype(data) or
285-
is_integer_dtype(data) or lib.infer_dtype(data) == 'integer'):
286-
data = tools.to_datetime(data, dayfirst=dayfirst,
287-
yearfirst=yearfirst)
284+
285+
if is_object_dtype(data) or is_string_dtype(data):
286+
# TODO: We do not have tests specific to string-dtypes,
287+
# also complex or categorical or other extension
288+
copy = False
289+
if lib.infer_dtype(data) == 'integer':
290+
data = data.astype(np.int64)
291+
else:
292+
# data comes back here as either i8 to denote UTC timestamps
293+
# or M8[ns] to denote wall times
294+
data, inferred_tz = objects_to_datetime64ns(
295+
data, dayfirst=dayfirst, yearfirst=yearfirst)
296+
tz = maybe_infer_tz(tz, inferred_tz)
288297

289298
if is_datetime64tz_dtype(data):
290299
tz = maybe_infer_tz(tz, data.tz)

pandas/core/tools/datetimes.py

+21-21
Original file line numberDiff line numberDiff line change
@@ -171,7 +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
174+
from pandas.core.arrays.datetimes import (
175+
maybe_convert_dtype, objects_to_datetime64ns)
175176

176177
if isinstance(arg, (list, tuple)):
177178
arg = np.array(arg, dtype='O')
@@ -233,8 +234,9 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None,
233234

234235
tz_parsed = None
235236
result = None
236-
try:
237-
if format is not None:
237+
238+
if format is not None:
239+
try:
238240
# shortcut formatting here
239241
if format == '%Y%m%d':
240242
try:
@@ -266,24 +268,22 @@ def _convert_listlike_datetimes(arg, box, format, name=None, tz=None,
266268
if errors == 'raise':
267269
raise
268270
result = arg
269-
270-
if result is None:
271-
assert format is None or infer_datetime_format
272-
result, tz_parsed = tslib.array_to_datetime(
273-
arg,
274-
errors=errors,
275-
utc=tz == 'utc',
276-
dayfirst=dayfirst,
277-
yearfirst=yearfirst,
278-
require_iso8601=require_iso8601
279-
)
280-
except ValueError as e:
281-
# Fallback to try to convert datetime objects
282-
try:
283-
values, tz = conversion.datetime_to_datetime64(arg)
284-
return DatetimeIndex._simple_new(values, name=name, tz=tz)
285-
except (ValueError, TypeError):
286-
raise e
271+
except ValueError as e:
272+
# Fallback to try to convert datetime objects if timezone-aware
273+
# datetime objects are found without passing `utc=True`
274+
try:
275+
values, tz = conversion.datetime_to_datetime64(arg)
276+
return DatetimeIndex._simple_new(values, name=name, tz=tz)
277+
except (ValueError, TypeError):
278+
raise e
279+
280+
if result is None:
281+
assert format is None or infer_datetime_format
282+
utc = tz == 'utc'
283+
result, tz_parsed = objects_to_datetime64ns(
284+
arg, dayfirst=dayfirst, yearfirst=yearfirst,
285+
utc=utc, errors=errors, require_iso8601=require_iso8601,
286+
allow_object=True)
287287

288288
if tz_parsed is not None:
289289
if box:

0 commit comments

Comments
 (0)