diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index a893bb38f70d5..e0efb21fea6cd 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -896,6 +896,7 @@ Datetimelike - Bug in :meth:`DatetimeIndex.resolution` incorrectly returning "day" instead of "nanosecond" for nanosecond-resolution indexes (:issue:`46903`) - Bug in :class:`Timestamp` with an integer or float value and ``unit="Y"`` or ``unit="M"`` giving slightly-wrong results (:issue:`47266`) - Bug in :class:`.DatetimeArray` construction when passed another :class:`.DatetimeArray` and ``freq=None`` incorrectly inferring the freq from the given array (:issue:`47296`) +- Bug when adding a :class:`DateOffset` to a :class:`Series` would not add the ``nanoseconds`` field (:issue:`47856`) - Timedelta diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 48104965ec42b..e7c3a709bb251 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -297,8 +297,8 @@ _relativedelta_kwds = {"years", "months", "weeks", "days", "year", "month", cdef _determine_offset(kwds): # timedelta is used for sub-daily plural offsets and all singular - # offsets relativedelta is used for plural offsets of daily length or - # more nanosecond(s) are handled by apply_wraps + # offsets, relativedelta is used for plural offsets of daily length or + # more, nanosecond(s) are handled by apply_wraps kwds_no_nanos = dict( (k, v) for k, v in kwds.items() if k not in ('nanosecond', 'nanoseconds') @@ -1163,7 +1163,12 @@ cdef class RelativeDeltaOffset(BaseOffset): return dt64other elif not self._use_relativedelta and hasattr(self, "_offset"): # timedelta - delta = Timedelta(self._offset * self.n) + num_nano = getattr(self, "nanoseconds", 0) + if num_nano != 0: + rem_nano = Timedelta(nanoseconds=num_nano) + delta = Timedelta((self._offset + rem_nano) * self.n) + else: + delta = Timedelta(self._offset * self.n) td = (<_Timedelta>delta)._as_reso(reso) return dt64other + td else: diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 49661fe1ec8ce..bca4ba98f37b7 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -33,6 +33,7 @@ from pandas import ( DatetimeIndex, + Series, date_range, ) import pandas._testing as tm @@ -987,7 +988,7 @@ def test_dateoffset_add_sub(offset_kwargs, expected_arg): assert result == expected -def test_dataoffset_add_sub_timestamp_with_nano(): +def test_dateoffset_add_sub_timestamp_with_nano(): offset = DateOffset(minutes=2, nanoseconds=9) ts = Timestamp(4) result = ts + offset @@ -1032,3 +1033,26 @@ def test_construct_int_arg_no_kwargs_assumed_days(n): result = Timestamp(2022, 1, 2) + offset expected = Timestamp(2022, 1, 2 + n) assert result == expected + + +@pytest.mark.parametrize( + "offset, expected", + [ + ( + DateOffset(minutes=7, nanoseconds=18), + Timestamp("2022-01-01 00:07:00.000000018"), + ), + (DateOffset(nanoseconds=3), Timestamp("2022-01-01 00:00:00.000000003")), + ], +) +def test_dateoffset_add_sub_timestamp_series_with_nano(offset, expected): + # GH 47856 + start_time = Timestamp("2022-01-01") + teststamp = start_time + testseries = Series([start_time]) + testseries = testseries + offset + assert testseries[0] == expected + testseries -= offset + assert testseries[0] == teststamp + testseries = offset + testseries + assert testseries[0] == expected