Skip to content

Commit bcc60a4

Browse files
authored
BUG: Timestamp.normalize overflow (#62305)
1 parent 5328c01 commit bcc60a4

File tree

5 files changed

+35
-5
lines changed

5 files changed

+35
-5
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@ Datetimelike
902902
- Bug in :meth:`DatetimeIndex.union` and :meth:`DatetimeIndex.intersection` when ``unit`` was non-nanosecond (:issue:`59036`)
903903
- Bug in :meth:`Index.union` with a ``pyarrow`` timestamp dtype incorrectly returning ``object`` dtype (:issue:`58421`)
904904
- Bug in :meth:`Series.dt.microsecond` producing incorrect results for pyarrow backed :class:`Series`. (:issue:`59154`)
905+
- Bug in :meth:`Timestamp.normalize` and :meth:`DatetimeArray.normalize` returning incorrect results instead of raising on integer overflow for very small (distant past) values (:issue:`60583`)
905906
- Bug in :meth:`Timestamp.replace` failing to update ``unit`` attribute when replacement introduces non-zero ``nanosecond`` or ``microsecond`` (:issue:`57749`)
906907
- Bug in :meth:`to_datetime` not respecting dayfirst if an uncommon date string was passed. (:issue:`58859`)
907908
- Bug in :meth:`to_datetime` on float array with missing values throwing ``FloatingPointError`` (:issue:`58419`)

pandas/_libs/tslibs/timestamps.pyx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,7 +1339,12 @@ cdef class _Timestamp(ABCTimestamp):
13391339
int64_t ppd = periods_per_day(self._creso)
13401340
_Timestamp ts
13411341
1342-
normalized = normalize_i8_stamp(local_val, ppd)
1342+
try:
1343+
normalized = normalize_i8_stamp(local_val, ppd)
1344+
except OverflowError as err:
1345+
raise ValueError(
1346+
"Cannot normalize Timestamp without integer overflow"
1347+
) from err
13431348
ts = type(self)._from_value_and_reso(normalized, reso=self._creso, tz=None)
13441349
return ts.tz_localize(self.tzinfo)
13451350
@@ -3547,7 +3552,7 @@ Timestamp.daysinmonth = Timestamp.days_in_month
35473552
35483553
35493554
@cython.cdivision(False)
3550-
cdef int64_t normalize_i8_stamp(int64_t local_val, int64_t ppd) noexcept nogil:
3555+
cdef int64_t normalize_i8_stamp(int64_t local_val, int64_t ppd):
35513556
"""
35523557
Round the localized nanosecond timestamp down to the previous midnight.
35533558

@@ -3561,4 +3566,6 @@ cdef int64_t normalize_i8_stamp(int64_t local_val, int64_t ppd) noexcept nogil:
35613566
-------
35623567
int64_t
35633568
"""
3564-
return local_val - (local_val % ppd)
3569+
with cython.overflowcheck(True):
3570+
# GH#60583
3571+
return local_val - (local_val % ppd)

pandas/_libs/tslibs/vectorized.pyx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ def get_resolution(
254254
# -------------------------------------------------------------------------
255255

256256

257+
@cython.overflowcheck(True)
257258
@cython.cdivision(False)
258259
@cython.wraparound(False)
259260
@cython.boundscheck(False)
@@ -292,8 +293,12 @@ cpdef ndarray normalize_i8_timestamps(ndarray stamps, tzinfo tz, NPY_DATETIMEUNI
292293
res_val = NPY_NAT
293294
else:
294295
local_val = info.utc_val_to_local_val(utc_val, &pos)
295-
res_val = local_val - (local_val % ppd)
296-
296+
try:
297+
res_val = local_val - (local_val % ppd)
298+
except OverflowError as err:
299+
raise ValueError(
300+
"Cannot normalize Timestamp without integer overflow"
301+
) from err
297302
# Analogous to: result[i] = res_val
298303
(<int64_t*>cnp.PyArray_MultiIter_DATA(mi, 0))[0] = res_val
299304

pandas/tests/arrays/test_datetimes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ def test_normalize(self, unit):
9393
res = dta.normalize()
9494
tm.assert_extension_array_equal(res, expected)
9595

96+
def test_normalize_overflow_raises(self):
97+
# GH#60583
98+
ts = pd.Timestamp.min
99+
dta = DatetimeArray._from_sequence([ts], dtype="M8[ns]")
100+
101+
msg = "Cannot normalize Timestamp without integer overflow"
102+
with pytest.raises(ValueError, match=msg):
103+
dta.normalize()
104+
96105
def test_simple_new_requires_match(self, unit):
97106
arr = np.arange(5, dtype=np.int64).view(f"M8[{unit}]")
98107
dtype = DatetimeTZDtype(unit, "UTC")

pandas/tests/scalar/timestamp/methods/test_normalize.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ def test_normalize_pre_epoch_dates(self):
1919
result = Timestamp("1969-01-01 09:00:00").normalize()
2020
expected = Timestamp("1969-01-01 00:00:00")
2121
assert result == expected
22+
23+
def test_normalize_overflow_raises(self):
24+
# GH#60583
25+
ts = Timestamp.min
26+
27+
msg = "Cannot normalize Timestamp without integer overflow"
28+
with pytest.raises(ValueError, match=msg):
29+
ts.normalize()

0 commit comments

Comments
 (0)