From 4265cff9087e0961f98fc753aa4031610a917c0f Mon Sep 17 00:00:00 2001 From: jequinon Date: Sat, 28 Jul 2018 10:47:22 -0400 Subject: [PATCH 01/13] ENH: Added timetz attribute to DatetimeIndex. GH21358 --- pandas/_libs/tslib.pyx | 2 +- pandas/core/arrays/datetimes.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index acf6cd4b74362..37e7b6309b97b 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -73,7 +73,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(ndarray[int64_t] arr, tz=None, freq=None, diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 00d53ad82b2dc..7f84aa36cf3da 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -839,9 +839,15 @@ def time(self): timestamps = self._local_timestamps() else: timestamps = self.asi8 - return tslib.ints_to_pydatetime(timestamps, box="time") + @property + def timetz(self): + """ + Returns numpy array of datetime.time. The time part of the Timestamps. + """ + return tslib.ints_to_pydatetime(self.asi8, self.tz, box="time") + @property def date(self): """ From 29661763e9430587cf127478eb67c02c2d53fb12 Mon Sep 17 00:00:00 2001 From: jequinon Date: Sat, 28 Jul 2018 11:36:26 -0400 Subject: [PATCH 02/13] DOC: Changed method, class, and api.rst doc to add timetz to DatetimeIndex. GH21358 --- doc/source/api.rst | 1 + pandas/core/arrays/datetimes.py | 3 ++- pandas/core/indexes/datetimes.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/api.rst b/doc/source/api.rst index 9056b1f47323a..2138c93387cfd 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -1739,6 +1739,7 @@ Time/Date Components DatetimeIndex.nanosecond DatetimeIndex.date DatetimeIndex.time + DatetimeIndex.timetz DatetimeIndex.dayofyear DatetimeIndex.weekofyear DatetimeIndex.week diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 7f84aa36cf3da..907f44f204932 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -844,7 +844,8 @@ def time(self): @property def timetz(self): """ - Returns numpy array of datetime.time. The time part of the Timestamps. + 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") diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 3ee91a106f36b..219683f1139be 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 From c972de76dec32fc696934f20a44d9db607625fdf Mon Sep 17 00:00:00 2001 From: jequinon Date: Sat, 28 Jul 2018 14:05:26 -0400 Subject: [PATCH 03/13] TST: Added a test for DatetimeIndex.timetz. GH21358 --- pandas/tests/indexes/datetimes/test_timezones.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 3697d183d2fc6..5bdad529bf29a 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -731,6 +731,16 @@ def test_time_accessor(self, dtype): tm.assert_numpy_array_equal(result, expected) + @pytest.mark.parametrize('tz', [ + dateutil.tz.gettz('US/Eastern'), dateutil.tz.gettz('US/Pacific'), dateutil.tz.gettz('UTC')]) + def test_timetz_accessor(self, tz): + # GH21358 + 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") From 4473e7cdb3214cce2bc9a86d2891d813d37ff988 Mon Sep 17 00:00:00 2001 From: jequinon Date: Sun, 29 Jul 2018 13:15:18 -0400 Subject: [PATCH 04/13] Fixed style errors --- pandas/core/arrays/datetimes.py | 2 +- pandas/tests/indexes/datetimes/test_timezones.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 907f44f204932..10b97f820b7b4 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -844,7 +844,7 @@ def time(self): @property def timetz(self): """ - Returns numpy array of datetime.time also containing timezone + 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") diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 5bdad529bf29a..50dd3c1dc03d2 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -732,7 +732,8 @@ def test_time_accessor(self, dtype): tm.assert_numpy_array_equal(result, expected) @pytest.mark.parametrize('tz', [ - dateutil.tz.gettz('US/Eastern'), dateutil.tz.gettz('US/Pacific'), dateutil.tz.gettz('UTC')]) + dateutil.tz.gettz('US/Eastern'), dateutil.tz.gettz('US/Pacific'), + dateutil.tz.gettz('UTC')]) def test_timetz_accessor(self, tz): # GH21358 expected = np.array([time(10, 20, 30, tzinfo=tz), pd.NaT]) From d51e0c11e1f078334a18a140306efc9c17beb396 Mon Sep 17 00:00:00 2001 From: jequinon Date: Mon, 30 Jul 2018 11:36:09 -0400 Subject: [PATCH 05/13] Fixed spacing on test --- pandas/tests/indexes/datetimes/test_timezones.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 50dd3c1dc03d2..f9d6921f1c8a4 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -740,6 +740,7 @@ def test_timetz_accessor(self, tz): 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): From b4af058cd6797d7a704847d2f4cad03605ef314e Mon Sep 17 00:00:00 2001 From: jequinon Date: Mon, 30 Jul 2018 12:38:03 -0400 Subject: [PATCH 06/13] Added whatsnew entry --- doc/source/whatsnew/v0.24.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index d2d5d40393b62..78c576505b10b 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -180,6 +180,7 @@ Other Enhancements - :func:`~DataFrame.to_csv` and :func:`~DataFrame.to_json` now support ``compression='infer'`` to infer compression based on filename (:issue:`15008`) - :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 timetz attribute. Returns time with timezone information (:issue:`21358`) .. _whatsnew_0240.api_breaking: From 028b05d64519c9ef4253127c2a4d8fc607289fa2 Mon Sep 17 00:00:00 2001 From: jequinon Date: Mon, 30 Jul 2018 12:45:49 -0400 Subject: [PATCH 07/13] Added attribute tag to whatsnew --- doc/source/whatsnew/v0.24.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 78c576505b10b..6805b8eb2d32d 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -180,7 +180,7 @@ Other Enhancements - :func:`~DataFrame.to_csv` and :func:`~DataFrame.to_json` now support ``compression='infer'`` to infer compression based on filename (:issue:`15008`) - :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 timetz attribute. Returns time with timezone information (:issue:`21358`) +- :class:`DatetimeIndex` gained :attr:`~DatetimeIndex.timetz` attribute. Returns time with timezone information (:issue:`21358`) .. _whatsnew_0240.api_breaking: From 51855522d0650654d30ed4d009e0e398513093b4 Mon Sep 17 00:00:00 2001 From: jequinon Date: Mon, 30 Jul 2018 13:04:39 -0400 Subject: [PATCH 08/13] Fixed another spacing mistake --- pandas/core/arrays/datetimes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 10b97f820b7b4..022a0185121f9 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -839,6 +839,7 @@ def time(self): timestamps = self._local_timestamps() else: timestamps = self.asi8 + return tslib.ints_to_pydatetime(timestamps, box="time") @property From 8878613d5323d6f33f26cf6b9b49fc11a77111db Mon Sep 17 00:00:00 2001 From: jequinon Date: Tue, 31 Jul 2018 10:47:32 -0400 Subject: [PATCH 09/13] Updated docs and changed test to use tz_naive_fixture --- doc/source/timeseries.rst | 1 + doc/source/whatsnew/v0.24.0.txt | 2 +- pandas/tests/indexes/datetimes/test_timezones.py | 10 ++++++---- 3 files changed, 8 insertions(+), 5 deletions(-) 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 6805b8eb2d32d..255e891624989 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -180,7 +180,7 @@ Other Enhancements - :func:`~DataFrame.to_csv` and :func:`~DataFrame.to_json` now support ``compression='infer'`` to infer compression based on filename (:issue:`15008`) - :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 time with timezone information (:issue:`21358`) +- :class:`DatetimeIndex` gained :attr:`DatetimeIndex.timetz` attribute. Returns local time with timezone information (:issue:`21358`) .. _whatsnew_0240.api_breaking: diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 614d14c835c2d..14626d0ba33ff 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -731,11 +731,13 @@ def test_time_accessor(self, dtype): tm.assert_numpy_array_equal(result, expected) - @pytest.mark.parametrize('tz', [ - dateutil.tz.gettz('US/Eastern'), dateutil.tz.gettz('US/Pacific'), - dateutil.tz.gettz('UTC')]) - def test_timetz_accessor(self, tz): + 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) From baf8883687ff2504075bb0cab3f024288e2d1f81 Mon Sep 17 00:00:00 2001 From: jequinon Date: Tue, 31 Jul 2018 12:42:43 -0400 Subject: [PATCH 10/13] Added test comparing Timestamp.tz with datetime.tz --- .../tests/scalar/timestamp/test_timezones.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 From f94cb13556d3925182262d5d46a41b54fcdb927d Mon Sep 17 00:00:00 2001 From: jequinon Date: Tue, 31 Jul 2018 18:18:32 -0400 Subject: [PATCH 11/13] Bump because build is failing unexpectedly --- doc/source/whatsnew/v0.24.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index f57fd1accceb4..1756e54b25536 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -180,7 +180,7 @@ Other Enhancements - :func:`~DataFrame.to_csv` and :func:`~DataFrame.to_json` now support ``compression='infer'`` to infer compression based on filename (:issue:`15008`) - :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`) +- :class:`DatetimeIndex` gained :attr:`DatetimeIndex.timetz` attribute. Returns local time with timezone information. (:issue:`21358`) .. _whatsnew_0240.api_breaking: From 659643c4a8f728218cb57588b36e647dce547591 Mon Sep 17 00:00:00 2001 From: jequinon Date: Thu, 2 Aug 2018 15:43:41 -0400 Subject: [PATCH 12/13] ENH: Added timetz to Series.dt. Also added a test and changed docs --- doc/source/api.rst | 1 + pandas/core/indexes/datetimes.py | 2 +- pandas/tests/series/test_datetime_values.py | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/doc/source/api.rst b/doc/source/api.rst index 2138c93387cfd..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 diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 219683f1139be..019aad4941d26 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -261,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/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index 7a02ce3a1fb2e..bfd2f2604cc37 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_naive_fixture) + 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) From ec5a29575f0bee8565f758fea08b96440cb26f57 Mon Sep 17 00:00:00 2001 From: jequinon Date: Thu, 2 Aug 2018 16:35:10 -0400 Subject: [PATCH 13/13] Made small change dt accessor test --- pandas/tests/series/test_datetime_values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/series/test_datetime_values.py b/pandas/tests/series/test_datetime_values.py index bfd2f2604cc37..06eb525bbac56 100644 --- a/pandas/tests/series/test_datetime_values.py +++ b/pandas/tests/series/test_datetime_values.py @@ -470,7 +470,7 @@ def test_dt_timetz_accessor(self, tz_naive_fixture): tz = None dtindex = pd.DatetimeIndex(['2014-04-04 23:56', '2014-07-18 21:24', - '2015-11-22 22:14'], tz=tz_naive_fixture) + '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)])