Skip to content

BUG: Construct Timestamp with tz correctly near DST border #21407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 13, 2018
Merged
4 changes: 4 additions & 0 deletions doc/source/whatsnew/v0.23.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ Bug Fixes
- Bug in :func:`concat` where error was raised in concatenating :class:`Series` with numpy scalar and tuple names (:issue:`21015`)
- Bug in :func:`concat` warning message providing the wrong guidance for future behavior (:issue:`21101`)

**Timezones**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to 0.23.2

- 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`)
- Bug in comparing :class:`DataFrame`s with tz-aware :class:`DatetimeIndex` columns with a DST transition that raised a ``KeyError`` (:issue:`19970`)

**Other**

- Tab completion on :class:`Index` in IPython no longer outputs deprecation warnings (:issue:`21125`)
Expand Down
3 changes: 1 addition & 2 deletions pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,9 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz,
if tz is not None:
tz = maybe_get_tz(tz)

# sort of a temporary hack
if ts.tzinfo is not None:
if hasattr(tz, 'normalize') and hasattr(ts.tzinfo, '_utcoffset'):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check might be superfluous since datetimes natively have the astimezone method. Then this whole block might be able to be simplified.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, you can try that and see if it works. maybe add a comment anyhow

ts = tz.normalize(ts)
ts = ts.astimezone(tz)
obj.value = pydatetime_to_dt64(ts, &obj.dts)
obj.tzinfo = ts.tzinfo
else:
Expand Down
10 changes: 10 additions & 0 deletions pandas/tests/frame/test_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,16 @@ def test_boolean_comparison(self):
result = df == tup
assert_frame_equal(result, expected)

@pytest.mark.parametrize('tz', [None, 'America/New_York'])
def test_boolean_compare_transpose_tzindex_with_dst(self, tz):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you move to test_timezones

# GH 19970
idx = date_range('20161101', '20161130', freq='4H', tz=tz)
df = DataFrame({'a': range(len(idx)), 'b': range(len(idx))},
index=idx)
result = df.T == df.T
expected = DataFrame(True, index=list('ab'), columns=idx)
assert_frame_equal(result, expected)

def test_combine_generic(self):
df1 = self.frame
df2 = self.frame.loc[self.frame.index[:-5], ['A', 'B', 'C']]
Expand Down
9 changes: 9 additions & 0 deletions pandas/tests/indexes/datetimes/test_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,15 @@ def test_constructor_with_non_normalized_pytz(self, tz):
result = DatetimeIndex(['2010'], tz=non_norm_tz)
assert pytz.timezone(tz) is result.tz

def test_constructor_timestamp_near_dst(self):
# GH 20854
ts = [Timestamp('2016-10-30 03:00:00+0300', tz='Europe/Helsinki'),
Timestamp('2016-10-30 03:00:00+0200', tz='Europe/Helsinki')]
result = DatetimeIndex(ts)
expected = DatetimeIndex([ts[0].to_pydatetime(),
ts[1].to_pydatetime()])
tm.assert_index_equal(result, expected)


class TestTimeSeries(object):

Expand Down
14 changes: 14 additions & 0 deletions pandas/tests/indexes/datetimes/test_date_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,20 @@ def test_wom_len(self, periods):
res = date_range(start='20110101', periods=periods, freq='WOM-1MON')
assert len(res) == periods

def test_construct_over_dst(self):
# GH 20854
pre_dst = Timestamp('2010-11-07 01:00:00').tz_localize('US/Pacific',
ambiguous=True)
pst_dst = Timestamp('2010-11-07 01:00:00').tz_localize('US/Pacific',
ambiguous=False)
expect_data = [Timestamp('2010-11-07 00:00:00', tz='US/Pacific'),
pre_dst,
pst_dst]
expected = DatetimeIndex(expect_data)
result = date_range(start='2010-11-7', periods=3,
freq='H', tz='US/Pacific')
tm.assert_index_equal(result, expected)


class TestGenRangeGeneration(object):

Expand Down
8 changes: 8 additions & 0 deletions pandas/tests/scalar/timestamp/test_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,14 @@ def test_disallow_setting_tz(self, tz):
with pytest.raises(AttributeError):
ts.tz = tz

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


class TestTimestamp(object):

Expand Down