diff --git a/doc/source/whatsnew/v0.19.2.txt b/doc/source/whatsnew/v0.19.2.txt index f4a45a6938a95..7b21ad0186d4e 100644 --- a/doc/source/whatsnew/v0.19.2.txt +++ b/doc/source/whatsnew/v0.19.2.txt @@ -43,3 +43,4 @@ Bug Fixes - Explicit check in ``to_stata`` and ``StataWriter`` for out-of-range values when writing doubles (:issue:`14618`) +- Fix ``AmbiguousTimeError`` exception when resampling a ``DatetimeIndex`` in local TZ, covering a DST change (:issue:`14682`) diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index 70e2d2c121773..024306edef2d8 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -439,7 +439,7 @@ def _generate(cls, start, end, periods, name, offset, tz = tz.localize(date.replace(tzinfo=None)).tzinfo if tz is not None and inferred_tz is not None: - if not inferred_tz == tz: + if not tslib.get_timezone(inferred_tz) == tslib.get_timezone(tz): raise AssertionError("Inferred time zone not equal to passed " "time zone") diff --git a/pandas/tseries/resample.py b/pandas/tseries/resample.py old mode 100644 new mode 100755 index d02c403cb3c66..31781eb3fc131 --- a/pandas/tseries/resample.py +++ b/pandas/tseries/resample.py @@ -1283,9 +1283,18 @@ def _adjust_dates_anchored(first, last, offset, closed='right', base=0): # # See https://github.com/pandas-dev/pandas/issues/8683 + # 14682 - Since we need to drop the TZ information to perform + # the adjustment in the presence of a DST change, + # save TZ Info and the DST state of the first and last parameters + # so that we can accurately rebuild them at the end. first_tzinfo = first.tzinfo + last_tzinfo = last.tzinfo + first_dst = bool(first.dst()) + last_dst = bool(last.dst()) + first = first.tz_localize(None) last = last.tz_localize(None) + start_day_nanos = first.normalize().value base_nanos = (base % offset.n) * offset.nanos // offset.n @@ -1320,11 +1329,8 @@ def _adjust_dates_anchored(first, last, offset, closed='right', base=0): else: lresult = last.value + offset.nanos -# return (Timestamp(fresult, tz=first.tz), -# Timestamp(lresult, tz=last.tz)) - - return (Timestamp(fresult).tz_localize(first_tzinfo), - Timestamp(lresult).tz_localize(first_tzinfo)) + return (Timestamp(fresult).tz_localize(first_tzinfo, ambiguous=first_dst), + Timestamp(lresult).tz_localize(last_tzinfo, ambiguous=last_dst)) def asfreq(obj, freq, method=None, how=None, normalize=False): diff --git a/pandas/tseries/tests/test_resample.py b/pandas/tseries/tests/test_resample.py old mode 100644 new mode 100755 index 9d3d27f3224b4..b8c060c024867 --- a/pandas/tseries/tests/test_resample.py +++ b/pandas/tseries/tests/test_resample.py @@ -1912,7 +1912,33 @@ def test_resample_size(self): right = Series(val, index=ix) assert_series_equal(left, right) - def test_resmaple_dst_anchor(self): + def test_resample_across_dst(self): + # The test resamples a DatetimeIndex with values before and after a + # DST change + # Issue: 14682 + + # The DatetimeIndex we will start with + # (note that DST happens at 03:00+02:00 -> 02:00+01:00) + # 2016-10-30 02:23:00+02:00, 2016-10-30 02:23:00+01:00 + df1 = DataFrame([1477786980, 1477790580], columns=['ts']) + dti1 = DatetimeIndex(pd.to_datetime(df1.ts, unit='s') + .dt.tz_localize('UTC') + .dt.tz_convert('Europe/Madrid')) + + # The expected DatetimeIndex after resampling. + # 2016-10-30 02:00:00+02:00, 2016-10-30 02:00:00+01:00 + df2 = DataFrame([1477785600, 1477789200], columns=['ts']) + dti2 = DatetimeIndex(pd.to_datetime(df2.ts, unit='s') + .dt.tz_localize('UTC') + .dt.tz_convert('Europe/Madrid')) + df = DataFrame([5, 5], index=dti1) + + result = df.resample(rule='H').sum() + expected = DataFrame([5, 5], index=dti2) + + assert_frame_equal(result, expected) + + def test_resample_dst_anchor(self): # 5172 dti = DatetimeIndex([datetime(2012, 11, 4, 23)], tz='US/Eastern') df = DataFrame([5], index=dti)