From ac9df7d42d6994db38e3a092597aac1f97d607c4 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 15 Jun 2022 11:59:20 -0700 Subject: [PATCH] ENH: Timedelta division support non-nano --- pandas/_libs/tslibs/timedeltas.pyx | 52 +++++++++++------ .../tests/scalar/timedelta/test_timedelta.py | 58 +++++++++++++++++++ 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 028371633a2c1..dfd64dd50f213 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -1742,13 +1742,21 @@ class Timedelta(_Timedelta): other = Timedelta(other) if other is NaT: return np.nan + if other._reso != self._reso: + raise ValueError( + "division between Timedeltas with mismatched resolutions " + "are not supported. Explicitly cast to matching resolutions " + "before dividing." + ) return self.value / float(other.value) elif is_integer_object(other) or is_float_object(other): # integers or floats - if self._reso != NPY_FR_ns: - raise NotImplementedError - return Timedelta(self.value / other, unit='ns') + if util.is_nan(other): + return NaT + return Timedelta._from_value_and_reso( + (self.value / other), self._reso + ) elif is_array(other): return self.to_timedelta64() / other @@ -1761,8 +1769,12 @@ class Timedelta(_Timedelta): other = Timedelta(other) if other is NaT: return np.nan - if self._reso != NPY_FR_ns: - raise NotImplementedError + if self._reso != other._reso: + raise ValueError( + "division between Timedeltas with mismatched resolutions " + "are not supported. Explicitly cast to matching resolutions " + "before dividing." + ) return float(other.value) / self.value elif is_array(other): @@ -1781,14 +1793,18 @@ class Timedelta(_Timedelta): other = Timedelta(other) if other is NaT: return np.nan - if self._reso != NPY_FR_ns: - raise NotImplementedError + if self._reso != other._reso: + raise ValueError( + "floordivision between Timedeltas with mismatched resolutions " + "are not supported. Explicitly cast to matching resolutions " + "before dividing." + ) return self.value // other.value elif is_integer_object(other) or is_float_object(other): - if self._reso != NPY_FR_ns: - raise NotImplementedError - return Timedelta(self.value // other, unit='ns') + if util.is_nan(other): + return NaT + return type(self)._from_value_and_reso(self.value // other, self._reso) elif is_array(other): if other.dtype.kind == 'm': @@ -1798,9 +1814,7 @@ class Timedelta(_Timedelta): return _broadcast_floordiv_td64(self.value, other, _floordiv) elif other.dtype.kind in ['i', 'u', 'f']: if other.ndim == 0: - if self._reso != NPY_FR_ns: - raise NotImplementedError - return Timedelta(self.value // other) + return self // other.item() else: return self.to_timedelta64() // other @@ -1816,8 +1830,12 @@ class Timedelta(_Timedelta): other = Timedelta(other) if other is NaT: return np.nan - if self._reso != NPY_FR_ns: - raise NotImplementedError + if self._reso != other._reso: + raise ValueError( + "floordivision between Timedeltas with mismatched resolutions " + "are not supported. Explicitly cast to matching resolutions " + "before dividing." + ) return other.value // self.value elif is_array(other): @@ -1914,10 +1932,10 @@ cdef _broadcast_floordiv_td64( if mask: return np.nan - return operation(value, other.astype('m8[ns]').astype('i8')) + return operation(value, other.astype('m8[ns]', copy=False).astype('i8')) else: - res = operation(value, other.astype('m8[ns]').astype('i8')) + res = operation(value, other.astype('m8[ns]', copy=False).astype('i8')) if mask.any(): res = res.astype('f8') diff --git a/pandas/tests/scalar/timedelta/test_timedelta.py b/pandas/tests/scalar/timedelta/test_timedelta.py index 5ae6ed9f13ece..00072e6724a6b 100644 --- a/pandas/tests/scalar/timedelta/test_timedelta.py +++ b/pandas/tests/scalar/timedelta/test_timedelta.py @@ -173,6 +173,64 @@ def test_to_timedelta64(self, td, unit): elif unit == 9: assert res.dtype == "m8[us]" + def test_truediv_timedeltalike(self, td): + assert td / td == 1 + assert (2.5 * td) / td == 2.5 + + other = Timedelta(td.value) + msg = "with mismatched resolutions are not supported" + with pytest.raises(ValueError, match=msg): + td / other + + with pytest.raises(ValueError, match=msg): + # __rtruediv__ + other.to_pytimedelta() / td + + def test_truediv_numeric(self, td): + assert td / np.nan is NaT + + res = td / 2 + assert res.value == td.value / 2 + assert res._reso == td._reso + + res = td / 2.0 + assert res.value == td.value / 2 + assert res._reso == td._reso + + def test_floordiv_timedeltalike(self, td): + assert td // td == 1 + assert (2.5 * td) // td == 2 + + other = Timedelta(td.value) + msg = "with mismatched resolutions are not supported" + with pytest.raises(ValueError, match=msg): + td // other + + with pytest.raises(ValueError, match=msg): + # __rfloordiv__ + other.to_pytimedelta() // td + + def test_floordiv_numeric(self, td): + assert td // np.nan is NaT + + res = td // 2 + assert res.value == td.value // 2 + assert res._reso == td._reso + + res = td // 2.0 + assert res.value == td.value // 2 + assert res._reso == td._reso + + assert td // np.array(np.nan) is NaT + + res = td // np.array(2) + assert res.value == td.value // 2 + assert res._reso == td._reso + + res = td // np.array(2.0) + assert res.value == td.value // 2 + assert res._reso == td._reso + class TestTimedeltaUnaryOps: def test_invert(self):