Skip to content

Commit 772f4ae

Browse files
committed
fixed bug of overflow in Timestamp normalize()
1 parent bc24e84 commit 772f4ae

File tree

3 files changed

+30
-4
lines changed

3 files changed

+30
-4
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ Datetimelike
640640
- Bug in :attr:`is_year_start` where a DateTimeIndex constructed via a date_range with frequency 'MS' wouldn't have the correct year or quarter start attributes (:issue:`57377`)
641641
- Bug in :class:`DataFrame` raising ``ValueError`` when ``dtype`` is ``timedelta64`` and ``data`` is a list containing ``None`` (:issue:`60064`)
642642
- Bug in :class:`Timestamp` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``tzinfo`` or data (:issue:`48688`)
643+
- Bug in :class:`Timestamp` where :meth:`normalize` overflows at edge cases without raising an exception (:issue:`60583`)
643644
- Bug in :func:`date_range` where the last valid timestamp would sometimes not be produced (:issue:`56134`)
644645
- Bug in :func:`date_range` where using a negative frequency value would not include all points between the start and end values (:issue:`56147`)
645646
- Bug in :func:`tseries.api.guess_datetime_format` would fail to infer time format when "%Y" == "%H%M" (:issue:`57452`)

pandas/_libs/tslibs/timestamps.pyx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ cdef class _Timestamp(ABCTimestamp):
12691269
int64_t ppd = periods_per_day(self._creso)
12701270
_Timestamp ts
12711271
1272-
normalized = normalize_i8_stamp(local_val, ppd)
1272+
normalized = normalize_i8_stamp(self, local_val, ppd)
12731273
ts = type(self)._from_value_and_reso(normalized, reso=self._creso, tz=None)
12741274
return ts.tz_localize(self.tzinfo)
12751275
@@ -3438,9 +3438,9 @@ Timestamp.daysinmonth = Timestamp.days_in_month
34383438
# ----------------------------------------------------------------------
34393439
# Scalar analogues to functions in vectorized.pyx
34403440
3441-
3441+
@cython.overflowcheck(True)
34423442
@cython.cdivision(False)
3443-
cdef int64_t normalize_i8_stamp(int64_t local_val, int64_t ppd) noexcept nogil:
3443+
def normalize_i8_stamp(self, int64_t local_val, int64_t ppd):
34443444
"""
34453445
Round the localized nanosecond timestamp down to the previous midnight.
34463446

@@ -3454,4 +3454,15 @@ cdef int64_t normalize_i8_stamp(int64_t local_val, int64_t ppd) noexcept nogil:
34543454
-------
34553455
int64_t
34563456
"""
3457-
return local_val - (local_val % ppd)
3457+
cdef:
3458+
int64_t remainder
3459+
int64_t result
3460+
try:
3461+
remainder = local_val % ppd
3462+
result = local_val - remainder
3463+
except (OverflowError, OutOfBoundsDatetime) as err:
3464+
raise OutOfBoundsDatetime(
3465+
f"Cannot normalize {self} to midnight without overflow"
3466+
) from err
3467+
3468+
return result

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pandas._libs.tslibs import Timestamp
44
from pandas._libs.tslibs.dtypes import NpyDatetimeUnit
5+
from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime
56

67

78
class TestTimestampNormalize:
@@ -19,3 +20,16 @@ def test_normalize_pre_epoch_dates(self):
1920
result = Timestamp("1969-01-01 09:00:00").normalize()
2021
expected = Timestamp("1969-01-01 00:00:00")
2122
assert result == expected
23+
24+
def test_normalize_edge_cases(self):
25+
# GH: 60583
26+
expected_msg = (
27+
r"Cannot normalize 1677-09-21 00:12:43\.145224193 to midnight "
28+
"without overflow"
29+
)
30+
with pytest.raises(OutOfBoundsDatetime, match=expected_msg):
31+
Timestamp.min.normalize()
32+
33+
result = Timestamp.max.normalize()
34+
excepted = Timestamp("2262-04-11 00:00:00")
35+
assert result == excepted

0 commit comments

Comments
 (0)