Skip to content

Commit b74dc5c

Browse files
authored
ENH: Timestamp +- timedeltalike scalar support non-nano (#47313)
* ENH: Timestamp +- timedeltalike scalar support non-nano * catch and re-raise OverflowError
1 parent 8de88ff commit b74dc5c

File tree

2 files changed

+42
-11
lines changed

2 files changed

+42
-11
lines changed

pandas/_libs/tslibs/timestamps.pyx

+19-11
Original file line numberDiff line numberDiff line change
@@ -370,9 +370,6 @@ cdef class _Timestamp(ABCTimestamp):
370370
cdef:
371371
int64_t nanos = 0
372372

373-
if isinstance(self, _Timestamp) and self._reso != NPY_FR_ns:
374-
raise NotImplementedError(self._reso)
375-
376373
if is_any_td_scalar(other):
377374
if is_timedelta64_object(other):
378375
other_reso = get_datetime64_unit(other)
@@ -390,20 +387,31 @@ cdef class _Timestamp(ABCTimestamp):
390387
# TODO: no tests get here
391388
other = ensure_td64ns(other)
392389

390+
# TODO: what to do with mismatched resos?
393391
# TODO: disallow round_ok
394392
nanos = delta_to_nanoseconds(
395393
other, reso=self._reso, round_ok=True
396394
)
397395
try:
398-
result = type(self)(self.value + nanos, tz=self.tzinfo)
396+
new_value = self.value + nanos
399397
except OverflowError:
400398
# Use Python ints
401399
# Hit in test_tdi_add_overflow
402-
result = type(self)(int(self.value) + int(nanos), tz=self.tzinfo)
400+
new_value = int(self.value) + int(nanos)
401+
402+
try:
403+
result = type(self)._from_value_and_reso(new_value, reso=self._reso, tz=self.tzinfo)
404+
except OverflowError as err:
405+
# TODO: don't hard-code nanosecond here
406+
raise OutOfBoundsDatetime(f"Out of bounds nanosecond timestamp: {new_value}") from err
407+
403408
if result is not NaT:
404409
result._set_freq(self._freq) # avoid warning in constructor
405410
return result
406411

412+
elif isinstance(self, _Timestamp) and self._reso != NPY_FR_ns:
413+
raise NotImplementedError(self._reso)
414+
407415
elif is_integer_object(other):
408416
raise integer_op_not_supported(self)
409417

@@ -431,13 +439,16 @@ cdef class _Timestamp(ABCTimestamp):
431439
return NotImplemented
432440

433441
def __sub__(self, other):
434-
if isinstance(self, _Timestamp) and self._reso != NPY_FR_ns:
435-
raise NotImplementedError(self._reso)
442+
if other is NaT:
443+
return NaT
436444

437-
if is_any_td_scalar(other) or is_integer_object(other):
445+
elif is_any_td_scalar(other) or is_integer_object(other):
438446
neg_other = -other
439447
return self + neg_other
440448

449+
elif isinstance(self, _Timestamp) and self._reso != NPY_FR_ns:
450+
raise NotImplementedError(self._reso)
451+
441452
elif is_array(other):
442453
if other.dtype.kind in ['i', 'u']:
443454
raise integer_op_not_supported(self)
@@ -450,9 +461,6 @@ cdef class _Timestamp(ABCTimestamp):
450461
)
451462
return NotImplemented
452463

453-
if other is NaT:
454-
return NaT
455-
456464
# coerce if necessary if we are a Timestamp-like
457465
if (PyDateTime_Check(self)
458466
and (PyDateTime_Check(other) or is_datetime64_object(other))):

pandas/tests/scalar/timestamp/test_timestamp.py

+23
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,29 @@ def test_to_period(self, dt64, ts):
859859
alt = Timestamp(dt64)
860860
assert ts.to_period("D") == alt.to_period("D")
861861

862+
@pytest.mark.parametrize(
863+
"td", [timedelta(days=4), Timedelta(days=4), np.timedelta64(4, "D")]
864+
)
865+
def test_addsub_timedeltalike_non_nano(self, dt64, ts, td):
866+
867+
result = ts - td
868+
expected = Timestamp(dt64) - td
869+
assert isinstance(result, Timestamp)
870+
assert result._reso == ts._reso
871+
assert result == expected
872+
873+
result = ts + td
874+
expected = Timestamp(dt64) + td
875+
assert isinstance(result, Timestamp)
876+
assert result._reso == ts._reso
877+
assert result == expected
878+
879+
result = td + ts
880+
expected = td + Timestamp(dt64)
881+
assert isinstance(result, Timestamp)
882+
assert result._reso == ts._reso
883+
assert result == expected
884+
862885

863886
class TestAsUnit:
864887
def test_as_unit(self):

0 commit comments

Comments
 (0)