diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 856aa52f82cf55..e28462f7103b95 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -18,6 +18,7 @@ from numpy cimport int64_t cnp.import_array() +from pandas._libs.tslibs cimport util from pandas._libs.tslibs.util cimport is_string_object, is_integer_object from pandas._libs.tslibs.ccalendar import MONTHS, DAYS @@ -408,6 +409,10 @@ class _BaseOffset(object): return self.apply(other) def __mul__(self, other): + if hasattr(other, "_typ"): + return NotImplemented + if util.is_array(other): + return np.array([self * x for x in other]) return type(self)(n=other * self.n, normalize=self.normalize, **self.kwds) @@ -458,6 +463,9 @@ class _BaseOffset(object): TypeError if `int(n)` raises ValueError if n != int(n) """ + if util.is_timedelta64_object(n): + raise TypeError('`n` argument must be an integer, ' + 'got {ntype}'.format(ntype=type(n))) try: nint = int(n) except (ValueError, TypeError): @@ -533,12 +541,20 @@ class _Tick(object): can do isinstance checks on _Tick and avoid importing tseries.offsets """ + # ensure that reversed-ops with numpy scalars return NotImplemented + __array_priority__ = 1000 + def __truediv__(self, other): result = self.delta.__truediv__(other) return _wrap_timedelta_result(result) + def __rtruediv__(self, other): + result = self.delta.__rtruediv__(other) + return _wrap_timedelta_result(result) + if PY2: __div__ = __truediv__ + __rdiv__ = __rtruediv__ # ---------------------------------------------------------------------- diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index ac3955970587fe..621572da575416 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -257,6 +257,26 @@ def test_offset_n(self, offset_types): mul_offset = offset * 3 assert mul_offset.n == 3 + def test_offset_timedelta64_arg(self, offset_types): + # check that offset._validate_n raises TypeError on a timedelt64 + # object + off = self._get_offset(offset_types) + + td64 = np.timedelta64(4567, 's') + with pytest.raises(TypeError, match="argument must be an integer"): + type(off)(n=td64, **off.kwds) + + def test_offset_mul_ndarray(self, offset_types): + off = self._get_offset(offset_types) + + expected = np.array([[off, off * 2], [off * 3, off * 4]]) + + result = np.array([[1, 2], [3, 4]]) * off + tm.assert_numpy_array_equal(result, expected) + + result = off * np.array([[1, 2], [3, 4]]) + tm.assert_numpy_array_equal(result, expected) + def test_offset_freqstr(self, offset_types): offset = self._get_offset(offset_types) diff --git a/pandas/tests/tseries/offsets/test_ticks.py b/pandas/tests/tseries/offsets/test_ticks.py index f4b012ec1897f9..9a8251201f75fa 100644 --- a/pandas/tests/tseries/offsets/test_ticks.py +++ b/pandas/tests/tseries/offsets/test_ticks.py @@ -11,6 +11,7 @@ import pytest from pandas import Timedelta, Timestamp +import pandas.util.testing as tm from pandas.tseries import offsets from pandas.tseries.offsets import Hour, Micro, Milli, Minute, Nano, Second @@ -262,6 +263,28 @@ def test_tick_division(cls): assert result.delta == off.delta / .001 +@pytest.mark.parametrize('cls', tick_classes) +def test_tick_rdiv(cls): + off = cls(10) + delta = off.delta + td64 = delta.to_timedelta64() + + with pytest.raises(TypeError): + 2 / off + with pytest.raises(TypeError): + 2.0 / off + + assert (td64 * 2.5) / off == 2.5 + + if cls is not Nano: + # skip pytimedelta for Nano since it gets dropped + assert (delta.to_pytimedelta() * 2) / off == 2 + + result = np.array([2 * td64, td64]) / off + expected = np.array([2., 1.]) + tm.assert_numpy_array_equal(result, expected) + + @pytest.mark.parametrize('cls1', tick_classes) @pytest.mark.parametrize('cls2', tick_classes) def test_tick_zero(cls1, cls2):