Skip to content

Commit d4c48aa

Browse files
mroeschkejorisvandenbossche
authored andcommitted
BUG: Construct Timestamp with tz correctly near DST border (#21407)
(cherry picked from commit bc4ccd7)
1 parent 475c8bc commit d4c48aa

File tree

6 files changed

+49
-18
lines changed

6 files changed

+49
-18
lines changed

doc/source/whatsnew/v0.23.2.txt

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ Bug Fixes
7373

7474
-
7575

76+
**Timezones**
77+
- Bug in :class:`Timestamp` and :class:`DatetimeIndex` where passing a :class:`Timestamp` localized after a DST transition would return a datetime before the DST transition (:issue:`20854`)
78+
- Bug in comparing :class:`DataFrame`s with tz-aware :class:`DatetimeIndex` columns with a DST transition that raised a ``KeyError`` (:issue:`19970`)
79+
7680
**Other**
7781

7882
-

pandas/_libs/tslibs/conversion.pyx

+4-18
Original file line numberDiff line numberDiff line change
@@ -347,25 +347,11 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz,
347347
if tz is not None:
348348
tz = maybe_get_tz(tz)
349349

350-
# sort of a temporary hack
351350
if ts.tzinfo is not None:
352-
if hasattr(tz, 'normalize') and hasattr(ts.tzinfo, '_utcoffset'):
353-
ts = tz.normalize(ts)
354-
obj.value = pydatetime_to_dt64(ts, &obj.dts)
355-
obj.tzinfo = ts.tzinfo
356-
else:
357-
# tzoffset
358-
try:
359-
tz = ts.astimezone(tz).tzinfo
360-
except:
361-
pass
362-
obj.value = pydatetime_to_dt64(ts, &obj.dts)
363-
ts_offset = get_utcoffset(ts.tzinfo, ts)
364-
obj.value -= int(ts_offset.total_seconds() * 1e9)
365-
tz_offset = get_utcoffset(tz, ts)
366-
obj.value += int(tz_offset.total_seconds() * 1e9)
367-
dt64_to_dtstruct(obj.value, &obj.dts)
368-
obj.tzinfo = tz
351+
# Convert the current timezone to the passed timezone
352+
ts = ts.astimezone(tz)
353+
obj.value = pydatetime_to_dt64(ts, &obj.dts)
354+
obj.tzinfo = ts.tzinfo
369355
elif not is_utc(tz):
370356
ts = _localize_pydatetime(ts, tz)
371357
obj.value = pydatetime_to_dt64(ts, &obj.dts)

pandas/tests/frame/test_timezones.py

+10
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,13 @@ def test_frame_reset_index(self, tz):
133133
xp = df.index.tz
134134
rs = roundtripped.index.tz
135135
assert xp == rs
136+
137+
@pytest.mark.parametrize('tz', [None, 'America/New_York'])
138+
def test_boolean_compare_transpose_tzindex_with_dst(self, tz):
139+
# GH 19970
140+
idx = date_range('20161101', '20161130', freq='4H', tz=tz)
141+
df = DataFrame({'a': range(len(idx)), 'b': range(len(idx))},
142+
index=idx)
143+
result = df.T == df.T
144+
expected = DataFrame(True, index=list('ab'), columns=idx)
145+
tm.assert_frame_equal(result, expected)

pandas/tests/indexes/datetimes/test_construction.py

+9
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,15 @@ def test_constructor_with_non_normalized_pytz(self, tz):
469469
result = DatetimeIndex(['2010'], tz=non_norm_tz)
470470
assert pytz.timezone(tz) is result.tz
471471

472+
def test_constructor_timestamp_near_dst(self):
473+
# GH 20854
474+
ts = [Timestamp('2016-10-30 03:00:00+0300', tz='Europe/Helsinki'),
475+
Timestamp('2016-10-30 03:00:00+0200', tz='Europe/Helsinki')]
476+
result = DatetimeIndex(ts)
477+
expected = DatetimeIndex([ts[0].to_pydatetime(),
478+
ts[1].to_pydatetime()])
479+
tm.assert_index_equal(result, expected)
480+
472481

473482
class TestTimeSeries(object):
474483

pandas/tests/indexes/datetimes/test_date_range.py

+14
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,20 @@ def test_wom_len(self, periods):
278278
res = date_range(start='20110101', periods=periods, freq='WOM-1MON')
279279
assert len(res) == periods
280280

281+
def test_construct_over_dst(self):
282+
# GH 20854
283+
pre_dst = Timestamp('2010-11-07 01:00:00').tz_localize('US/Pacific',
284+
ambiguous=True)
285+
pst_dst = Timestamp('2010-11-07 01:00:00').tz_localize('US/Pacific',
286+
ambiguous=False)
287+
expect_data = [Timestamp('2010-11-07 00:00:00', tz='US/Pacific'),
288+
pre_dst,
289+
pst_dst]
290+
expected = DatetimeIndex(expect_data)
291+
result = date_range(start='2010-11-7', periods=3,
292+
freq='H', tz='US/Pacific')
293+
tm.assert_index_equal(result, expected)
294+
281295

282296
class TestGenRangeGeneration(object):
283297

pandas/tests/scalar/timestamp/test_timestamp.py

+8
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,14 @@ def test_disallow_setting_tz(self, tz):
528528
with pytest.raises(AttributeError):
529529
ts.tz = tz
530530

531+
@pytest.mark.parametrize('offset', ['+0300', '+0200'])
532+
def test_construct_timestamp_near_dst(self, offset):
533+
# GH 20854
534+
expected = Timestamp('2016-10-30 03:00:00{}'.format(offset),
535+
tz='Europe/Helsinki')
536+
result = Timestamp(expected, tz='Europe/Helsinki')
537+
assert result == expected
538+
531539

532540
class TestTimestamp(object):
533541

0 commit comments

Comments
 (0)