Skip to content

Commit f65fa75

Browse files
mroeschkejreback
authored andcommitted
BUG: Avoid AmbiguousTime or NonExistentTime Error when resampling (#22809)
1 parent fb784ca commit f65fa75

File tree

3 files changed

+33
-20
lines changed

3 files changed

+33
-20
lines changed

doc/source/whatsnew/v0.24.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ Timezones
679679
- Bug when setting a new value with :meth:`DataFrame.loc` with a :class:`DatetimeIndex` with a DST transition (:issue:`18308`, :issue:`20724`)
680680
- Bug in :meth:`DatetimeIndex.unique` that did not re-localize tz-aware dates correctly (:issue:`21737`)
681681
- Bug when indexing a :class:`Series` with a DST transition (:issue:`21846`)
682+
- Bug in :meth:`DataFrame.resample` and :meth:`Series.resample` where an ``AmbiguousTimeError`` or ``NonExistentTimeError`` would raise if a timezone aware timeseries ended on a DST transition (:issue:`19375`, :issue:`10117`)
682683

683684
Offsets
684685
^^^^^^^

pandas/core/resample.py

+16-20
Original file line numberDiff line numberDiff line change
@@ -1328,8 +1328,7 @@ def _get_time_bins(self, ax):
13281328
data=[], freq=self.freq, name=ax.name)
13291329
return binner, [], labels
13301330

1331-
first, last = ax.min(), ax.max()
1332-
first, last = _get_range_edges(first, last, self.freq,
1331+
first, last = _get_range_edges(ax.min(), ax.max(), self.freq,
13331332
closed=self.closed,
13341333
base=self.base)
13351334
tz = ax.tz
@@ -1519,9 +1518,6 @@ def _take_new_index(obj, indexer, new_index, axis=0):
15191518

15201519

15211520
def _get_range_edges(first, last, offset, closed='left', base=0):
1522-
if isinstance(offset, compat.string_types):
1523-
offset = to_offset(offset)
1524-
15251521
if isinstance(offset, Tick):
15261522
is_day = isinstance(offset, Day)
15271523
day_nanos = delta_to_nanoseconds(timedelta(1))
@@ -1531,8 +1527,7 @@ def _get_range_edges(first, last, offset, closed='left', base=0):
15311527
return _adjust_dates_anchored(first, last, offset,
15321528
closed=closed, base=base)
15331529

1534-
if not isinstance(offset, Tick): # and first.time() != last.time():
1535-
# hack!
1530+
else:
15361531
first = first.normalize()
15371532
last = last.normalize()
15381533

@@ -1553,19 +1548,16 @@ def _adjust_dates_anchored(first, last, offset, closed='right', base=0):
15531548
#
15541549
# See https://github.com/pandas-dev/pandas/issues/8683
15551550

1556-
# 14682 - Since we need to drop the TZ information to perform
1557-
# the adjustment in the presence of a DST change,
1558-
# save TZ Info and the DST state of the first and last parameters
1559-
# so that we can accurately rebuild them at the end.
1551+
# GH 10117 & GH 19375. If first and last contain timezone information,
1552+
# Perform the calculation in UTC in order to avoid localizing on an
1553+
# Ambiguous or Nonexistent time.
15601554
first_tzinfo = first.tzinfo
15611555
last_tzinfo = last.tzinfo
1562-
first_dst = bool(first.dst())
1563-
last_dst = bool(last.dst())
1564-
1565-
first = first.tz_localize(None)
1566-
last = last.tz_localize(None)
1567-
15681556
start_day_nanos = first.normalize().value
1557+
if first_tzinfo is not None:
1558+
first = first.tz_convert('UTC')
1559+
if last_tzinfo is not None:
1560+
last = last.tz_convert('UTC')
15691561

15701562
base_nanos = (base % offset.n) * offset.nanos // offset.n
15711563
start_day_nanos += base_nanos
@@ -1598,9 +1590,13 @@ def _adjust_dates_anchored(first, last, offset, closed='right', base=0):
15981590
lresult = last.value + (offset.nanos - loffset)
15991591
else:
16001592
lresult = last.value + offset.nanos
1601-
1602-
return (Timestamp(fresult).tz_localize(first_tzinfo, ambiguous=first_dst),
1603-
Timestamp(lresult).tz_localize(last_tzinfo, ambiguous=last_dst))
1593+
fresult = Timestamp(fresult)
1594+
lresult = Timestamp(lresult)
1595+
if first_tzinfo is not None:
1596+
fresult = fresult.tz_localize('UTC').tz_convert(first_tzinfo)
1597+
if last_tzinfo is not None:
1598+
lresult = lresult.tz_localize('UTC').tz_convert(last_tzinfo)
1599+
return fresult, lresult
16041600

16051601

16061602
def asfreq(obj, freq, method=None, how=None, normalize=False, fill_value=None):

pandas/tests/test_resample.py

+16
Original file line numberDiff line numberDiff line change
@@ -2485,6 +2485,22 @@ def test_with_local_timezone_dateutil(self):
24852485
expected = Series(1, index=expected_index)
24862486
assert_series_equal(result, expected)
24872487

2488+
def test_resample_nonexistent_time_bin_edge(self):
2489+
# GH 19375
2490+
index = date_range('2017-03-12', '2017-03-12 1:45:00', freq='15T')
2491+
s = Series(np.zeros(len(index)), index=index)
2492+
expected = s.tz_localize('US/Pacific')
2493+
result = expected.resample('900S').mean()
2494+
tm.assert_series_equal(result, expected)
2495+
2496+
def test_resample_ambiguous_time_bin_edge(self):
2497+
# GH 10117
2498+
idx = pd.date_range("2014-10-25 22:00:00", "2014-10-26 00:30:00",
2499+
freq="30T", tz="Europe/London")
2500+
expected = Series(np.zeros(len(idx)), index=idx)
2501+
result = expected.resample('30T').mean()
2502+
tm.assert_series_equal(result, expected)
2503+
24882504
def test_fill_method_and_how_upsample(self):
24892505
# GH2073
24902506
s = Series(np.arange(9, dtype='int64'),

0 commit comments

Comments
 (0)