Skip to content

Commit f0919f2

Browse files
jbrockmendeljreback
authored andcommitted
BUG: Fix timedelta64+Timestamp, closes #24775 (#26916)
1 parent 606178a commit f0919f2

File tree

5 files changed

+46
-25
lines changed

5 files changed

+46
-25
lines changed

doc/source/whatsnew/v0.25.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,8 @@ Datetimelike
603603
- Bug when comparing a :class:`PeriodIndex` against a zero-dimensional numpy array (:issue:`26689`)
604604
- Bug in constructing a ``Series`` or ``DataFrame`` from a numpy ``datetime64`` array with a non-ns unit and out-of-bound timestamps generating rubbish data, which will now correctly raise an ``OutOfBoundsDatetime`` error (:issue:`26206`).
605605
- Bug in :func:`date_range` with unnecessary ``OverflowError`` being raised for very large or very small dates (:issue:`26651`)
606+
- Bug where adding :class:`Timestamp` to a ``np.timedelta64`` object would raise instead of returning a :class:`Timestamp` (:issue:`24775`)
607+
- Bug where comparing a zero-dimensional numpy array containing a ``np.datetime64`` object to a :class:`Timestamp` would incorrect raise ``TypeError`` (:issue:`26916`)
606608

607609
Timedelta
608610
^^^^^^^^^

pandas/_libs/tslibs/c_timestamp.pyx

+12
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def maybe_integer_op_deprecated(obj):
5555

5656
cdef class _Timestamp(datetime):
5757

58+
# higher than np.ndarray and np.matrix
59+
__array_priority__ = 100
60+
5861
def __hash__(_Timestamp self):
5962
if self.nanosecond:
6063
return hash(self.value)
@@ -85,6 +88,15 @@ cdef class _Timestamp(datetime):
8588
if ndim == 0:
8689
if is_datetime64_object(other):
8790
other = self.__class__(other)
91+
elif is_array(other):
92+
# zero-dim array, occurs if try comparison with
93+
# datetime64 scalar on the left hand side
94+
# Unfortunately, for datetime64 values, other.item()
95+
# incorrectly returns an integer, so we need to use
96+
# the numpy C api to extract it.
97+
other = cnp.PyArray_ToScalar(cnp.PyArray_DATA(other),
98+
other)
99+
other = self.__class__(other)
88100
else:
89101
return NotImplemented
90102
elif is_array(other):

pandas/tests/scalar/timestamp/test_arithmetic.py

+20
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,23 @@ def test_addition_subtraction_preserve_frequency(self):
112112
td64 = np.timedelta64(1, 'D')
113113
assert (ts + td64).freq == original_freq
114114
assert (ts - td64).freq == original_freq
115+
116+
@pytest.mark.parametrize('td', [Timedelta(hours=3),
117+
np.timedelta64(3, 'h'),
118+
timedelta(hours=3)])
119+
def test_radd_tdscalar(self, td):
120+
# GH#24775 timedelta64+Timestamp should not raise
121+
ts = Timestamp.now()
122+
assert td + ts == ts + td
123+
124+
@pytest.mark.parametrize('other,expected_difference', [
125+
(np.timedelta64(-123, 'ns'), -123),
126+
(np.timedelta64(1234567898, 'ns'), 1234567898),
127+
(np.timedelta64(-123, 'us'), -123000),
128+
(np.timedelta64(-123, 'ms'), -123000000)
129+
])
130+
def test_timestamp_add_timedelta64_unit(self, other, expected_difference):
131+
ts = Timestamp(datetime.utcnow())
132+
result = ts + other
133+
valdiff = result.value - ts.value
134+
assert valdiff == expected_difference

pandas/tests/scalar/timestamp/test_comparisons.py

+12
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,18 @@ def test_timestamp_compare_with_early_datetime(self):
156156
assert stamp < datetime(2700, 1, 1)
157157
assert stamp <= datetime(2700, 1, 1)
158158

159+
def test_compare_zerodim_array(self):
160+
# GH#26916
161+
ts = Timestamp.now()
162+
dt64 = np.datetime64('2016-01-01', 'ns')
163+
arr = np.array(dt64)
164+
assert arr.ndim == 0
165+
166+
result = arr < ts
167+
assert result is True
168+
result = arr > ts
169+
assert result is False
170+
159171

160172
def test_rich_comparison_with_unsupported_type():
161173
# Comparisons with unsupported objects should return NotImplemented

pandas/tests/scalar/timestamp/test_timestamp.py

-25
Original file line numberDiff line numberDiff line change
@@ -796,31 +796,6 @@ def test_tz_conversion_freq(self, tz_naive_fixture):
796796

797797
class TestTimestampNsOperations:
798798

799-
def setup_method(self, method):
800-
self.timestamp = Timestamp(datetime.utcnow())
801-
802-
def assert_ns_timedelta(self, modified_timestamp, expected_value):
803-
value = self.timestamp.value
804-
modified_value = modified_timestamp.value
805-
806-
assert modified_value - value == expected_value
807-
808-
def test_timedelta_ns_arithmetic(self):
809-
self.assert_ns_timedelta(self.timestamp + np.timedelta64(-123, 'ns'),
810-
-123)
811-
812-
def test_timedelta_ns_based_arithmetic(self):
813-
self.assert_ns_timedelta(self.timestamp + np.timedelta64(
814-
1234567898, 'ns'), 1234567898)
815-
816-
def test_timedelta_us_arithmetic(self):
817-
self.assert_ns_timedelta(self.timestamp + np.timedelta64(-123, 'us'),
818-
-123000)
819-
820-
def test_timedelta_ms_arithmetic(self):
821-
time = self.timestamp + np.timedelta64(-123, 'ms')
822-
self.assert_ns_timedelta(time, -123000000)
823-
824799
def test_nanosecond_string_parsing(self):
825800
ts = Timestamp('2013-05-01 07:15:45.123456789')
826801
# GH 7878

0 commit comments

Comments
 (0)