diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index b7d17d1abbe91..afcd7ed02c457 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -293,9 +293,9 @@ Categorical Datetimelike ^^^^^^^^^^^^ - :meth:`DatetimeIndex.map` with ``na_action="ignore"`` now works as expected. (:issue:`51644`) +- Bug in :func:`date_range` when ``freq`` was a :class:`DateOffset` with ``nanoseconds`` (:issue:`46877`) - Bug in :meth:`Timestamp.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsDatetime`` (:issue:`51494`) - Bug in :meth:`arrays.DatetimeArray.map` and :meth:`DatetimeIndex.map`, where the supplied callable operated array-wise instead of element-wise (:issue:`51977`) -- Timedelta ^^^^^^^^^ diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 8bbaafa536457..0614cb2a9d8c9 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -1217,7 +1217,10 @@ cdef class RelativeDeltaOffset(BaseOffset): @apply_wraps def _apply(self, other: datetime) -> datetime: + other_nanos = 0 if self._use_relativedelta: + if isinstance(other, _Timestamp): + other_nanos = other.nanosecond other = _as_datetime(other) if len(self.kwds) > 0: @@ -1226,17 +1229,17 @@ cdef class RelativeDeltaOffset(BaseOffset): # perform calculation in UTC other = other.replace(tzinfo=None) - if hasattr(self, "nanoseconds"): - td_nano = Timedelta(nanoseconds=self.nanoseconds) - else: - td_nano = Timedelta(0) - if self.n > 0: for i in range(self.n): - other = other + self._offset + td_nano + other = other + self._offset else: for i in range(-self.n): - other = other - self._offset - td_nano + other = other - self._offset + + if hasattr(self, "nanoseconds"): + other = self.n * Timedelta(nanoseconds=self.nanoseconds) + other + if other_nanos != 0: + other = Timedelta(nanoseconds=other_nanos) + other if tzinfo is not None and self._use_relativedelta: # bring tz back from UTC calculation diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index a765f4ae1b21b..b14a54a872a69 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -2581,7 +2581,14 @@ def _generate_range( break # faster than cur + offset - next_date = offset._apply(cur).as_unit(unit) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Discarding nonzero nanoseconds in conversion", + category=UserWarning, + ) + next_date = offset._apply(cur) + next_date = next_date.as_unit(unit) if next_date <= cur: raise ValueError(f"Offset {offset} did not increment date") cur = next_date @@ -2595,7 +2602,14 @@ def _generate_range( break # faster than cur + offset - next_date = offset._apply(cur).as_unit(unit) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Discarding nonzero nanoseconds in conversion", + category=UserWarning, + ) + next_date = offset._apply(cur) + next_date = next_date.as_unit(unit) if next_date >= cur: raise ValueError(f"Offset {offset} did not decrement date") cur = next_date diff --git a/pandas/tests/indexes/datetimes/test_date_range.py b/pandas/tests/indexes/datetimes/test_date_range.py index ef909feccfcd3..f3f5d0dfd5ec1 100644 --- a/pandas/tests/indexes/datetimes/test_date_range.py +++ b/pandas/tests/indexes/datetimes/test_date_range.py @@ -790,6 +790,27 @@ def test_range_where_start_equal_end(self, inclusive_endpoints_fixture): tm.assert_index_equal(result, expected) + def test_freq_dateoffset_with_relateivedelta_nanos(self): + # GH 46877 + freq = DateOffset(hours=10, days=57, nanoseconds=3) + result = date_range(end="1970-01-01 00:00:00", periods=10, freq=freq, name="a") + expected = DatetimeIndex( + [ + "1968-08-02T05:59:59.999999973", + "1968-09-28T15:59:59.999999976", + "1968-11-25T01:59:59.999999979", + "1969-01-21T11:59:59.999999982", + "1969-03-19T21:59:59.999999985", + "1969-05-16T07:59:59.999999988", + "1969-07-12T17:59:59.999999991", + "1969-09-08T03:59:59.999999994", + "1969-11-04T13:59:59.999999997", + "1970-01-01T00:00:00.000000000", + ], + name="a", + ) + tm.assert_index_equal(result, expected) + class TestDateRangeTZ: """Tests for date_range with timezones"""