diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index ec8478f3fc82a..ddf8813c9f971 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -370,9 +370,6 @@ cdef class _Timestamp(ABCTimestamp): cdef: int64_t nanos = 0 - if isinstance(self, _Timestamp) and self._reso != NPY_FR_ns: - raise NotImplementedError(self._reso) - if is_any_td_scalar(other): if is_timedelta64_object(other): other_reso = get_datetime64_unit(other) @@ -390,20 +387,31 @@ cdef class _Timestamp(ABCTimestamp): # TODO: no tests get here other = ensure_td64ns(other) + # TODO: what to do with mismatched resos? # TODO: disallow round_ok nanos = delta_to_nanoseconds( other, reso=self._reso, round_ok=True ) try: - result = type(self)(self.value + nanos, tz=self.tzinfo) + new_value = self.value + nanos except OverflowError: # Use Python ints # Hit in test_tdi_add_overflow - result = type(self)(int(self.value) + int(nanos), tz=self.tzinfo) + new_value = int(self.value) + int(nanos) + + try: + result = type(self)._from_value_and_reso(new_value, reso=self._reso, tz=self.tzinfo) + except OverflowError as err: + # TODO: don't hard-code nanosecond here + raise OutOfBoundsDatetime(f"Out of bounds nanosecond timestamp: {new_value}") from err + if result is not NaT: result._set_freq(self._freq) # avoid warning in constructor return result + elif isinstance(self, _Timestamp) and self._reso != NPY_FR_ns: + raise NotImplementedError(self._reso) + elif is_integer_object(other): raise integer_op_not_supported(self) @@ -431,13 +439,16 @@ cdef class _Timestamp(ABCTimestamp): return NotImplemented def __sub__(self, other): - if isinstance(self, _Timestamp) and self._reso != NPY_FR_ns: - raise NotImplementedError(self._reso) + if other is NaT: + return NaT - if is_any_td_scalar(other) or is_integer_object(other): + elif is_any_td_scalar(other) or is_integer_object(other): neg_other = -other return self + neg_other + elif isinstance(self, _Timestamp) and self._reso != NPY_FR_ns: + raise NotImplementedError(self._reso) + elif is_array(other): if other.dtype.kind in ['i', 'u']: raise integer_op_not_supported(self) @@ -450,9 +461,6 @@ cdef class _Timestamp(ABCTimestamp): ) return NotImplemented - if other is NaT: - return NaT - # coerce if necessary if we are a Timestamp-like if (PyDateTime_Check(self) and (PyDateTime_Check(other) or is_datetime64_object(other))): diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 292c8f95ec2e6..5214223c3a656 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -853,6 +853,29 @@ def test_to_period(self, dt64, ts): alt = Timestamp(dt64) assert ts.to_period("D") == alt.to_period("D") + @pytest.mark.parametrize( + "td", [timedelta(days=4), Timedelta(days=4), np.timedelta64(4, "D")] + ) + def test_addsub_timedeltalike_non_nano(self, dt64, ts, td): + + result = ts - td + expected = Timestamp(dt64) - td + assert isinstance(result, Timestamp) + assert result._reso == ts._reso + assert result == expected + + result = ts + td + expected = Timestamp(dt64) + td + assert isinstance(result, Timestamp) + assert result._reso == ts._reso + assert result == expected + + result = td + ts + expected = td + Timestamp(dt64) + assert isinstance(result, Timestamp) + assert result._reso == ts._reso + assert result == expected + class TestAsUnit: def test_as_unit(self):