diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index 851c1a3fbd6e9..a64576565d093 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -794,6 +794,7 @@ Groupby/Resample/Rolling - Bug in :meth:`Resampler.asfreq` when frequency of ``TimedeltaIndex`` is a subperiod of a new frequency (:issue:`13022`). - Bug in :meth:`SeriesGroupBy.mean` when values were integral but could not fit inside of int64, overflowing instead. (:issue:`22487`) - :func:`RollingGroupby.agg` and :func:`ExpandingGroupby.agg` now support multiple aggregation functions as parameters (:issue:`15072`) +- Bug in :meth:`DataFrame.resample` and :meth:`Series.resample` when resampling by a weekly offset (``'W'``) across a DST transition (:issue:`9119`, :issue:`21459`) Sparse ^^^^^^ diff --git a/pandas/core/resample.py b/pandas/core/resample.py index 878ac957a8557..70a8deb33b7f2 100644 --- a/pandas/core/resample.py +++ b/pandas/core/resample.py @@ -16,7 +16,8 @@ from pandas.tseries.frequencies import to_offset, is_subperiod, is_superperiod from pandas.core.indexes.datetimes import DatetimeIndex, date_range from pandas.core.indexes.timedeltas import TimedeltaIndex -from pandas.tseries.offsets import DateOffset, Tick, Day, delta_to_nanoseconds +from pandas.tseries.offsets import (DateOffset, Tick, Day, + delta_to_nanoseconds, Nano) from pandas.core.indexes.period import PeriodIndex from pandas.errors import AbstractMethodError import pandas.core.algorithms as algos @@ -1395,18 +1396,21 @@ def _get_time_bins(self, ax): def _adjust_bin_edges(self, binner, ax_values): # Some hacks for > daily data, see #1471, #1458, #1483 - bin_edges = binner.asi8 - if self.freq != 'D' and is_superperiod(self.freq, 'D'): - day_nanos = delta_to_nanoseconds(timedelta(1)) if self.closed == 'right': - bin_edges = bin_edges + day_nanos - 1 + # GH 21459, GH 9119: Adjust the bins relative to the wall time + bin_edges = binner.tz_localize(None) + bin_edges = bin_edges + timedelta(1) - Nano(1) + bin_edges = bin_edges.tz_localize(binner.tz).asi8 + else: + bin_edges = binner.asi8 # intraday values on last day if bin_edges[-2] > ax_values.max(): bin_edges = bin_edges[:-1] binner = binner[:-1] - + else: + bin_edges = binner.asi8 return binner, bin_edges def _get_time_delta_bins(self, ax): diff --git a/pandas/tests/test_resample.py b/pandas/tests/test_resample.py index ccd2461d1512e..5cd31e08e0a9b 100644 --- a/pandas/tests/test_resample.py +++ b/pandas/tests/test_resample.py @@ -2114,6 +2114,28 @@ def test_downsample_across_dst(self): freq='H')) tm.assert_series_equal(result, expected) + def test_downsample_across_dst_weekly(self): + # GH 9119, GH 21459 + df = DataFrame(index=DatetimeIndex([ + '2017-03-25', '2017-03-26', '2017-03-27', + '2017-03-28', '2017-03-29' + ], tz='Europe/Amsterdam'), + data=[11, 12, 13, 14, 15]) + result = df.resample('1W').sum() + expected = DataFrame([23, 42], index=pd.DatetimeIndex([ + '2017-03-26', '2017-04-02' + ], tz='Europe/Amsterdam')) + tm.assert_frame_equal(result, expected) + + idx = pd.date_range("2013-04-01", "2013-05-01", tz='Europe/London', + freq='H') + s = Series(index=idx) + result = s.resample('W').mean() + expected = Series(index=pd.date_range( + '2013-04-07', freq='W', periods=5, tz='Europe/London' + )) + tm.assert_series_equal(result, expected) + def test_resample_with_nat(self): # GH 13020 index = DatetimeIndex([pd.NaT,