From 7aa12cc7301da02c636fc73b551597ebe15936dc Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 12 Dec 2022 16:40:54 -0800 Subject: [PATCH 1/2] ENH: handle nonexistent shift for ZoneInfo --- pandas/_libs/tslibs/tzconversion.pyx | 26 ++++++++++++++----- .../indexes/datetimes/test_constructors.py | 9 +------ .../tests/series/methods/test_tz_localize.py | 9 ------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/pandas/_libs/tslibs/tzconversion.pyx b/pandas/_libs/tslibs/tzconversion.pyx index ad894f70f0cb2..d722dc9bede79 100644 --- a/pandas/_libs/tslibs/tzconversion.pyx +++ b/pandas/_libs/tslibs/tzconversion.pyx @@ -240,6 +240,8 @@ timedelta-like} str stamp Localizer info = Localizer(tz, creso=creso) int64_t pph = periods_per_day(creso) // 24 + int64_t pps = periods_per_second(creso) + npy_datetimestruct dts # Vectorized version of DstTzInfo.localize if info.use_utc: @@ -388,14 +390,26 @@ timedelta-like} new_local = val - remaining_mins - 1 if is_zi: - raise NotImplementedError( - "nonexistent shifting is not implemented with ZoneInfo tzinfos" - ) + # use the same construction as in _get_utc_bounds_zoneinfo + pandas_datetime_to_datetimestruct(new_local, creso, &dts) + extra = (dts.ps // 1000) * (pps // 1_000_000_000) + + dt = datetime_new(dts.year, dts.month, dts.day, dts.hour, + dts.min, dts.sec, dts.us, None) - delta_idx = bisect_right_i8(info.tdata, new_local, info.ntrans) + if shift_forward or shift_delta > 0: + dt = dt.replace(tzinfo=tz, fold=1) + else: + dt = dt.replace(tzinfo=tz, fold=0) + dt = dt.astimezone(utc_stdlib) + dt = dt.replace(tzinfo=None) + result[i] = pydatetime_to_dt64(dt, &dts, creso) + extra + + else: + delta_idx = bisect_right_i8(info.tdata, new_local, info.ntrans) - delta_idx = delta_idx - delta_idx_offset - result[i] = new_local - info.deltas[delta_idx] + delta_idx = delta_idx - delta_idx_offset + result[i] = new_local - info.deltas[delta_idx] elif fill_nonexist: result[i] = NPY_NAT else: diff --git a/pandas/tests/indexes/datetimes/test_constructors.py b/pandas/tests/indexes/datetimes/test_constructors.py index effabafb4db67..3a5ec45dce8f1 100644 --- a/pandas/tests/indexes/datetimes/test_constructors.py +++ b/pandas/tests/indexes/datetimes/test_constructors.py @@ -880,15 +880,8 @@ def test_constructor_with_ambiguous_keyword_arg(self): result = date_range(end=end, periods=2, ambiguous=False) tm.assert_index_equal(result, expected) - def test_constructor_with_nonexistent_keyword_arg(self, warsaw, request): + def test_constructor_with_nonexistent_keyword_arg(self, warsaw): # GH 35297 - if type(warsaw).__name__ == "ZoneInfo": - mark = pytest.mark.xfail( - reason="nonexistent-shift not yet implemented for ZoneInfo", - raises=NotImplementedError, - ) - request.node.add_marker(mark) - timezone = warsaw # nonexistent keyword in start diff --git a/pandas/tests/series/methods/test_tz_localize.py b/pandas/tests/series/methods/test_tz_localize.py index a9e28bfeeb76b..51714bfde1634 100644 --- a/pandas/tests/series/methods/test_tz_localize.py +++ b/pandas/tests/series/methods/test_tz_localize.py @@ -97,15 +97,6 @@ def test_tz_localize_nonexistent(self, warsaw, method, exp): with pytest.raises(ValueError, match=msg): df.tz_localize(tz, nonexistent=method) - elif method == "shift_forward" and type(tz).__name__ == "ZoneInfo": - msg = "nonexistent shifting is not implemented with ZoneInfo tzinfos" - with pytest.raises(NotImplementedError, match=msg): - ser.tz_localize(tz, nonexistent=method) - with pytest.raises(NotImplementedError, match=msg): - df.tz_localize(tz, nonexistent=method) - with pytest.raises(NotImplementedError, match=msg): - dti.tz_localize(tz, nonexistent=method) - else: result = ser.tz_localize(tz, nonexistent=method) expected = Series(1, index=DatetimeIndex([exp] * n, tz=tz)) From cbcd18ea2def0baadc7690299400ea784c4dfb48 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 2 Jan 2023 15:55:10 -0800 Subject: [PATCH 2/2] add test with shift_backward --- pandas/tests/series/methods/test_tz_localize.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/series/methods/test_tz_localize.py b/pandas/tests/series/methods/test_tz_localize.py index 797d8c87c4b18..6b096a7fcf3eb 100644 --- a/pandas/tests/series/methods/test_tz_localize.py +++ b/pandas/tests/series/methods/test_tz_localize.py @@ -64,6 +64,7 @@ def test_series_tz_localize_matching_index(self): "method, exp", [ ["shift_forward", "2015-03-29 03:00:00"], + ["shift_backward", "2015-03-29 01:59:59.999999999"], ["NaT", NaT], ["raise", None], ["foo", "invalid"],