diff --git a/doc/source/api.rst b/doc/source/api.rst index 9056b1f47323a..551b3eff10fa0 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -545,6 +545,7 @@ These can be accessed like ``Series.dt.``. Series.dt.date Series.dt.time + Series.dt.timetz Series.dt.year Series.dt.month Series.dt.day @@ -1739,6 +1740,7 @@ Time/Date Components DatetimeIndex.nanosecond DatetimeIndex.date DatetimeIndex.time + DatetimeIndex.timetz DatetimeIndex.dayofyear DatetimeIndex.weekofyear DatetimeIndex.week diff --git a/doc/source/timeseries.rst b/doc/source/timeseries.rst index 9e01296d9c9c7..b7771436f8e55 100644 --- a/doc/source/timeseries.rst +++ b/doc/source/timeseries.rst @@ -724,6 +724,7 @@ There are several time/date properties that one can access from ``Timestamp`` or nanosecond,"The nanoseconds of the datetime" date,"Returns datetime.date (does not contain timezone information)" time,"Returns datetime.time (does not contain timezone information)" + timetz,"Returns datetime.time as local time with timezone information" dayofyear,"The ordinal day of year" weekofyear,"The week ordinal of the year" week,"The week ordinal of the year" diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 5c15c7b6a742f..9426a0b119be0 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -181,6 +181,7 @@ Other Enhancements The default compression for ``to_csv``, ``to_json``, and ``to_pickle`` methods has been updated to ``'infer'`` (:issue:`22004`). - :func:`to_timedelta` now supports iso-formated timedelta strings (:issue:`21877`) - :class:`Series` and :class:`DataFrame` now support :class:`Iterable` in constructor (:issue:`2193`) +- :class:`DatetimeIndex` gained :attr:`DatetimeIndex.timetz` attribute. Returns local time with timezone information. (:issue:`21358`) .. _whatsnew_0240.api_breaking: diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index 12089f1617f5d..04e039a9fc2c9 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -72,7 +72,7 @@ cdef inline object create_time_from_ts( int64_t value, npy_datetimestruct dts, object tz, object freq): """ convenience routine to construct a datetime.time from its parts """ - return time(dts.hour, dts.min, dts.sec, dts.us) + return time(dts.hour, dts.min, dts.sec, dts.us, tz) def ints_to_pydatetime(int64_t[:] arr, tz=None, freq=None, box="datetime"): diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index ee0677f760705..1dd34cdf73ab5 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -856,6 +856,14 @@ def time(self): return tslib.ints_to_pydatetime(timestamps, box="time") + @property + def timetz(self): + """ + Returns numpy array of datetime.time also containing timezone + information. The time part of the Timestamps. + """ + return tslib.ints_to_pydatetime(self.asi8, self.tz, box="time") + @property def date(self): """ diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 3ee91a106f36b..019aad4941d26 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -170,6 +170,7 @@ class DatetimeIndex(DatetimeArrayMixin, DatelikeOps, TimelikeOps, nanosecond date time + timetz dayofyear weekofyear week @@ -260,7 +261,7 @@ def _add_comparison_methods(cls): 'dayofyear', 'quarter', 'days_in_month', 'daysinmonth', 'microsecond', 'nanosecond'] - _other_ops = ['date', 'time'] + _other_ops = ['date', 'time', 'timetz'] _datetimelike_ops = _field_ops + _object_ops + _bool_ops + _other_ops _datetimelike_methods = ['to_period', 'tz_localize', 'tz_convert', diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index b83645414c8ff..95531b2d7a7ae 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -731,6 +731,20 @@ def test_time_accessor(self, dtype): tm.assert_numpy_array_equal(result, expected) + def test_timetz_accessor(self, tz_naive_fixture): + # GH21358 + if tz_naive_fixture is not None: + tz = dateutil.tz.gettz(tz_naive_fixture) + else: + tz = None + + expected = np.array([time(10, 20, 30, tzinfo=tz), pd.NaT]) + + index = DatetimeIndex(['2018-06-04 10:20:30', pd.NaT], tz=tz) + result = index.timetz + + tm.assert_numpy_array_equal(result, expected) + def test_dti_drop_dont_lose_tz(self): # GH#2621 ind = date_range("2012-12-01", periods=10, tz="utc") diff --git a/pandas/tests/scalar/timestamp/test_timezones.py b/pandas/tests/scalar/timestamp/test_timezones.py index cd0379e7af1a3..8cebfafeae82a 100644 --- a/pandas/tests/scalar/timestamp/test_timezones.py +++ b/pandas/tests/scalar/timestamp/test_timezones.py @@ -2,7 +2,7 @@ """ Tests for Timestamp timezone-related methods """ -from datetime import date, timedelta +from datetime import datetime, date, timedelta from distutils.version import LooseVersion import pytest @@ -290,3 +290,20 @@ def test_timestamp_add_timedelta_push_over_dst_boundary(self, tz): expected = Timestamp('3/11/2012 05:00', tz=tz) assert result == expected + + def test_timestamp_timetz_equivalent_with_datetime_tz(self, + tz_naive_fixture): + # GH21358 + if tz_naive_fixture is not None: + tz = dateutil.tz.gettz(tz_naive_fixture) + else: + tz = None + + stamp = Timestamp('2018-06-04 10:20:30', tz=tz) + _datetime = datetime(2018, 6, 4, hour=10, + minute=20, second=30, tzinfo=tz) + + result = stamp.timetz() + expected = _datetime.timetz() + + assert result == expected diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index 7a02ce3a1fb2e..06eb525bbac56 100644 --- a/pandas/tests/series/test_datetime_values.py +++ b/pandas/tests/series/test_datetime_values.py @@ -5,7 +5,7 @@ import calendar import pytest -from datetime import datetime, date +from datetime import datetime, time, date import numpy as np import pandas as pd @@ -16,6 +16,8 @@ PeriodIndex, DatetimeIndex, TimedeltaIndex) import pandas.core.common as com +import dateutil + from pandas.util.testing import assert_series_equal import pandas.util.testing as tm @@ -459,3 +461,18 @@ def test_datetime_understood(self): expected = pd.Series(pd.to_datetime([ '2011-12-26', '2011-12-27', '2011-12-28'])) tm.assert_series_equal(result, expected) + + def test_dt_timetz_accessor(self, tz_naive_fixture): + # GH21358 + if tz_naive_fixture is not None: + tz = dateutil.tz.gettz(tz_naive_fixture) + else: + tz = None + + dtindex = pd.DatetimeIndex(['2014-04-04 23:56', '2014-07-18 21:24', + '2015-11-22 22:14'], tz=tz) + s = Series(dtindex) + expected = Series([time(23, 56, tzinfo=tz), time(21, 24, tzinfo=tz), + time(22, 14, tzinfo=tz)]) + result = s.dt.timetz + tm.assert_series_equal(result, expected)