Repository: pandas-dev/pandas
Pull request: BUG: Fix timedelta64+Timestamp, closes #24775
- [x] closes #24775
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Problem statement:
BUG: timedelta64 + Timestamp raises
```
>>> np.timedelta64(3600*10**9, 'ns') + pd.Timestamp.now()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ufunc add cannot use operands with types dtype('<m8[ns]') and dtype('O')
```

I think we can fix this by defining `Timestamp.__array_priority__`

Hints from issue discussion:

Commit message: BUG: Fix timedelta64+Timestamp, closes #24775

Patch to review:
diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst
index 2b1a61186dca6..7bc1ca9b25914 100644
--- a/doc/source/whatsnew/v0.25.0.rst
+++ b/doc/source/whatsnew/v0.25.0.rst
@@ -567,6 +567,7 @@ Datetimelike
 - Bug in :meth:`isin` for datetimelike indexes; :class:`DatetimeIndex`, :class:`TimedeltaIndex` and :class:`PeriodIndex` where the ``levels`` parameter was ignored. (:issue:`26675`)
 - Bug in :func:`to_datetime` which raises ``TypeError`` for ``format='%Y%m%d'`` when called for invalid integer dates with length >= 6 digits with ``errors='ignore'``
 - Bug when comparing a :class:`PeriodIndex` against a zero-dimensional numpy array (:issue:`26689`)
+- Bug where adding :class:`Timestamp` to a ``np.timedelta64`` object would raise instead of returning a :class:`Timestamp` (:issue:24775)
 
 Timedelta
 ^^^^^^^^^
diff --git a/pandas/_libs/tslibs/c_timestamp.pyx b/pandas/_libs/tslibs/c_timestamp.pyx
index 6bf6b6dcea8dd..346dce85451fe 100644
--- a/pandas/_libs/tslibs/c_timestamp.pyx
+++ b/pandas/_libs/tslibs/c_timestamp.pyx
@@ -55,6 +55,9 @@ def maybe_integer_op_deprecated(obj):
 
 cdef class _Timestamp(datetime):
 
+    # higher than np.ndarray and np.matrix
+    __array_priority__ = 100
+
     def __hash__(_Timestamp self):
         if self.nanosecond:
             return hash(self.value)
diff --git a/pandas/tests/scalar/timestamp/test_arithmetic.py b/pandas/tests/scalar/timestamp/test_arithmetic.py
index 21e1dccaefc4b..4bcb93860031e 100644
--- a/pandas/tests/scalar/timestamp/test_arithmetic.py
+++ b/pandas/tests/scalar/timestamp/test_arithmetic.py
@@ -112,3 +112,9 @@ def test_addition_subtraction_preserve_frequency(self):
         td64 = np.timedelta64(1, 'D')
         assert (ts + td64).freq == original_freq
         assert (ts - td64).freq == original_freq
+
+    def test_radd_timedelta64(self):
+        # GH#24775 timedelta64+Timestamp should not raise
+        ts = Timestamp.now()
+        td = np.timedelta64(3, 'h')
+        assert td + ts == ts + td


Code review comments:
diff_hunk: @@ -112,3 +112,9 @@ def test_addition_subtraction_preserve_frequency(self):
         td64 = np.timedelta64(1, 'D')
         assert (ts + td64).freq == original_freq
         assert (ts - td64).freq == original_freq
+
+    def test_radd_timedelta64(self):
path: pandas/tests/scalar/timestamp/test_arithmetic.py
text: @user1:
seems like a lone test; can u  co-locate with same for Timedelta and (or parametrize)



@author:
This is definitely the appropriate place.  I think there are other tests in tests.scalar.timestamp that belong in this file

@user1:
my point is to also include pd.Timedelta here as well via parameterization. I suspect we *already* test this elsewhere, so rather than add a one-off, should add elsewhere (or simply combine / move them here if this is the final destination).

```
(pandas) bash-3.2$ grep -r _radd pandas/tests/ --include '*.py'
pandas/tests//tseries/offsets/test_offsets.py:    def test_radd(self):
pandas/tests//frame/test_query_eval.py:            for op_str, op, rop in [('+', '__add__', '__radd__'),
pandas/tests//arithmetic/test_datetime64.py:        if op_str not in ['__add__', '__radd__', '__sub__']:
pandas/tests//arithmetic/test_datetime64.py:        if op_str not in ['__add__', '__radd__', '__sub__', '__rsub__']:
pandas/tests//arithmetic/test_datetime64.py:    @user2.mark.parametrize('op', ['__add__', '__radd__',
pandas/tests//arithmetic/test_timedelta64.py:    # Tests for timedelta64[ns] __add__, __sub__, __radd__, __rsub__
pandas/tests//arithmetic/test_numeric.py:    # __add__, __sub__, __radd__, __rsub__, __iadd__, __isub__
pandas/tests//arithmetic/test_numeric.py:    def test_series_frame_radd_bug(self):
pandas/tests//arithmetic/test_object.py:    def test_objarr_radd_str(self, box):
pandas/tests//arithmetic/test_object.py:    def test_objarr_radd_str_invalid(self, dtype, data, box):
pandas/tests//arithmetic/test_object.py:    def test_series_with_dtype_radd_timedelta(self, dtype):
pandas/tests//scalar/timedelta/test_arithmetic.py:        __add__, __radd__,
pandas/tests//scalar/timedelta/test_arithmetic.py:            # datetime + Timedelta does _not_ call Timedelta.__radd__,
pandas/tests//indexes/test_category.py:        (lambda idx: ['a', 'b'] + idx, '__radd__'),
```

@author:
> I suspect we already test this elsewhere, so rather than add a one-off, should add elsewhere (or simply combine / move them here if this is the final destination).

well we don't already test this specifically, since this test fails on master.  Most of the grep results posted are for non-scalars; we definitely don't want to start mixing the scalar tests in with those.  I'll collect other Timestamp arithmetic tests that are currently misplaced and put them in this file.


@user1:
> > I suspect we already test this elsewhere, so rather than add a one-off, should add elsewhere (or simply combine / move them here if this is the final destination).
> 
> well we don't already test this specifically, since this test fails on master. Most of the grep results posted are for non-scalars; we definitely don't want to start mixing the scalar tests in with those. I'll collect other Timestamp arithmetic tests that are currently misplaced and put them in this file.

If you read my point is that we certaily test this for ``Timedelta``. so these belong together.

diff_hunk: @@ -567,6 +567,7 @@ Datetimelike
 - Bug in :meth:`isin` for datetimelike indexes; :class:`DatetimeIndex`, :class:`TimedeltaIndex` and :class:`PeriodIndex` where the ``levels`` parameter was ignored. (:issue:`26675`)
 - Bug in :func:`to_datetime` which raises ``TypeError`` for ``format='%Y%m%d'`` when called for invalid integer dates with length >= 6 digits with ``errors='ignore'``
 - Bug when comparing a :class:`PeriodIndex` against a zero-dimensional numpy array (:issue:`26689`)
+- Bug where adding :class:`Timestamp` to a ``np.timedelta64`` object would raise instead of returning a :class:`Timestamp` (:issue:24775)
path: doc/source/whatsnew/v0.25.0.rst
text: @user1:
missing backticks around the issue number