Skip to content

Commit c2cdeaf

Browse files
authored
BUG: DatetimeIndex with dayfirst/yearfirst and tz (#55813)
* BUG: DatetimeIndex with dayfirst/yearfirst and tz * GH ref
1 parent 5f5ee75 commit c2cdeaf

File tree

8 files changed

+51
-23
lines changed

8 files changed

+51
-23
lines changed

doc/source/whatsnew/v2.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ Categorical
343343

344344
Datetimelike
345345
^^^^^^^^^^^^
346+
- Bug in :class:`DatetimeIndex` construction when passing both a ``tz`` and either ``dayfirst`` or ``yearfirst`` ignoring dayfirst/yearfirst (:issue:`55813`)
346347
- Bug in :class:`DatetimeIndex` when passing an object-dtype ndarray of float objects and a ``tz`` incorrectly localizing the result (:issue:`55780`)
347348
- Bug in :func:`concat` raising ``AttributeError`` when concatenating all-NA DataFrame with :class:`DatetimeTZDtype` dtype DataFrame. (:issue:`52093`)
348349
- Bug in :func:`to_datetime` and :class:`DatetimeIndex` when passing a list of mixed-string-and-numeric types incorrectly raising (:issue:`55780`)

pandas/_libs/tslib.pyi

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,9 @@ def array_to_datetime(
2929
# returned ndarray may be object dtype or datetime64[ns]
3030

3131
def array_to_datetime_with_tz(
32-
values: npt.NDArray[np.object_], tz: tzinfo, creso: int
32+
values: npt.NDArray[np.object_],
33+
tz: tzinfo,
34+
dayfirst: bool,
35+
yearfirst: bool,
36+
creso: int,
3337
) -> npt.NDArray[np.int64]: ...

pandas/_libs/tslib.pyx

+11-13
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ from pandas._libs.tslibs.conversion cimport (
6161
_TSObject,
6262
cast_from_unit,
6363
convert_str_to_tsobject,
64+
convert_to_tsobject,
6465
get_datetime64_nanos,
6566
parse_pydatetime,
6667
)
@@ -673,7 +674,9 @@ cdef _array_to_datetime_object(
673674
return oresult_nd, None
674675

675676

676-
def array_to_datetime_with_tz(ndarray values, tzinfo tz, NPY_DATETIMEUNIT creso):
677+
def array_to_datetime_with_tz(
678+
ndarray values, tzinfo tz, bint dayfirst, bint yearfirst, NPY_DATETIMEUNIT creso
679+
):
677680
"""
678681
Vectorized analogue to pd.Timestamp(value, tz=tz)
679682
@@ -689,7 +692,7 @@ def array_to_datetime_with_tz(ndarray values, tzinfo tz, NPY_DATETIMEUNIT creso)
689692
Py_ssize_t i, n = values.size
690693
object item
691694
int64_t ival
692-
datetime ts
695+
_TSObject tsobj
693696

694697
for i in range(n):
695698
# Analogous to `item = values[i]`
@@ -700,17 +703,12 @@ def array_to_datetime_with_tz(ndarray values, tzinfo tz, NPY_DATETIMEUNIT creso)
700703
ival = NPY_NAT
701704

702705
else:
703-
if PyDateTime_Check(item) and item.tzinfo is not None:
704-
# We can't call Timestamp constructor with a tz arg, have to
705-
# do 2-step
706-
ts = Timestamp(item).tz_convert(tz)
707-
else:
708-
ts = Timestamp(item, tz=tz)
709-
if ts is NaT:
710-
ival = NPY_NAT
711-
else:
712-
ts = (<_Timestamp>ts)._as_creso(creso)
713-
ival = ts._value
706+
tsobj = convert_to_tsobject(
707+
item, tz=tz, unit="ns", dayfirst=dayfirst, yearfirst=yearfirst, nanos=0
708+
)
709+
if tsobj.value != NPY_NAT:
710+
tsobj.ensure_reso(creso, item, round_ok=True)
711+
ival = tsobj.value
714712

715713
# Analogous to: result[i] = ival
716714
(<int64_t*>cnp.PyArray_MultiIter_DATA(mi, 0))[0] = ival

pandas/_libs/tslibs/conversion.pxd

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ cdef class _TSObject:
2424
bint fold
2525
NPY_DATETIMEUNIT creso
2626

27-
cdef int64_t ensure_reso(self, NPY_DATETIMEUNIT creso, str val=*) except? -1
27+
cdef int64_t ensure_reso(
28+
self, NPY_DATETIMEUNIT creso, val=*, bint round_ok=*
29+
) except? -1
2830

2931

3032
cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit,

pandas/_libs/tslibs/conversion.pyx

+11-2
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,14 @@ cdef class _TSObject:
235235
self.fold = 0
236236
self.creso = NPY_FR_ns # default value
237237

238-
cdef int64_t ensure_reso(self, NPY_DATETIMEUNIT creso, str val=None) except? -1:
238+
cdef int64_t ensure_reso(
239+
self, NPY_DATETIMEUNIT creso, val=None, bint round_ok=False
240+
) except? -1:
239241
if self.creso != creso:
240242
try:
241-
self.value = convert_reso(self.value, self.creso, creso, False)
243+
self.value = convert_reso(
244+
self.value, self.creso, creso, round_ok=round_ok
245+
)
242246
except OverflowError as err:
243247
if val is not None:
244248
raise OutOfBoundsDatetime(
@@ -283,6 +287,11 @@ cdef _TSObject convert_to_tsobject(object ts, tzinfo tz, str unit,
283287
obj.value = get_datetime64_nanos(ts, reso)
284288
if obj.value != NPY_NAT:
285289
pandas_datetime_to_datetimestruct(obj.value, reso, &obj.dts)
290+
if tz is not None:
291+
# GH#24559, GH#42288 We treat np.datetime64 objects as *wall* times
292+
obj.value = tz_localize_to_utc_single(
293+
obj.value, tz, ambiguous="raise", nonexistent=None, creso=reso
294+
)
286295
elif is_integer_object(ts):
287296
try:
288297
ts = <int64_t>ts

pandas/_libs/tslibs/timestamps.pyx

-4
Original file line numberDiff line numberDiff line change
@@ -1873,10 +1873,6 @@ class Timestamp(_Timestamp):
18731873
"the tz parameter. Use tz_convert instead.")
18741874

18751875
tzobj = maybe_get_tz(tz)
1876-
if tzobj is not None and is_datetime64_object(ts_input):
1877-
# GH#24559, GH#42288 As of 2.0 we treat datetime64 as
1878-
# wall-time (consistent with DatetimeIndex)
1879-
return cls(ts_input).tz_localize(tzobj)
18801876

18811877
if nanosecond is None:
18821878
nanosecond = 0

pandas/core/arrays/datetimes.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -2240,10 +2240,13 @@ def _sequence_to_dt64(
22402240
if lib.infer_dtype(data, skipna=False) == "integer":
22412241
data = data.astype(np.int64)
22422242
elif tz is not None and ambiguous == "raise":
2243-
# TODO: yearfirst/dayfirst/etc?
22442243
obj_data = np.asarray(data, dtype=object)
22452244
i8data = tslib.array_to_datetime_with_tz(
2246-
obj_data, tz, abbrev_to_npy_unit(out_unit)
2245+
obj_data,
2246+
tz=tz,
2247+
dayfirst=dayfirst,
2248+
yearfirst=yearfirst,
2249+
creso=abbrev_to_npy_unit(out_unit),
22472250
)
22482251
return i8data.view(out_dtype), tz, None
22492252
else:

pandas/tests/indexes/datetimes/test_constructors.py

+15
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,21 @@ def test_datetimeindex_constructor_misc(self):
12471247
assert len(idx1) == len(idx2)
12481248
assert idx1.freq == idx2.freq
12491249

1250+
def test_dti_constructor_object_dtype_dayfirst_yearfirst_with_tz(self):
1251+
# GH#55813
1252+
val = "5/10/16"
1253+
1254+
dfirst = Timestamp(2016, 10, 5, tz="US/Pacific")
1255+
yfirst = Timestamp(2005, 10, 16, tz="US/Pacific")
1256+
1257+
result1 = DatetimeIndex([val], tz="US/Pacific", dayfirst=True)
1258+
expected1 = DatetimeIndex([dfirst])
1259+
tm.assert_index_equal(result1, expected1)
1260+
1261+
result2 = DatetimeIndex([val], tz="US/Pacific", yearfirst=True)
1262+
expected2 = DatetimeIndex([yfirst])
1263+
tm.assert_index_equal(result2, expected2)
1264+
12501265
def test_pass_datetimeindex_to_index(self):
12511266
# Bugs in #1396
12521267
rng = date_range("1/1/2000", "3/1/2000")

0 commit comments

Comments
 (0)