Skip to content

Commit df6a323

Browse files
authored
BUG: Bad shift_forward around UTC+0 when DST (#56017)
* add tests * fix * add whatsnew * refactor * Revert "refactor" This reverts commit 4482dd8. * fix bug * update * more cmt
1 parent 1e1f53c commit df6a323

File tree

3 files changed

+46
-3
lines changed

3 files changed

+46
-3
lines changed

doc/source/whatsnew/v2.2.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ Timezones
381381
^^^^^^^^^
382382
- Bug in :class:`AbstractHolidayCalendar` where timezone data was not propagated when computing holiday observances (:issue:`54580`)
383383
- Bug in :class:`Timestamp` construction with an ambiguous value and a ``pytz`` timezone failing to raise ``pytz.AmbiguousTimeError`` (:issue:`55657`)
384-
-
384+
- Bug in :meth:`Timestamp.tz_localize` with ``nonexistent="shift_forward`` around UTC+0 during DST (:issue:`51501`)
385385

386386
Numeric
387387
^^^^^^^

pandas/_libs/tslibs/tzconversion.pyx

+7-2
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,13 @@ timedelta-like}
416416

417417
else:
418418
delta_idx = bisect_right_i8(info.tdata, new_local, info.ntrans)
419-
420-
delta_idx = delta_idx - delta_idx_offset
419+
# Logic similar to the precompute section. But check the current
420+
# delta in case we are moving between UTC+0 and non-zero timezone
421+
if (shift_forward or shift_delta > 0) and \
422+
info.deltas[delta_idx - 1] >= 0:
423+
delta_idx = delta_idx - 1
424+
else:
425+
delta_idx = delta_idx - delta_idx_offset
421426
result[i] = new_local - info.deltas[delta_idx]
422427
elif fill_nonexist:
423428
result[i] = NPY_NAT

pandas/tests/scalar/timestamp/methods/test_tz_localize.py

+38
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,44 @@ def test_tz_localize_nonexistent(self, stamp, tz):
123123
ts.tz_localize(tz, nonexistent="raise")
124124
assert ts.tz_localize(tz, nonexistent="NaT") is NaT
125125

126+
@pytest.mark.parametrize(
127+
"stamp, tz, forward_expected, backward_expected",
128+
[
129+
(
130+
"2015-03-29 02:00:00",
131+
"Europe/Warsaw",
132+
"2015-03-29 03:00:00",
133+
"2015-03-29 01:59:59",
134+
), # utc+1 -> utc+2
135+
(
136+
"2023-03-12 02:00:00",
137+
"America/Los_Angeles",
138+
"2023-03-12 03:00:00",
139+
"2023-03-12 01:59:59",
140+
), # utc-8 -> utc-7
141+
(
142+
"2023-03-26 01:00:00",
143+
"Europe/London",
144+
"2023-03-26 02:00:00",
145+
"2023-03-26 00:59:59",
146+
), # utc+0 -> utc+1
147+
(
148+
"2023-03-26 00:00:00",
149+
"Atlantic/Azores",
150+
"2023-03-26 01:00:00",
151+
"2023-03-25 23:59:59",
152+
), # utc-1 -> utc+0
153+
],
154+
)
155+
def test_tz_localize_nonexistent_shift(
156+
self, stamp, tz, forward_expected, backward_expected
157+
):
158+
ts = Timestamp(stamp)
159+
forward_ts = ts.tz_localize(tz, nonexistent="shift_forward")
160+
assert forward_ts == Timestamp(forward_expected, tz=tz)
161+
backward_ts = ts.tz_localize(tz, nonexistent="shift_backward")
162+
assert backward_ts == Timestamp(backward_expected, tz=tz)
163+
126164
def test_tz_localize_ambiguous_raise(self):
127165
# GH#13057
128166
ts = Timestamp("2015-11-1 01:00")

0 commit comments

Comments
 (0)