From d6ed581eadea2375ece62e9da40d9541b0c9df65 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Wed, 30 Jan 2019 10:08:11 -0800 Subject: [PATCH 1/7] ENH: Support fold argument in Timestamp.replace --- doc/source/whatsnew/v0.25.0.rst | 2 +- pandas/_libs/tslibs/timestamps.pyx | 6 +++--- pandas/tests/scalar/timestamp/test_unary_ops.py | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index a9fa8b2174dd0..8e1fc352ba4f7 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -19,7 +19,7 @@ including other versions of pandas. Other Enhancements ^^^^^^^^^^^^^^^^^^ -- +- :meth:`Timestamp.replace` now supports the ``fold`` argument to disambiguate DST transition times (:issue:`25017`) - - diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index fe0564cb62c30..fe2dd419bdf56 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1195,7 +1195,6 @@ class Timestamp(_Timestamp): nanosecond : int, optional tzinfo : tz-convertible, optional fold : int, optional, default is 0 - added in 3.6, NotImplemented Returns ------- @@ -1252,12 +1251,13 @@ class Timestamp(_Timestamp): # see GH#18319 ts_input = _tzinfo.localize(datetime(dts.year, dts.month, dts.day, dts.hour, dts.min, dts.sec, - dts.us)) + dts.us), + is_dst=not bool(fold)) _tzinfo = ts_input.tzinfo else: ts_input = datetime(dts.year, dts.month, dts.day, dts.hour, dts.min, dts.sec, dts.us, - tzinfo=_tzinfo) + tzinfo=_tzinfo, fold=fold) ts = convert_datetime_to_tsobject(ts_input, _tzinfo) value = ts.value + (dts.ps // 1000) diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index 3f9a30d254126..b1822be38756c 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -329,6 +329,17 @@ def test_replace_dst_border(self): expected = Timestamp('2013-11-3 03:00:00', tz='America/Chicago') assert result == expected + @pytest.mark.parametrize('fold', [0, 1]) + def test_replace_dst_fold(self, fold): + # GH 25017 + tz = pytz.timezone('Europe/London') + d = datetime(2019, 10, 27, 1, 0) + d_loc = tz.localize(d, is_dst=True) + ts = Timestamp(d_loc) + result = ts.replace(minute=0, fold=fold) + expected = Timestamp(d).tz_localize(tz, ambiguous=not fold) + assert result == expected + # -------------------------------------------------------------- # Timestamp.normalize From 6a57b5a9eb191bfd788fe1e5a1be82c1699ec20a Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Wed, 30 Jan 2019 10:42:38 -0800 Subject: [PATCH 2/7] PY36 compat for fold --- pandas/_libs/tslibs/timestamps.pyx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index fe2dd419bdf56..25f3499b62ae7 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import sys import warnings from cpython cimport (PyObject_RichCompareBool, PyObject_RichCompare, @@ -43,7 +44,7 @@ from pandas._libs.tslibs.timezones import UTC # Constants _zero_time = datetime_time(0, 0) _no_input = object() - +PY36 = sys.version_info >= (3, 6) # ---------------------------------------------------------------------- @@ -1255,9 +1256,12 @@ class Timestamp(_Timestamp): is_dst=not bool(fold)) _tzinfo = ts_input.tzinfo else: - ts_input = datetime(dts.year, dts.month, dts.day, - dts.hour, dts.min, dts.sec, dts.us, - tzinfo=_tzinfo, fold=fold) + kwargs = {'year': dts.year, 'month': dts.month, 'day': dts.day, + 'hour': dts.hour, 'minute': dts.min, 'second': dts.sec, + 'microsecond': dts.us, 'tzinfo': _tzinfo} + if PY36: + kwargs['fold'] = fold + ts_input = datetime(**kwargs) ts = convert_datetime_to_tsobject(ts_input, _tzinfo) value = ts.value + (dts.ps // 1000) From c84913bed65f6070174ed0685c62b9e18644698b Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Wed, 30 Jan 2019 16:49:03 -0800 Subject: [PATCH 3/7] Add dateutil test --- pandas/tests/scalar/timestamp/test_unary_ops.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index b1822be38756c..9c81abb3aa77f 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -330,14 +330,15 @@ def test_replace_dst_border(self): assert result == expected @pytest.mark.parametrize('fold', [0, 1]) - def test_replace_dst_fold(self, fold): + @pytest.mark.parametrize('tz', ['dateutil/Europe/London', 'Europe/London']) + def test_replace_dst_fold(self, fold, tz): # GH 25017 - tz = pytz.timezone('Europe/London') - d = datetime(2019, 10, 27, 1, 0) - d_loc = tz.localize(d, is_dst=True) - ts = Timestamp(d_loc) - result = ts.replace(minute=0, fold=fold) - expected = Timestamp(d).tz_localize(tz, ambiguous=not fold) + d = datetime(2019, 10, 27, 2, 30) + ts = Timestamp(d, tz=tz) + result = ts.replace(hour=1, fold=fold) + expected = Timestamp(datetime(2019, 10, 27, 1, 30)).tz_localize( + tz, ambiguous=not fold + ) assert result == expected # -------------------------------------------------------------- From bb3de739f3c99c233ca290d3c9c1cb96d4c36c56 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Wed, 30 Jan 2019 22:56:57 -0800 Subject: [PATCH 4/7] fix NaT docstring for replace --- pandas/_libs/tslibs/nattype.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/_libs/tslibs/nattype.pyx b/pandas/_libs/tslibs/nattype.pyx index a55d15a7c4e85..c719bcb2ef135 100644 --- a/pandas/_libs/tslibs/nattype.pyx +++ b/pandas/_libs/tslibs/nattype.pyx @@ -669,7 +669,6 @@ class NaTType(_NaT): nanosecond : int, optional tzinfo : tz-convertible, optional fold : int, optional, default is 0 - added in 3.6, NotImplemented Returns ------- From 83a7d31b242a50dd03078edbaf9f0ffdb20fb38e Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Thu, 31 Jan 2019 08:50:36 -0800 Subject: [PATCH 5/7] Skipif not PY3.6 --- pandas/tests/scalar/timestamp/test_unary_ops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index 9c81abb3aa77f..dd238fadd0e4a 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -8,7 +8,7 @@ from pandas._libs.tslibs import conversion from pandas._libs.tslibs.frequencies import INVALID_FREQ_ERR_MSG -from pandas.compat import PY3 +from pandas.compat import PY3, PY36 import pandas.util._test_decorators as td from pandas import NaT, Timestamp @@ -329,6 +329,7 @@ def test_replace_dst_border(self): expected = Timestamp('2013-11-3 03:00:00', tz='America/Chicago') assert result == expected + @pytest.mark.skif(not PY36, reason='Fold not available until PY3.6') @pytest.mark.parametrize('fold', [0, 1]) @pytest.mark.parametrize('tz', ['dateutil/Europe/London', 'Europe/London']) def test_replace_dst_fold(self, fold, tz): From 0764e12f61dec56e97b86413f371e5aae0b5a6f9 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Thu, 31 Jan 2019 09:44:56 -0800 Subject: [PATCH 6/7] skipif typo --- pandas/tests/scalar/timestamp/test_unary_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/scalar/timestamp/test_unary_ops.py b/pandas/tests/scalar/timestamp/test_unary_ops.py index dd238fadd0e4a..adcf66200a672 100644 --- a/pandas/tests/scalar/timestamp/test_unary_ops.py +++ b/pandas/tests/scalar/timestamp/test_unary_ops.py @@ -329,7 +329,7 @@ def test_replace_dst_border(self): expected = Timestamp('2013-11-3 03:00:00', tz='America/Chicago') assert result == expected - @pytest.mark.skif(not PY36, reason='Fold not available until PY3.6') + @pytest.mark.skipif(not PY36, reason='Fold not available until PY3.6') @pytest.mark.parametrize('fold', [0, 1]) @pytest.mark.parametrize('tz', ['dateutil/Europe/London', 'Europe/London']) def test_replace_dst_fold(self, fold, tz): From 6cb5d02e076a664ea951d523ea634d00cce81bf0 Mon Sep 17 00:00:00 2001 From: Matt Roeschke Date: Thu, 31 Jan 2019 11:11:40 -0800 Subject: [PATCH 7/7] flake8 --- pandas/_libs/tslibs/timestamps.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 25f3499b62ae7..85d94f822056b 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -48,6 +48,7 @@ PY36 = sys.version_info >= (3, 6) # ---------------------------------------------------------------------- + def maybe_integer_op_deprecated(obj): # GH#22535 add/sub of integers and int-arrays is deprecated if obj.freq is not None: