diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 22f6659367683..26ae7904b66fc 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -819,6 +819,7 @@ Datetimelike - Bug in :func:`to_datetime` was throwing ``ValueError`` when parsing dates with ISO8601 format where some values were not zero-padded (:issue:`21422`) - Bug in :func:`to_datetime` was giving incorrect results when using ``format='%Y%m%d'`` and ``errors='ignore'`` (:issue:`26493`) - Bug in :func:`to_datetime` was failing to parse date strings ``'today'`` and ``'now'`` if ``format`` was not ISO8601 (:issue:`50359`) +- Bug in :class:`RelativeDeltaOffset` which caused inconsistent behavior upon multiplying a :class:`DateOffset` (:issue:`47953`) - Timedelta diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 0bc9751694e9f..edb7e3cac8314 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1229,12 +1229,7 @@ cdef class RelativeDeltaOffset(BaseOffset): else: td_nano = Timedelta(0) - if self.n > 0: - for i in range(self.n): - other = other + self._offset + td_nano - else: - for i in range(-self.n): - other = other - self._offset - td_nano + other = other + ((self._offset + td_nano) * self.n) if tzinfo is not None and self._use_relativedelta: # bring tz back from UTC calculation diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 933723edd6e66..190ceaa80e976 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -33,6 +33,7 @@ from pandas.errors import PerformanceWarning from pandas import ( + DataFrame, DatetimeIndex, Series, date_range, @@ -1061,6 +1062,38 @@ def test_construct_int_arg_no_kwargs_assumed_days(n): assert result == expected +def test_offset_multiplication(): + # GH#47953 + mo1 = DateOffset(months=1) + mo2 = DateOffset(months=2) + + startscalar = Timestamp("2020-01-30") + startarray = Series([Timestamp("2020-01-30")]) + + resultscalar1 = startscalar + (mo1 * 2) + resultscalar2 = startscalar + mo2 + resultarray1 = startarray + (mo1 * 2) + resultarray2 = startarray + mo2 + + expectedscalar = Timestamp("2020-03-30") + expectedarray = Series([Timestamp("2020-03-30")]) + assert resultscalar1 == expectedscalar + assert resultscalar2 == expectedscalar + tm.assert_series_equal(resultarray1, expectedarray) + tm.assert_series_equal(resultarray2, expectedarray) + + df = DataFrame({"T": [Timestamp("2019-04-30")], "D": [DateOffset(months=1)]}) + frameresult1 = df["T"] + 26 * df["D"] + df2 = DataFrame( + { + "T": [Timestamp("2019-04-30"), Timestamp("2019-04-30")], + "D": [DateOffset(months=1), DateOffset(months=1)], + } + ) + frameresult2 = df2["T"] + 26 * df2["D"] + assert frameresult1[0] == frameresult2[0] + + @pytest.mark.parametrize( "offset, expected", [