diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 01650940c4692..5cf0abc27b6b3 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -1016,6 +1016,7 @@ Timedelta - Bug in :class:`Timedelta` constructor failing to raise when passed an invalid keyword (:issue:`53801`) - Bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`) - Bug in multiplication operations with ``timedelta64`` dtype failing to raise ``TypeError`` when multiplying by ``bool`` objects or dtypes (:issue:`58054`) +- Bug in multiplication operations with ``timedelta64`` dtype incorrectly raising when multiplying by numpy-nullable dtypes or pyarrow integer dtypes (:issue:`58054`) Timezones ^^^^^^^^^ diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index c081d6190204e..1647a7f6714ed 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -51,7 +51,11 @@ is_string_dtype, pandas_dtype, ) -from pandas.core.dtypes.dtypes import ExtensionDtype +from pandas.core.dtypes.dtypes import ( + ArrowDtype, + BaseMaskedDtype, + ExtensionDtype, +) from pandas.core.dtypes.missing import isna from pandas.core import ( @@ -501,6 +505,10 @@ def __mul__(self, other) -> Self: f"Cannot multiply '{self.dtype}' by bool, explicitly cast to " "integers instead" ) + if isinstance(other.dtype, (ArrowDtype, BaseMaskedDtype)): + # GH#58054 + return NotImplemented + if len(other) != len(self) and not lib.is_np_dtype(other.dtype, "m"): # Exclude timedelta64 here so we correctly raise TypeError # for that instead of ValueError diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 2361a353f3f8a..7212b93a7c5b7 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -1602,6 +1602,31 @@ def test_td64arr_mul_bool_raises(self, dtype, box_with_array): with pytest.raises(TypeError, match=msg2): other * obj + @pytest.mark.parametrize( + "dtype", + [ + "Int64", + "Float64", + pytest.param("int64[pyarrow]", marks=td.skip_if_no("pyarrow")), + ], + ) + def test_td64arr_mul_masked(self, dtype, box_with_array): + ser = Series(np.arange(5) * timedelta(hours=1)) + obj = tm.box_expected(ser, box_with_array) + + other = Series(np.arange(5), dtype=dtype) + other = tm.box_expected(other, box_with_array) + + expected = Series([Timedelta(hours=n**2) for n in range(5)]) + expected = tm.box_expected(expected, box_with_array) + if dtype == "int64[pyarrow]": + expected = expected.astype("duration[ns][pyarrow]") + + result = obj * other + tm.assert_equal(result, expected) + result = other * obj + tm.assert_equal(result, expected) + # ------------------------------------------------------------------ # __div__, __rdiv__