diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index c072bfeff4a72..c4fca8eba784d 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -711,7 +711,7 @@ Datetimelike - Bug in :func:`pandas.to_datetime` when called with ``None`` raising ``TypeError`` instead of returning ``NaT`` (:issue:`30011`) - Bug in :func:`pandas.to_datetime` failing for `deques` when using ``cache=True`` (the default) (:issue:`29403`) - Bug in :meth:`Series.item` with ``datetime64`` or ``timedelta64`` dtype, :meth:`DatetimeIndex.item`, and :meth:`TimedeltaIndex.item` returning an integer instead of a :class:`Timestamp` or :class:`Timedelta` (:issue:`30175`) -- +- Bug in :class:`DatetimeIndex` addition when adding a non-optimized :class:`DateOffset` incorrectly dropping timezone information (:issue:`30336`) Timedelta ^^^^^^^^^ diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index e41f2a840d151..10669b09cefec 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -794,9 +794,7 @@ def _add_offset(self, offset): values = self.tz_localize(None) else: values = self - result = offset.apply_index(values) - if self.tz is not None: - result = result.tz_localize(self.tz) + result = offset.apply_index(values).tz_localize(self.tz) except NotImplementedError: warnings.warn( @@ -804,6 +802,9 @@ def _add_offset(self, offset): PerformanceWarning, ) result = self.astype("O") + offset + if len(self) == 0: + # _from_sequence won't be able to infer self.tz + return type(self)._from_sequence(result).tz_localize(self.tz) return type(self)._from_sequence(result, freq="infer") diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 458d69c1d3216..6f628bf86829a 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -20,6 +20,7 @@ from pandas._libs.tslibs.offsets import ApplyTypeError import pandas.compat as compat from pandas.compat.numpy import np_datetime64_compat +from pandas.errors import PerformanceWarning from pandas.core.indexes.datetimes import DatetimeIndex, _to_M8, date_range from pandas.core.series import Series @@ -43,7 +44,10 @@ CBMonthBegin, CBMonthEnd, CDay, + CustomBusinessDay, CustomBusinessHour, + CustomBusinessMonthBegin, + CustomBusinessMonthEnd, DateOffset, Day, Easter, @@ -607,6 +611,46 @@ def test_add(self, offset_types, tz_naive_fixture): assert isinstance(result, Timestamp) assert result == expected_localize + def test_add_empty_datetimeindex(self, offset_types, tz_naive_fixture): + # GH#12724, GH#30336 + offset_s = self._get_offset(offset_types) + + dti = DatetimeIndex([], tz=tz_naive_fixture) + + warn = None + if isinstance( + offset_s, + ( + Easter, + WeekOfMonth, + LastWeekOfMonth, + CustomBusinessDay, + BusinessHour, + CustomBusinessHour, + CustomBusinessMonthBegin, + CustomBusinessMonthEnd, + FY5253, + FY5253Quarter, + ), + ): + # We don't have an optimized apply_index + warn = PerformanceWarning + + with tm.assert_produces_warning(warn): + result = dti + offset_s + tm.assert_index_equal(result, dti) + with tm.assert_produces_warning(warn): + result = offset_s + dti + tm.assert_index_equal(result, dti) + + dta = dti._data + with tm.assert_produces_warning(warn): + result = dta + offset_s + tm.assert_equal(result, dta) + with tm.assert_produces_warning(warn): + result = offset_s + dta + tm.assert_equal(result, dta) + def test_pickle_v0_15_2(self, datapath): offsets = { "DateOffset": DateOffset(years=1),