Skip to content

Fix Timedelta floordiv, rfloordiv with offset, fix td64 return types #19770

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.23.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,8 @@ Datetimelike
- 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`)
- Bug in :func:`Timestamp.floor` :func:`DatetimeIndex.floor` where time stamps far in the future and past were not rounded correctly (:issue:`19206`)
- 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`)
- 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`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would not be averse to a PR separating this whatsnew to Datetime, Period, Timedelta sections (so each is not so long)

- 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`)
-

Timezones
Expand Down
16 changes: 15 additions & 1 deletion pandas/_libs/tslibs/timedeltas.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -478,11 +478,16 @@ def _binary_op_method_timedeltalike(op, name):
elif other is NaT:
return NaT

elif is_timedelta64_object(other):
# convert to Timedelta below; avoid catching this in
# has-dtype check before then
pass

elif is_datetime64_object(other) or PyDateTime_CheckExact(other):
# the PyDateTime_CheckExact case is for a datetime object that
# is specifically *not* a Timestamp, as the Timestamp case will be
# handled after `_validate_ops_compat` returns False below
from ..tslib import Timestamp
from timestamps import Timestamp
return op(self, Timestamp(other))
# We are implicitly requiring the canonical behavior to be
# defined by Timestamp methods.
Expand All @@ -503,6 +508,9 @@ def _binary_op_method_timedeltalike(op, name):
# failed to parse as timedelta
return NotImplemented

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

f.__name__ = name
Expand Down Expand Up @@ -1096,6 +1104,9 @@ class Timedelta(_Timedelta):
# just defer
if hasattr(other, '_typ'):
# Series, DataFrame, ...
if other._typ == 'dateoffset' and hasattr(other, 'delta'):
# Tick offset
return self // other.delta
return NotImplemented

if hasattr(other, 'dtype'):
Expand Down Expand Up @@ -1128,6 +1139,9 @@ class Timedelta(_Timedelta):
# just defer
if hasattr(other, '_typ'):
# Series, DataFrame, ...
if other._typ == 'dateoffset' and hasattr(other, 'delta'):
# Tick offset
return other.delta // self
return NotImplemented

if hasattr(other, 'dtype'):
Expand Down
18 changes: 11 additions & 7 deletions pandas/tests/scalar/timedelta/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ def test_td_add_pytimedelta(self, op):
assert isinstance(result, Timedelta)
assert result == Timedelta(days=19)

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

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

def test_td_sub_nat(self):
td = Timedelta(10, unit='d')
result = td - NaT
assert result is NaT

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

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

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

def test_td_floordiv_offsets(self):
# GH#19738
td = Timedelta(hours=3, minutes=4)
assert td // pd.offsets.Hour(1) == 3
assert td // pd.offsets.Minute(2) == 92

def test_td_floordiv_invalid_scalar(self):
# GH#18846
td = Timedelta(hours=3, minutes=4)
Expand All @@ -322,7 +322,7 @@ def test_td_floordiv_numeric_scalar(self):
assert td // np.int32(2.0) == expected
assert td // np.uint8(2.0) == expected

def test_floordiv_timedeltalike_array(self):
def test_td_floordiv_timedeltalike_array(self):
# GH#18846
td = Timedelta(hours=3, minutes=4)
scalar = Timedelta(hours=3, minutes=3)
Expand Down Expand Up @@ -371,6 +371,10 @@ def test_td_rfloordiv_null_scalar(self):
assert np.isnan(td.__rfloordiv__(NaT))
assert np.isnan(td.__rfloordiv__(np.timedelta64('NaT')))

def test_td_rfloordiv_offsets(self):
# GH#19738
assert pd.offsets.Hour(1) // Timedelta(minutes=25) == 2

def test_td_rfloordiv_invalid_scalar(self):
# GH#18846
td = Timedelta(hours=3, minutes=3)
Expand Down