Skip to content

Commit e1368cf

Browse files
authored
BUG: tslibs uncaught overflows (#55503)
* BUG: tslibs uncaught overflows * GH refs * windows/32bit builds
1 parent c1d3604 commit e1368cf

File tree

8 files changed

+75
-21
lines changed

8 files changed

+75
-21
lines changed

doc/source/whatsnew/v2.2.0.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,14 @@ Categorical
291291
Datetimelike
292292
^^^^^^^^^^^^
293293
- Bug in :meth:`DatetimeIndex.union` returning object dtype for tz-aware indexes with the same timezone but different units (:issue:`55238`)
294-
-
294+
- Bug in :meth:`Tick.delta` with very large ticks raising ``OverflowError`` instead of ``OutOfBoundsTimedelta`` (:issue:`55503`)
295+
- Bug in addition or subtraction of very large :class:`Tick` objects with :class:`Timestamp` or :class:`Timedelta` objects raising ``OverflowError`` instead of ``OutOfBoundsTimedelta`` (:issue:`55503`)
296+
295297

296298
Timedelta
297299
^^^^^^^^^
300+
- Bug in :class:`Timedelta` construction raising ``OverflowError`` instead of ``OutOfBoundsTimedelta`` (:issue:`55503`)
298301
- Bug in rendering (``__repr__``) of :class:`TimedeltaIndex` and :class:`Series` with timedelta64 values with non-nanosecond resolution entries that are all multiples of 24 hours failing to use the compact representation used in the nanosecond cases (:issue:`55405`)
299-
-
300302

301303
Timezones
302304
^^^^^^^^^
@@ -353,7 +355,7 @@ I/O
353355

354356
Period
355357
^^^^^^
356-
-
358+
- Bug in :class:`Period` addition silently wrapping around instead of raising ``OverflowError`` (:issue:`55503`)
357359
-
358360

359361
Plotting

pandas/_libs/tslibs/offsets.pyx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -961,7 +961,12 @@ cdef class Tick(SingleConstructorOffset):
961961

962962
@property
963963
def delta(self):
964-
return self.n * Timedelta(self._nanos_inc)
964+
try:
965+
return self.n * Timedelta(self._nanos_inc)
966+
except OverflowError as err:
967+
# GH#55503 as_unit will raise a more useful OutOfBoundsTimedelta
968+
Timedelta(self).as_unit("ns")
969+
raise AssertionError("This should not be reached.")
965970

966971
@property
967972
def nanos(self) -> int64_t:

pandas/_libs/tslibs/period.pyx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,7 +1814,7 @@ cdef class _Period(PeriodMixin):
18141814

18151815
def _add_timedeltalike_scalar(self, other) -> "Period":
18161816
cdef:
1817-
int64_t inc
1817+
int64_t inc, ordinal
18181818

18191819
if not self._dtype._is_tick_like():
18201820
raise IncompatibleFrequency("Input cannot be converted to "
@@ -1832,8 +1832,8 @@ cdef class _Period(PeriodMixin):
18321832
except ValueError as err:
18331833
raise IncompatibleFrequency("Input cannot be converted to "
18341834
f"Period(freq={self.freqstr})") from err
1835-
# TODO: overflow-check here
1836-
ordinal = self.ordinal + inc
1835+
with cython.overflowcheck(True):
1836+
ordinal = self.ordinal + inc
18371837
return Period(ordinal=ordinal, freq=self.freq)
18381838

18391839
def _add_offset(self, other) -> "Period":
@@ -1846,6 +1846,7 @@ cdef class _Period(PeriodMixin):
18461846
ordinal = self.ordinal + other.n
18471847
return Period(ordinal=ordinal, freq=self.freq)
18481848

1849+
@cython.overflowcheck(True)
18491850
def __add__(self, other):
18501851
if not is_period_object(self):
18511852
# cython semantics; this is analogous to a call to __radd__

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,7 +1784,7 @@ class Timedelta(_Timedelta):
17841784
)
17851785

17861786
# GH43764, convert any input to nanoseconds first and then
1787-
# create the timestamp. This ensures that any potential
1787+
# create the timedelta. This ensures that any potential
17881788
# nanosecond contributions from kwargs parsed as floats
17891789
# are taken into consideration.
17901790
seconds = int((
@@ -1797,12 +1797,24 @@ class Timedelta(_Timedelta):
17971797
) * 1_000_000_000
17981798
)
17991799

1800-
value = np.timedelta64(
1801-
int(kwargs.get("nanoseconds", 0))
1802-
+ int(kwargs.get("microseconds", 0) * 1_000)
1803-
+ int(kwargs.get("milliseconds", 0) * 1_000_000)
1804-
+ seconds
1805-
)
1800+
ns = kwargs.get("nanoseconds", 0)
1801+
us = kwargs.get("microseconds", 0)
1802+
ms = kwargs.get("milliseconds", 0)
1803+
try:
1804+
value = np.timedelta64(
1805+
int(ns)
1806+
+ int(us * 1_000)
1807+
+ int(ms * 1_000_000)
1808+
+ seconds
1809+
)
1810+
except OverflowError as err:
1811+
# GH#55503
1812+
msg = (
1813+
f"seconds={seconds}, milliseconds={ms}, "
1814+
f"microseconds={us}, nanoseconds={ns}"
1815+
)
1816+
raise OutOfBoundsTimedelta(msg) from err
1817+
18061818
if unit in {"Y", "y", "M"}:
18071819
raise ValueError(
18081820
"Units 'M', 'Y', and 'y' are no longer supported, as they do not "

pandas/tests/scalar/period/test_period.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,26 @@ def test_comparison_numpy_zerodim_arr(self, zerodim_arr, expected):
11821182

11831183

11841184
class TestArithmetic:
1185+
def test_add_overflow_raises(self):
1186+
# GH#55503
1187+
per = Timestamp.max.to_period("ns")
1188+
1189+
msg = "|".join(
1190+
[
1191+
"Python int too large to convert to C long",
1192+
# windows, 32bit linux builds
1193+
"int too big to convert",
1194+
]
1195+
)
1196+
with pytest.raises(OverflowError, match=msg):
1197+
per + 1
1198+
1199+
msg = "value too large"
1200+
with pytest.raises(OverflowError, match=msg):
1201+
per + Timedelta(1)
1202+
with pytest.raises(OverflowError, match=msg):
1203+
per + offsets.Nano(1)
1204+
11851205
@pytest.mark.parametrize("unit", ["ns", "us", "ms", "s", "m"])
11861206
def test_add_sub_td64_nat(self, unit):
11871207
# GH#47196

pandas/tests/scalar/timedelta/test_constructors.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
)
1616

1717

18+
def test_construct_from_kwargs_overflow():
19+
# GH#55503
20+
msg = "seconds=86400000000000000000, milliseconds=0, microseconds=0, nanoseconds=0"
21+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
22+
Timedelta(days=10**6)
23+
msg = "seconds=60000000000000000000, milliseconds=0, microseconds=0, nanoseconds=0"
24+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
25+
Timedelta(minutes=10**9)
26+
27+
1828
def test_construct_with_weeks_unit_overflow():
1929
# GH#47268 don't silently wrap around
2030
with pytest.raises(OutOfBoundsTimedelta, match="without overflow"):

pandas/tests/scalar/timestamp/test_arithmetic.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,12 @@ def test_overflow_offset_raises(self):
4040

4141
stamp = Timestamp("2017-01-13 00:00:00").as_unit("ns")
4242
offset_overflow = 20169940 * offsets.Day(1)
43-
msg = (
44-
"the add operation between "
45-
r"\<-?\d+ \* Days\> and \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} "
46-
"will overflow"
47-
)
4843
lmsg2 = r"Cannot cast -?20169940 days \+?00:00:00 to unit='ns' without overflow"
4944

5045
with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
5146
stamp + offset_overflow
5247

53-
with pytest.raises(OverflowError, match=msg):
48+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
5449
offset_overflow + stamp
5550

5651
with pytest.raises(OutOfBoundsTimedelta, match=lmsg2):
@@ -68,7 +63,7 @@ def test_overflow_offset_raises(self):
6863
with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):
6964
stamp + offset_overflow
7065

71-
with pytest.raises(OverflowError, match=msg):
66+
with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):
7267
offset_overflow + stamp
7368

7469
with pytest.raises(OutOfBoundsTimedelta, match=lmsg3):

pandas/tests/tseries/offsets/test_ticks.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pytest
1616

1717
from pandas._libs.tslibs.offsets import delta_to_tick
18+
from pandas.errors import OutOfBoundsTimedelta
1819

1920
from pandas import (
2021
Timedelta,
@@ -237,6 +238,14 @@ def test_tick_addition(kls, expected):
237238
assert result == expected
238239

239240

241+
def test_tick_delta_overflow():
242+
# GH#55503 raise OutOfBoundsTimedelta, not OverflowError
243+
tick = offsets.Day(10**9)
244+
msg = "Cannot cast 1000000000 days 00:00:00 to unit='ns' without overflow"
245+
with pytest.raises(OutOfBoundsTimedelta, match=msg):
246+
tick.delta
247+
248+
240249
@pytest.mark.parametrize("cls", tick_classes)
241250
def test_tick_division(cls):
242251
off = cls(10)

0 commit comments

Comments
 (0)