Skip to content

Commit

Permalink
BUG: Tick + np.timedelta64 (#44474)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel committed Nov 16, 2021
1 parent b314627 commit fa28c61
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 17 deletions.
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.4.0.rst
Expand Up @@ -540,7 +540,7 @@ Datetimelike
- Bug in inplace addition and subtraction of :class:`DatetimeIndex` or :class:`TimedeltaIndex` with :class:`DatetimeArray` or :class:`TimedeltaArray` (:issue:`43904`)
- Bug in in calling ``np.isnan``, ``np.isfinite``, or ``np.isinf`` on a timezone-aware :class:`DatetimeIndex` incorrectly raising ``TypeError`` (:issue:`43917`)
- Bug in constructing a :class:`Series` from datetime-like strings with mixed timezones incorrectly partially-inferring datetime values (:issue:`40111`)
-
- Bug in addition with a :class:`Tick` object and a ``np.timedelta64`` object incorrectly raising instead of returning :class:`Timedelta` (:issue:`44474`)

Timedelta
^^^^^^^^^
Expand Down
23 changes: 16 additions & 7 deletions pandas/_libs/tslibs/offsets.pyx
Expand Up @@ -72,7 +72,10 @@ from pandas._libs.tslibs.np_datetime cimport (
from pandas._libs.tslibs.tzconversion cimport tz_convert_from_utc_single

from .dtypes cimport PeriodDtypeCode
from .timedeltas cimport delta_to_nanoseconds
from .timedeltas cimport (
delta_to_nanoseconds,
is_any_td_scalar,
)

from .timedeltas import Timedelta

Expand Down Expand Up @@ -154,7 +157,11 @@ def apply_wraps(func):

if other is NaT:
return NaT
elif isinstance(other, BaseOffset) or PyDelta_Check(other):
elif (
isinstance(other, BaseOffset)
or PyDelta_Check(other)
or util.is_timedelta64_object(other)
):
# timedelta path
return func(self, other)
elif is_datetime64_object(other) or PyDate_Check(other):
Expand Down Expand Up @@ -902,7 +909,7 @@ cdef class Tick(SingleConstructorOffset):
# PyDate_Check includes date, datetime
return Timestamp(other) + self

if PyDelta_Check(other):
if util.is_timedelta64_object(other) or PyDelta_Check(other):
return other + self.delta
elif isinstance(other, type(self)):
# TODO: this is reached in tests that specifically call apply,
Expand Down Expand Up @@ -1396,9 +1403,10 @@ cdef class BusinessDay(BusinessMixin):
result = result + self.offset
return result

elif PyDelta_Check(other) or isinstance(other, Tick):
elif is_any_td_scalar(other):
td = Timedelta(self.offset) + other
return BusinessDay(
self.n, offset=self.offset + other, normalize=self.normalize
self.n, offset=td.to_pytimedelta(), normalize=self.normalize
)
else:
raise ApplyTypeError(
Expand Down Expand Up @@ -3265,8 +3273,9 @@ cdef class CustomBusinessDay(BusinessDay):
result = result + self.offset
return result

elif PyDelta_Check(other) or isinstance(other, Tick):
return BDay(self.n, offset=self.offset + other, normalize=self.normalize)
elif is_any_td_scalar(other):
td = Timedelta(self.offset) + other
return BDay(self.n, offset=td.to_pytimedelta(), normalize=self.normalize)
else:
raise ApplyTypeError(
"Only know how to combine trading day with "
Expand Down
27 changes: 24 additions & 3 deletions pandas/tests/tseries/offsets/test_business_day.py
Expand Up @@ -7,6 +7,7 @@
timedelta,
)

import numpy as np
import pytest

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

from pandas import (
DatetimeIndex,
Timedelta,
_testing as tm,
)
from pandas.tests.tseries.offsets.common import (
Expand Down Expand Up @@ -57,11 +59,30 @@ def test_with_offset(self):

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

def test_with_offset_index(self):
dti = DatetimeIndex([self.d])
result = dti + (self.offset + timedelta(hours=2))
@pytest.mark.parametrize("reverse", [True, False])
@pytest.mark.parametrize(
"td",
[
Timedelta(hours=2),
Timedelta(hours=2).to_pytimedelta(),
Timedelta(hours=2).to_timedelta64(),
],
ids=lambda x: type(x),
)
def test_with_offset_index(self, reverse, td, request):
if reverse and isinstance(td, np.timedelta64):
mark = pytest.mark.xfail(
reason="need __array_priority__, but that causes other errors"
)
request.node.add_marker(mark)

dti = DatetimeIndex([self.d])
expected = DatetimeIndex([datetime(2008, 1, 2, 2)])

if reverse:
result = dti + (td + self.offset)
else:
result = dti + (self.offset + td)
tm.assert_index_equal(result, expected)

def test_eq(self):
Expand Down
26 changes: 23 additions & 3 deletions pandas/tests/tseries/offsets/test_custom_business_day.py
Expand Up @@ -19,6 +19,7 @@

from pandas import (
DatetimeIndex,
Timedelta,
_testing as tm,
read_pickle,
)
Expand Down Expand Up @@ -62,11 +63,30 @@ def test_with_offset(self):

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

def test_with_offset_index(self):
dti = DatetimeIndex([self.d])
result = dti + (self.offset + timedelta(hours=2))
@pytest.mark.parametrize("reverse", [True, False])
@pytest.mark.parametrize(
"td",
[
Timedelta(hours=2),
Timedelta(hours=2).to_pytimedelta(),
Timedelta(hours=2).to_timedelta64(),
],
ids=lambda x: type(x),
)
def test_with_offset_index(self, reverse, td, request):
if reverse and isinstance(td, np.timedelta64):
mark = pytest.mark.xfail(
reason="need __array_priority__, but that causes other errors"
)
request.node.add_marker(mark)

dti = DatetimeIndex([self.d])
expected = DatetimeIndex([datetime(2008, 1, 2, 2)])

if reverse:
result = dti + (td + self.offset)
else:
result = dti + (self.offset + td)
tm.assert_index_equal(result, expected)

def test_eq(self):
Expand Down
13 changes: 10 additions & 3 deletions pandas/tests/tseries/offsets/test_ticks.py
Expand Up @@ -230,9 +230,16 @@ def test_Nanosecond():
)
def test_tick_addition(kls, expected):
offset = kls(3)
result = offset + Timedelta(hours=2)
assert isinstance(result, Timedelta)
assert result == expected
td = Timedelta(hours=2)

for other in [td, td.to_pytimedelta(), td.to_timedelta64()]:
result = offset + other
assert isinstance(result, Timedelta)
assert result == expected

result = other + offset
assert isinstance(result, Timedelta)
assert result == expected


@pytest.mark.parametrize("cls", tick_classes)
Expand Down

0 comments on commit fa28c61

Please sign in to comment.