Skip to content

Commit fa28c61

Browse files
authored
BUG: Tick + np.timedelta64 (pandas-dev#44474)
1 parent b314627 commit fa28c61

File tree

5 files changed

+74
-17
lines changed

5 files changed

+74
-17
lines changed

doc/source/whatsnew/v1.4.0.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ Datetimelike
540540
- Bug in inplace addition and subtraction of :class:`DatetimeIndex` or :class:`TimedeltaIndex` with :class:`DatetimeArray` or :class:`TimedeltaArray` (:issue:`43904`)
541541
- Bug in in calling ``np.isnan``, ``np.isfinite``, or ``np.isinf`` on a timezone-aware :class:`DatetimeIndex` incorrectly raising ``TypeError`` (:issue:`43917`)
542542
- Bug in constructing a :class:`Series` from datetime-like strings with mixed timezones incorrectly partially-inferring datetime values (:issue:`40111`)
543-
-
543+
- Bug in addition with a :class:`Tick` object and a ``np.timedelta64`` object incorrectly raising instead of returning :class:`Timedelta` (:issue:`44474`)
544544

545545
Timedelta
546546
^^^^^^^^^

pandas/_libs/tslibs/offsets.pyx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ from pandas._libs.tslibs.np_datetime cimport (
7272
from pandas._libs.tslibs.tzconversion cimport tz_convert_from_utc_single
7373

7474
from .dtypes cimport PeriodDtypeCode
75-
from .timedeltas cimport delta_to_nanoseconds
75+
from .timedeltas cimport (
76+
delta_to_nanoseconds,
77+
is_any_td_scalar,
78+
)
7679

7780
from .timedeltas import Timedelta
7881

@@ -154,7 +157,11 @@ def apply_wraps(func):
154157

155158
if other is NaT:
156159
return NaT
157-
elif isinstance(other, BaseOffset) or PyDelta_Check(other):
160+
elif (
161+
isinstance(other, BaseOffset)
162+
or PyDelta_Check(other)
163+
or util.is_timedelta64_object(other)
164+
):
158165
# timedelta path
159166
return func(self, other)
160167
elif is_datetime64_object(other) or PyDate_Check(other):
@@ -902,7 +909,7 @@ cdef class Tick(SingleConstructorOffset):
902909
# PyDate_Check includes date, datetime
903910
return Timestamp(other) + self
904911

905-
if PyDelta_Check(other):
912+
if util.is_timedelta64_object(other) or PyDelta_Check(other):
906913
return other + self.delta
907914
elif isinstance(other, type(self)):
908915
# TODO: this is reached in tests that specifically call apply,
@@ -1396,9 +1403,10 @@ cdef class BusinessDay(BusinessMixin):
13961403
result = result + self.offset
13971404
return result
13981405

1399-
elif PyDelta_Check(other) or isinstance(other, Tick):
1406+
elif is_any_td_scalar(other):
1407+
td = Timedelta(self.offset) + other
14001408
return BusinessDay(
1401-
self.n, offset=self.offset + other, normalize=self.normalize
1409+
self.n, offset=td.to_pytimedelta(), normalize=self.normalize
14021410
)
14031411
else:
14041412
raise ApplyTypeError(
@@ -3265,8 +3273,9 @@ cdef class CustomBusinessDay(BusinessDay):
32653273
result = result + self.offset
32663274
return result
32673275

3268-
elif PyDelta_Check(other) or isinstance(other, Tick):
3269-
return BDay(self.n, offset=self.offset + other, normalize=self.normalize)
3276+
elif is_any_td_scalar(other):
3277+
td = Timedelta(self.offset) + other
3278+
return BDay(self.n, offset=td.to_pytimedelta(), normalize=self.normalize)
32703279
else:
32713280
raise ApplyTypeError(
32723281
"Only know how to combine trading day with "

pandas/tests/tseries/offsets/test_business_day.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
timedelta,
88
)
99

10+
import numpy as np
1011
import pytest
1112

1213
from pandas._libs.tslibs.offsets import (
@@ -17,6 +18,7 @@
1718

1819
from pandas import (
1920
DatetimeIndex,
21+
Timedelta,
2022
_testing as tm,
2123
)
2224
from pandas.tests.tseries.offsets.common import (
@@ -57,11 +59,30 @@ def test_with_offset(self):
5759

5860
assert (self.d + offset) == datetime(2008, 1, 2, 2)
5961

60-
def test_with_offset_index(self):
61-
dti = DatetimeIndex([self.d])
62-
result = dti + (self.offset + timedelta(hours=2))
62+
@pytest.mark.parametrize("reverse", [True, False])
63+
@pytest.mark.parametrize(
64+
"td",
65+
[
66+
Timedelta(hours=2),
67+
Timedelta(hours=2).to_pytimedelta(),
68+
Timedelta(hours=2).to_timedelta64(),
69+
],
70+
ids=lambda x: type(x),
71+
)
72+
def test_with_offset_index(self, reverse, td, request):
73+
if reverse and isinstance(td, np.timedelta64):
74+
mark = pytest.mark.xfail(
75+
reason="need __array_priority__, but that causes other errors"
76+
)
77+
request.node.add_marker(mark)
6378

79+
dti = DatetimeIndex([self.d])
6480
expected = DatetimeIndex([datetime(2008, 1, 2, 2)])
81+
82+
if reverse:
83+
result = dti + (td + self.offset)
84+
else:
85+
result = dti + (self.offset + td)
6586
tm.assert_index_equal(result, expected)
6687

6788
def test_eq(self):

pandas/tests/tseries/offsets/test_custom_business_day.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from pandas import (
2121
DatetimeIndex,
22+
Timedelta,
2223
_testing as tm,
2324
read_pickle,
2425
)
@@ -62,11 +63,30 @@ def test_with_offset(self):
6263

6364
assert (self.d + offset) == datetime(2008, 1, 2, 2)
6465

65-
def test_with_offset_index(self):
66-
dti = DatetimeIndex([self.d])
67-
result = dti + (self.offset + timedelta(hours=2))
66+
@pytest.mark.parametrize("reverse", [True, False])
67+
@pytest.mark.parametrize(
68+
"td",
69+
[
70+
Timedelta(hours=2),
71+
Timedelta(hours=2).to_pytimedelta(),
72+
Timedelta(hours=2).to_timedelta64(),
73+
],
74+
ids=lambda x: type(x),
75+
)
76+
def test_with_offset_index(self, reverse, td, request):
77+
if reverse and isinstance(td, np.timedelta64):
78+
mark = pytest.mark.xfail(
79+
reason="need __array_priority__, but that causes other errors"
80+
)
81+
request.node.add_marker(mark)
6882

83+
dti = DatetimeIndex([self.d])
6984
expected = DatetimeIndex([datetime(2008, 1, 2, 2)])
85+
86+
if reverse:
87+
result = dti + (td + self.offset)
88+
else:
89+
result = dti + (self.offset + td)
7090
tm.assert_index_equal(result, expected)
7191

7292
def test_eq(self):

pandas/tests/tseries/offsets/test_ticks.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,16 @@ def test_Nanosecond():
230230
)
231231
def test_tick_addition(kls, expected):
232232
offset = kls(3)
233-
result = offset + Timedelta(hours=2)
234-
assert isinstance(result, Timedelta)
235-
assert result == expected
233+
td = Timedelta(hours=2)
234+
235+
for other in [td, td.to_pytimedelta(), td.to_timedelta64()]:
236+
result = offset + other
237+
assert isinstance(result, Timedelta)
238+
assert result == expected
239+
240+
result = other + offset
241+
assert isinstance(result, Timedelta)
242+
assert result == expected
236243

237244

238245
@pytest.mark.parametrize("cls", tick_classes)

0 commit comments

Comments
 (0)