Skip to content

Commit 51eb151

Browse files
catch correct error in Timedelta sub
1 parent 89b2431 commit 51eb151

File tree

5 files changed

+32
-19
lines changed

5 files changed

+32
-19
lines changed

pandas/_libs/tslibs/timedeltas.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ def array_to_timedelta64(
7373
) -> np.ndarray: ... # np.ndarray[m8ns]
7474
def parse_timedelta_unit(unit: str | None) -> UnitChoices: ...
7575
def delta_to_nanoseconds(delta: np.timedelta64 | timedelta | Tick) -> int: ...
76-
def calculate(op, left: int, right: int) -> int: ...
7776

7877
class Timedelta(timedelta):
7978
min: ClassVar[Timedelta]

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -684,13 +684,23 @@ def _op_unary_method(func, name):
684684
return f
685685

686686

687-
cpdef int64_t calculate(object op, int64_t a, int64_t b) except? -1:
687+
@cython.overflowcheck(True)
688+
cpdef int64_t _calculate(object op, int64_t a, int64_t b) except? -1:
689+
"""
690+
Calculate op(a, b) and return the result. Raises OverflowError if either operand
691+
or the result would overflow on conversion to int64_t.
692+
"""
693+
return op(a, b)
694+
695+
696+
cpdef int64_t calculate(object op, object a, object b) except? -1:
688697
"""
689-
Calculate op(a, b) and return the result, or raise if the operation would overflow.
698+
As above, but raises an OutOfBoundsTimedelta.
690699
"""
700+
cdef int64_t int_a, int_b
701+
691702
try:
692-
with cython.overflowcheck(True):
693-
return op(a, b)
703+
return _calculate(op, a, b)
694704
except OverflowError as ex:
695705
msg = f"outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
696706
raise OutOfBoundsTimedelta(msg) from ex

pandas/_libs/tslibs/timestamps.pyx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ from pandas._libs.tslibs.np_datetime cimport (
8686
pydatetime_to_dt64,
8787
)
8888

89-
from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime
89+
from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime, OutOfBoundsTimedelta
9090

9191
from pandas._libs.tslibs.offsets cimport (
9292
BaseOffset,
@@ -431,14 +431,13 @@ cdef class _Timestamp(ABCTimestamp):
431431
# Timedelta
432432
try:
433433
return Timedelta(self.value - other.value)
434-
except (OverflowError, OutOfBoundsDatetime) as err:
435-
if isinstance(other, _Timestamp):
436-
if both_timestamps:
437-
raise OutOfBoundsDatetime(
438-
"Result is too large for pandas.Timedelta. Convert inputs "
439-
"to datetime.datetime with 'Timestamp.to_pydatetime()' "
440-
"before subtracting."
441-
) from err
434+
except OutOfBoundsTimedelta as err:
435+
if both_timestamps:
436+
raise OutOfBoundsTimedelta(
437+
"Result is too large for pandas.Timedelta. Convert inputs "
438+
"to datetime.datetime with 'Timestamp.to_pydatetime()' "
439+
"before subtracting."
440+
) from err
442441
# We get here in stata tests, fall back to stdlib datetime
443442
# method and return stdlib timedelta object
444443
pass

pandas/tests/scalar/timestamp/test_arithmetic.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,22 @@ def test_overflow_offset_raises(self):
7272
with pytest.raises(OverflowError, match=lmsg):
7373
stamp - offset_overflow
7474

75-
def test_overflow_timestamp_raises(self):
75+
def test_sub_can_return_stdlib_timedelta_to_avoid_overflow(self, timedelta_overflow):
7676
# https://github.com/pandas-dev/pandas/issues/31774
77-
msg = "Result is too large"
77+
msg = "Result is too large for pandas.Timedelta"
7878
a = Timestamp("2101-01-01 00:00:00")
7979
b = Timestamp("1688-01-01 00:00:00")
8080

81-
with pytest.raises(OutOfBoundsDatetime, match=msg):
81+
with pytest.raises(timedelta_overflow["expected_exception"], match=msg):
8282
a - b
8383

84-
# but we're OK for timestamp and datetime.datetime
85-
assert (a - b.to_pydatetime()) == (a.to_pydatetime() - b)
84+
# but we're OK for Timestamp and datetime.datetime
85+
r0 = a - b.to_pydatetime()
86+
r1 = a.to_pydatetime() - b
87+
assert r0 == r1
88+
assert isinstance(r0, timedelta)
89+
assert isinstance(r1, timedelta)
90+
8691

8792
def test_delta_preserve_nanos(self):
8893
val = Timestamp(1337299200000000123)

0 commit comments

Comments
 (0)