Skip to content

Commit 97cbb42

Browse files
jbrockmendelyehoshuadimarsky
authored andcommitted
ENH: Timedelta division support non-nano (pandas-dev#47373)
1 parent 726cd01 commit 97cbb42

File tree

2 files changed

+93
-17
lines changed

2 files changed

+93
-17
lines changed

pandas/_libs/tslibs/timedeltas.pyx

+35-17
Original file line numberDiff line numberDiff line change
@@ -1742,13 +1742,21 @@ class Timedelta(_Timedelta):
17421742
other = Timedelta(other)
17431743
if other is NaT:
17441744
return np.nan
1745+
if other._reso != self._reso:
1746+
raise ValueError(
1747+
"division between Timedeltas with mismatched resolutions "
1748+
"are not supported. Explicitly cast to matching resolutions "
1749+
"before dividing."
1750+
)
17451751
return self.value / float(other.value)
17461752

17471753
elif is_integer_object(other) or is_float_object(other):
17481754
# integers or floats
1749-
if self._reso != NPY_FR_ns:
1750-
raise NotImplementedError
1751-
return Timedelta(self.value / other, unit='ns')
1755+
if util.is_nan(other):
1756+
return NaT
1757+
return Timedelta._from_value_and_reso(
1758+
<int64_t>(self.value / other), self._reso
1759+
)
17521760

17531761
elif is_array(other):
17541762
return self.to_timedelta64() / other
@@ -1761,8 +1769,12 @@ class Timedelta(_Timedelta):
17611769
other = Timedelta(other)
17621770
if other is NaT:
17631771
return np.nan
1764-
if self._reso != NPY_FR_ns:
1765-
raise NotImplementedError
1772+
if self._reso != other._reso:
1773+
raise ValueError(
1774+
"division between Timedeltas with mismatched resolutions "
1775+
"are not supported. Explicitly cast to matching resolutions "
1776+
"before dividing."
1777+
)
17661778
return float(other.value) / self.value
17671779

17681780
elif is_array(other):
@@ -1781,14 +1793,18 @@ class Timedelta(_Timedelta):
17811793
other = Timedelta(other)
17821794
if other is NaT:
17831795
return np.nan
1784-
if self._reso != NPY_FR_ns:
1785-
raise NotImplementedError
1796+
if self._reso != other._reso:
1797+
raise ValueError(
1798+
"floordivision between Timedeltas with mismatched resolutions "
1799+
"are not supported. Explicitly cast to matching resolutions "
1800+
"before dividing."
1801+
)
17861802
return self.value // other.value
17871803

17881804
elif is_integer_object(other) or is_float_object(other):
1789-
if self._reso != NPY_FR_ns:
1790-
raise NotImplementedError
1791-
return Timedelta(self.value // other, unit='ns')
1805+
if util.is_nan(other):
1806+
return NaT
1807+
return type(self)._from_value_and_reso(self.value // other, self._reso)
17921808

17931809
elif is_array(other):
17941810
if other.dtype.kind == 'm':
@@ -1798,9 +1814,7 @@ class Timedelta(_Timedelta):
17981814
return _broadcast_floordiv_td64(self.value, other, _floordiv)
17991815
elif other.dtype.kind in ['i', 'u', 'f']:
18001816
if other.ndim == 0:
1801-
if self._reso != NPY_FR_ns:
1802-
raise NotImplementedError
1803-
return Timedelta(self.value // other)
1817+
return self // other.item()
18041818
else:
18051819
return self.to_timedelta64() // other
18061820

@@ -1816,8 +1830,12 @@ class Timedelta(_Timedelta):
18161830
other = Timedelta(other)
18171831
if other is NaT:
18181832
return np.nan
1819-
if self._reso != NPY_FR_ns:
1820-
raise NotImplementedError
1833+
if self._reso != other._reso:
1834+
raise ValueError(
1835+
"floordivision between Timedeltas with mismatched resolutions "
1836+
"are not supported. Explicitly cast to matching resolutions "
1837+
"before dividing."
1838+
)
18211839
return other.value // self.value
18221840

18231841
elif is_array(other):
@@ -1914,10 +1932,10 @@ cdef _broadcast_floordiv_td64(
19141932
if mask:
19151933
return np.nan
19161934

1917-
return operation(value, other.astype('m8[ns]').astype('i8'))
1935+
return operation(value, other.astype('m8[ns]', copy=False).astype('i8'))
19181936

19191937
else:
1920-
res = operation(value, other.astype('m8[ns]').astype('i8'))
1938+
res = operation(value, other.astype('m8[ns]', copy=False).astype('i8'))
19211939

19221940
if mask.any():
19231941
res = res.astype('f8')

pandas/tests/scalar/timedelta/test_timedelta.py

+58
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,64 @@ def test_to_timedelta64(self, td, unit):
173173
elif unit == 9:
174174
assert res.dtype == "m8[us]"
175175

176+
def test_truediv_timedeltalike(self, td):
177+
assert td / td == 1
178+
assert (2.5 * td) / td == 2.5
179+
180+
other = Timedelta(td.value)
181+
msg = "with mismatched resolutions are not supported"
182+
with pytest.raises(ValueError, match=msg):
183+
td / other
184+
185+
with pytest.raises(ValueError, match=msg):
186+
# __rtruediv__
187+
other.to_pytimedelta() / td
188+
189+
def test_truediv_numeric(self, td):
190+
assert td / np.nan is NaT
191+
192+
res = td / 2
193+
assert res.value == td.value / 2
194+
assert res._reso == td._reso
195+
196+
res = td / 2.0
197+
assert res.value == td.value / 2
198+
assert res._reso == td._reso
199+
200+
def test_floordiv_timedeltalike(self, td):
201+
assert td // td == 1
202+
assert (2.5 * td) // td == 2
203+
204+
other = Timedelta(td.value)
205+
msg = "with mismatched resolutions are not supported"
206+
with pytest.raises(ValueError, match=msg):
207+
td // other
208+
209+
with pytest.raises(ValueError, match=msg):
210+
# __rfloordiv__
211+
other.to_pytimedelta() // td
212+
213+
def test_floordiv_numeric(self, td):
214+
assert td // np.nan is NaT
215+
216+
res = td // 2
217+
assert res.value == td.value // 2
218+
assert res._reso == td._reso
219+
220+
res = td // 2.0
221+
assert res.value == td.value // 2
222+
assert res._reso == td._reso
223+
224+
assert td // np.array(np.nan) is NaT
225+
226+
res = td // np.array(2)
227+
assert res.value == td.value // 2
228+
assert res._reso == td._reso
229+
230+
res = td // np.array(2.0)
231+
assert res.value == td.value // 2
232+
assert res._reso == td._reso
233+
176234

177235
class TestTimedeltaUnaryOps:
178236
def test_invert(self):

0 commit comments

Comments
 (0)