Skip to content

Commit 3cd496b

Browse files
jamestran201-altTomAugspurger
authored andcommitted
BUG: Using DatetimeIndex.date with timezone returns incorrect date (pandas-dev#21281)
* BUG: Using DatetimeIndex.date with timezone returns incorrect date pandas-dev#21230 * Fix bug where DTI.time returns a tz-aware Time instead of tz-naive pandas-dev#21267 (cherry picked from commit a363e1a)
1 parent 9296995 commit 3cd496b

File tree

4 files changed

+50
-4
lines changed

4 files changed

+50
-4
lines changed

doc/source/whatsnew/v0.23.1.txt

+2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ Indexing
8484
- Bug in :meth:`Series.reset_index` where appropriate error was not raised with an invalid level name (:issue:`20925`)
8585
- Bug in :func:`interval_range` when ``start``/``periods`` or ``end``/``periods`` are specified with float ``start`` or ``end`` (:issue:`21161`)
8686
- Bug in :meth:`MultiIndex.set_names` where error raised for a ``MultiIndex`` with ``nlevels == 1`` (:issue:`21149`)
87+
- Bug in :attr:`DatetimeIndex.date` where an incorrect date is returned when the input date has a non-UTC timezone (:issue:`21230`)
8788
- Bug in :class:`IntervalIndex` constructors where creating an ``IntervalIndex`` from categorical data was not fully supported (:issue:`21243`, issue:`21253`)
8889
- Bug in :meth:`MultiIndex.sort_index` which was not guaranteed to sort correctly with ``level=1``; this was also causing data misalignment in particular :meth:`DataFrame.stack` operations (:issue:`20994`, :issue:`20945`, :issue:`21052`)
90+
- Bug in :attr:`DatetimeIndex.time` where given a tz-aware Timestamp, a tz-aware Time is returned instead of tz-naive (:issue:`21267`)
8991
-
9092

9193
I/O

pandas/_libs/tslib.pyx

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ cdef inline object create_time_from_ts(
7777
int64_t value, pandas_datetimestruct dts,
7878
object tz, object freq):
7979
""" convenience routine to construct a datetime.time from its parts """
80-
return time(dts.hour, dts.min, dts.sec, dts.us, tz)
80+
return time(dts.hour, dts.min, dts.sec, dts.us)
8181

8282

8383
def ints_to_pydatetime(ndarray[int64_t] arr, tz=None, freq=None,

pandas/core/indexes/datetimes.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -2032,15 +2032,33 @@ def time(self):
20322032
"""
20332033
Returns numpy array of datetime.time. The time part of the Timestamps.
20342034
"""
2035-
return libts.ints_to_pydatetime(self.asi8, self.tz, box="time")
2035+
2036+
# If the Timestamps have a timezone that is not UTC,
2037+
# convert them into their i8 representation while
2038+
# keeping their timezone and not using UTC
2039+
if (self.tz is not None and self.tz is not utc):
2040+
timestamps = self._local_timestamps()
2041+
else:
2042+
timestamps = self.asi8
2043+
2044+
return libts.ints_to_pydatetime(timestamps, box="time")
20362045

20372046
@property
20382047
def date(self):
20392048
"""
20402049
Returns numpy array of python datetime.date objects (namely, the date
20412050
part of Timestamps without timezone information).
20422051
"""
2043-
return libts.ints_to_pydatetime(self.normalize().asi8, box="date")
2052+
2053+
# If the Timestamps have a timezone that is not UTC,
2054+
# convert them into their i8 representation while
2055+
# keeping their timezone and not using UTC
2056+
if (self.tz is not None and self.tz is not utc):
2057+
timestamps = self._local_timestamps()
2058+
else:
2059+
timestamps = self.asi8
2060+
2061+
return libts.ints_to_pydatetime(timestamps, box="date")
20442062

20452063
def normalize(self):
20462064
"""

pandas/tests/indexes/datetimes/test_timezones.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"""
33
Tests for DatetimeIndex timezone-related methods
44
"""
5-
from datetime import datetime, timedelta, tzinfo
5+
from datetime import datetime, timedelta, tzinfo, date, time
66
from distutils.version import LooseVersion
77

88
import pytest
@@ -706,6 +706,32 @@ def test_join_utc_convert(self, join_type):
706706
assert isinstance(result, DatetimeIndex)
707707
assert result.tz.zone == 'UTC'
708708

709+
@pytest.mark.parametrize("dtype", [
710+
None, 'datetime64[ns, CET]',
711+
'datetime64[ns, EST]', 'datetime64[ns, UTC]'
712+
])
713+
def test_date_accessor(self, dtype):
714+
# Regression test for GH#21230
715+
expected = np.array([date(2018, 6, 4), pd.NaT])
716+
717+
index = DatetimeIndex(['2018-06-04 10:00:00', pd.NaT], dtype=dtype)
718+
result = index.date
719+
720+
tm.assert_numpy_array_equal(result, expected)
721+
722+
@pytest.mark.parametrize("dtype", [
723+
None, 'datetime64[ns, CET]',
724+
'datetime64[ns, EST]', 'datetime64[ns, UTC]'
725+
])
726+
def test_time_accessor(self, dtype):
727+
# Regression test for GH#21267
728+
expected = np.array([time(10, 20, 30), pd.NaT])
729+
730+
index = DatetimeIndex(['2018-06-04 10:20:30', pd.NaT], dtype=dtype)
731+
result = index.time
732+
733+
tm.assert_numpy_array_equal(result, expected)
734+
709735
def test_dti_drop_dont_lose_tz(self):
710736
# GH#2621
711737
ind = date_range("2012-12-01", periods=10, tz="utc")

0 commit comments

Comments
 (0)