Skip to content

Commit ab4eedd

Browse files
authored
BUG: date_range with DateOffset with nanoseconds (#52902)
* date_range with freq with nano * add whatsnew and test, simplify logic
1 parent 0223c0c commit ab4eedd

File tree

4 files changed

+48
-10
lines changed

4 files changed

+48
-10
lines changed

doc/source/whatsnew/v2.1.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,9 @@ Categorical
293293
Datetimelike
294294
^^^^^^^^^^^^
295295
- :meth:`DatetimeIndex.map` with ``na_action="ignore"`` now works as expected. (:issue:`51644`)
296+
- Bug in :func:`date_range` when ``freq`` was a :class:`DateOffset` with ``nanoseconds`` (:issue:`46877`)
296297
- Bug in :meth:`Timestamp.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsDatetime`` (:issue:`51494`)
297298
- Bug in :meth:`arrays.DatetimeArray.map` and :meth:`DatetimeIndex.map`, where the supplied callable operated array-wise instead of element-wise (:issue:`51977`)
298-
-
299299

300300
Timedelta
301301
^^^^^^^^^

pandas/_libs/tslibs/offsets.pyx

+10-7
Original file line numberDiff line numberDiff line change
@@ -1217,7 +1217,10 @@ cdef class RelativeDeltaOffset(BaseOffset):
12171217

12181218
@apply_wraps
12191219
def _apply(self, other: datetime) -> datetime:
1220+
other_nanos = 0
12201221
if self._use_relativedelta:
1222+
if isinstance(other, _Timestamp):
1223+
other_nanos = other.nanosecond
12211224
other = _as_datetime(other)
12221225

12231226
if len(self.kwds) > 0:
@@ -1226,17 +1229,17 @@ cdef class RelativeDeltaOffset(BaseOffset):
12261229
# perform calculation in UTC
12271230
other = other.replace(tzinfo=None)
12281231

1229-
if hasattr(self, "nanoseconds"):
1230-
td_nano = Timedelta(nanoseconds=self.nanoseconds)
1231-
else:
1232-
td_nano = Timedelta(0)
1233-
12341232
if self.n > 0:
12351233
for i in range(self.n):
1236-
other = other + self._offset + td_nano
1234+
other = other + self._offset
12371235
else:
12381236
for i in range(-self.n):
1239-
other = other - self._offset - td_nano
1237+
other = other - self._offset
1238+
1239+
if hasattr(self, "nanoseconds"):
1240+
other = self.n * Timedelta(nanoseconds=self.nanoseconds) + other
1241+
if other_nanos != 0:
1242+
other = Timedelta(nanoseconds=other_nanos) + other
12401243

12411244
if tzinfo is not None and self._use_relativedelta:
12421245
# bring tz back from UTC calculation

pandas/core/arrays/datetimes.py

+16-2
Original file line numberDiff line numberDiff line change
@@ -2581,7 +2581,14 @@ def _generate_range(
25812581
break
25822582

25832583
# faster than cur + offset
2584-
next_date = offset._apply(cur).as_unit(unit)
2584+
with warnings.catch_warnings():
2585+
warnings.filterwarnings(
2586+
"ignore",
2587+
"Discarding nonzero nanoseconds in conversion",
2588+
category=UserWarning,
2589+
)
2590+
next_date = offset._apply(cur)
2591+
next_date = next_date.as_unit(unit)
25852592
if next_date <= cur:
25862593
raise ValueError(f"Offset {offset} did not increment date")
25872594
cur = next_date
@@ -2595,7 +2602,14 @@ def _generate_range(
25952602
break
25962603

25972604
# faster than cur + offset
2598-
next_date = offset._apply(cur).as_unit(unit)
2605+
with warnings.catch_warnings():
2606+
warnings.filterwarnings(
2607+
"ignore",
2608+
"Discarding nonzero nanoseconds in conversion",
2609+
category=UserWarning,
2610+
)
2611+
next_date = offset._apply(cur)
2612+
next_date = next_date.as_unit(unit)
25992613
if next_date >= cur:
26002614
raise ValueError(f"Offset {offset} did not decrement date")
26012615
cur = next_date

pandas/tests/indexes/datetimes/test_date_range.py

+21
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,27 @@ def test_range_where_start_equal_end(self, inclusive_endpoints_fixture):
790790

791791
tm.assert_index_equal(result, expected)
792792

793+
def test_freq_dateoffset_with_relateivedelta_nanos(self):
794+
# GH 46877
795+
freq = DateOffset(hours=10, days=57, nanoseconds=3)
796+
result = date_range(end="1970-01-01 00:00:00", periods=10, freq=freq, name="a")
797+
expected = DatetimeIndex(
798+
[
799+
"1968-08-02T05:59:59.999999973",
800+
"1968-09-28T15:59:59.999999976",
801+
"1968-11-25T01:59:59.999999979",
802+
"1969-01-21T11:59:59.999999982",
803+
"1969-03-19T21:59:59.999999985",
804+
"1969-05-16T07:59:59.999999988",
805+
"1969-07-12T17:59:59.999999991",
806+
"1969-09-08T03:59:59.999999994",
807+
"1969-11-04T13:59:59.999999997",
808+
"1970-01-01T00:00:00.000000000",
809+
],
810+
name="a",
811+
)
812+
tm.assert_index_equal(result, expected)
813+
793814

794815
class TestDateRangeTZ:
795816
"""Tests for date_range with timezones"""

0 commit comments

Comments
 (0)