Skip to content

Commit 21dbe7a

Browse files
jbrockmendeljreback
authored andcommitted
Fix Timedelta floordiv, rfloordiv with offset, fix td64 return types (#19770)
1 parent e0f6bea commit 21dbe7a

File tree

3 files changed

+28
-8
lines changed

3 files changed

+28
-8
lines changed

doc/source/whatsnew/v0.23.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,8 @@ Datetimelike
730730
- Bug in :class:`Timestamp` and :func:`to_datetime` where a string representing a barely out-of-bounds timestamp would be incorrectly rounded down instead of raising ``OutOfBoundsDatetime`` (:issue:`19382`)
731731
- Bug in :func:`Timestamp.floor` :func:`DatetimeIndex.floor` where time stamps far in the future and past were not rounded correctly (:issue:`19206`)
732732
- Bug in :func:`to_datetime` where passing an out-of-bounds datetime with ``errors='coerce'`` and ``utc=True`` would raise ``OutOfBoundsDatetime`` instead of parsing to ``NaT`` (:issue:`19612`)
733+
- Bug in :func:`Timedelta.__add__`, :func:`Timedelta.__sub__` where adding or subtracting a ``np.timedelta64`` object would return another ``np.timedelta64`` instead of a ``Timedelta`` (:issue:`19738`)
734+
- Bug in :func:`Timedelta.__floordiv__`, :func:`Timedelta.__rfloordiv__` where operating with a ``Tick`` object would raise a ``TypeError`` instead of returning a numeric value (:issue:`19738`)
733735
-
734736

735737
Timezones

pandas/_libs/tslibs/timedeltas.pyx

+15-1
Original file line numberDiff line numberDiff line change
@@ -478,11 +478,16 @@ def _binary_op_method_timedeltalike(op, name):
478478
elif other is NaT:
479479
return NaT
480480

481+
elif is_timedelta64_object(other):
482+
# convert to Timedelta below; avoid catching this in
483+
# has-dtype check before then
484+
pass
485+
481486
elif is_datetime64_object(other) or PyDateTime_CheckExact(other):
482487
# the PyDateTime_CheckExact case is for a datetime object that
483488
# is specifically *not* a Timestamp, as the Timestamp case will be
484489
# handled after `_validate_ops_compat` returns False below
485-
from ..tslib import Timestamp
490+
from timestamps import Timestamp
486491
return op(self, Timestamp(other))
487492
# We are implicitly requiring the canonical behavior to be
488493
# defined by Timestamp methods.
@@ -503,6 +508,9 @@ def _binary_op_method_timedeltalike(op, name):
503508
# failed to parse as timedelta
504509
return NotImplemented
505510

511+
if other is NaT:
512+
# e.g. if original other was timedelta64('NaT')
513+
return NaT
506514
return Timedelta(op(self.value, other.value), unit='ns')
507515

508516
f.__name__ = name
@@ -1096,6 +1104,9 @@ class Timedelta(_Timedelta):
10961104
# just defer
10971105
if hasattr(other, '_typ'):
10981106
# Series, DataFrame, ...
1107+
if other._typ == 'dateoffset' and hasattr(other, 'delta'):
1108+
# Tick offset
1109+
return self // other.delta
10991110
return NotImplemented
11001111

11011112
if hasattr(other, 'dtype'):
@@ -1128,6 +1139,9 @@ class Timedelta(_Timedelta):
11281139
# just defer
11291140
if hasattr(other, '_typ'):
11301141
# Series, DataFrame, ...
1142+
if other._typ == 'dateoffset' and hasattr(other, 'delta'):
1143+
# Tick offset
1144+
return other.delta // self
11311145
return NotImplemented
11321146

11331147
if hasattr(other, 'dtype'):

pandas/tests/scalar/timedelta/test_arithmetic.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ def test_td_add_pytimedelta(self, op):
100100
assert isinstance(result, Timedelta)
101101
assert result == Timedelta(days=19)
102102

103-
@pytest.mark.xfail(reason='GH#19738 argument not converted to Timedelta')
104103
@pytest.mark.parametrize('op', [operator.add, ops.radd])
105104
def test_td_add_timedelta64(self, op):
106105
td = Timedelta(10, unit='d')
@@ -130,21 +129,18 @@ def test_td_sub_pytimedelta(self):
130129
assert isinstance(result, Timedelta)
131130
assert result == expected
132131

133-
@pytest.mark.xfail(reason='GH#19738 argument not converted to Timedelta')
134132
def test_td_sub_timedelta64(self):
135133
td = Timedelta(10, unit='d')
136134
expected = Timedelta(0, unit='ns')
137135
result = td - td.to_timedelta64()
138136
assert isinstance(result, Timedelta)
139-
# comparison fails even if we comment out the isinstance assertion
140137
assert result == expected
141138

142139
def test_td_sub_nat(self):
143140
td = Timedelta(10, unit='d')
144141
result = td - NaT
145142
assert result is NaT
146143

147-
@pytest.mark.xfail(reason='GH#19738 argument not converted to Timedelta')
148144
def test_td_sub_td64_nat(self):
149145
td = Timedelta(10, unit='d')
150146
result = td - np.timedelta64('NaT')
@@ -171,7 +167,6 @@ def test_td_rsub_pytimedelta(self):
171167
assert isinstance(result, Timedelta)
172168
assert result == expected
173169

174-
@pytest.mark.xfail(reason='GH#19738 argument not converted to Timedelta')
175170
def test_td_rsub_timedelta64(self):
176171
td = Timedelta(10, unit='d')
177172
expected = Timedelta(0, unit='ns')
@@ -188,7 +183,6 @@ def test_td_rsub_nat(self):
188183
result = np.datetime64('NaT') - td
189184
assert result is NaT
190185

191-
@pytest.mark.xfail(reason='GH#19738 argument not converted to Timedelta')
192186
def test_td_rsub_td64_nat(self):
193187
td = Timedelta(10, unit='d')
194188
result = np.timedelta64('NaT') - td
@@ -304,6 +298,12 @@ def test_td_floordiv_null_scalar(self):
304298
assert np.isnan(td // NaT)
305299
assert np.isnan(td // np.timedelta64('NaT'))
306300

301+
def test_td_floordiv_offsets(self):
302+
# GH#19738
303+
td = Timedelta(hours=3, minutes=4)
304+
assert td // pd.offsets.Hour(1) == 3
305+
assert td // pd.offsets.Minute(2) == 92
306+
307307
def test_td_floordiv_invalid_scalar(self):
308308
# GH#18846
309309
td = Timedelta(hours=3, minutes=4)
@@ -322,7 +322,7 @@ def test_td_floordiv_numeric_scalar(self):
322322
assert td // np.int32(2.0) == expected
323323
assert td // np.uint8(2.0) == expected
324324

325-
def test_floordiv_timedeltalike_array(self):
325+
def test_td_floordiv_timedeltalike_array(self):
326326
# GH#18846
327327
td = Timedelta(hours=3, minutes=4)
328328
scalar = Timedelta(hours=3, minutes=3)
@@ -371,6 +371,10 @@ def test_td_rfloordiv_null_scalar(self):
371371
assert np.isnan(td.__rfloordiv__(NaT))
372372
assert np.isnan(td.__rfloordiv__(np.timedelta64('NaT')))
373373

374+
def test_td_rfloordiv_offsets(self):
375+
# GH#19738
376+
assert pd.offsets.Hour(1) // Timedelta(minutes=25) == 2
377+
374378
def test_td_rfloordiv_invalid_scalar(self):
375379
# GH#18846
376380
td = Timedelta(hours=3, minutes=3)

0 commit comments

Comments
 (0)