Skip to content

BUG: Fix timedelta64+Timestamp, closes #24775 #26916

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 25, 2019
2 changes: 2 additions & 0 deletions doc/source/whatsnew/v0.25.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,8 @@ 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`)
- Bug where comparing a zero-dimensional numpy array containing a ``np.datetime64`` object to a :class:`Timestamp` would incorrect raise ``TypeError`` (:issue:`26916`)

Timedelta
^^^^^^^^^
Expand Down
12 changes: 12 additions & 0 deletions pandas/_libs/tslibs/c_timestamp.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -85,6 +88,15 @@ cdef class _Timestamp(datetime):
if ndim == 0:
if is_datetime64_object(other):
other = self.__class__(other)
elif is_array(other):
# zero-dim array, occurs if try comparison with
# datetime64 scalar on the left hand side
# Unfortunately, for datetime64 values, other.item()
# incorrectly returns an integer, so we need to use
# the numpy C api to extract it.
other = cnp.PyArray_ToScalar(cnp.PyArray_DATA(other),
other)
other = self.__class__(other)
else:
return NotImplemented
elif is_array(other):
Expand Down
6 changes: 6 additions & 0 deletions pandas/tests/scalar/timestamp/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a lone test; can u co-locate with same for Timedelta and (or parametrize)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:    @pytest.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__'),

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

# GH#24775 timedelta64+Timestamp should not raise
ts = Timestamp.now()
td = np.timedelta64(3, 'h')
assert td + ts == ts + td
12 changes: 12 additions & 0 deletions pandas/tests/scalar/timestamp/test_comparisons.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,18 @@ def test_timestamp_compare_with_early_datetime(self):
assert stamp < datetime(2700, 1, 1)
assert stamp <= datetime(2700, 1, 1)

def test_compare_zerodim_array(self):
# GH#26916
ts = Timestamp.now()
td64 = np.datetime64('2016-01-01', 'ns')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is odd named td64 no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo, will update

arr = np.array(td64)
assert arr.ndim == 0

result = arr < ts
assert result is True
result = arr > ts
assert result is False


def test_rich_comparison_with_unsupported_type():
# Comparisons with unsupported objects should return NotImplemented
Expand Down