From 419d0f2da9f0534413976b8be953820aaf2a5c90 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Jun 2022 11:37:29 -0700 Subject: [PATCH 1/2] ENH: Timestamp +- timedeltalike scalar support non-nano --- pandas/_libs/tslibs/timestamps.pyx | 26 +++++++++++-------- .../tests/scalar/timestamp/test_timestamp.py | 23 ++++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index c6bae70d04a98..98a4f2517ac2d 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -368,9 +368,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) @@ -388,20 +385,27 @@ 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) + + result = type(self)._from_value_and_reso(new_value, reso=self._reso, tz=self.tzinfo) + 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) @@ -429,13 +433,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) @@ -448,9 +455,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 89e5ce2241e42..e3f89d311ee39 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -850,3 +850,26 @@ def test_timestamp(self, dt64, ts): 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 From 33da17ce0fc71f4717e864fdd3defae951f36113 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Jun 2022 15:10:19 -0700 Subject: [PATCH 2/2] catch and re-raise OverflowError --- pandas/_libs/tslibs/timestamps.pyx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 98a4f2517ac2d..5a5efbf8f6fe3 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -397,7 +397,11 @@ cdef class _Timestamp(ABCTimestamp): # Hit in test_tdi_add_overflow new_value = int(self.value) + int(nanos) - result = type(self)._from_value_and_reso(new_value, reso=self._reso, tz=self.tzinfo) + 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