Skip to content

Commit b6b0af1

Browse files
committed
BUG: Raise OutOfBoundsDatetime when Period is constructed from invalid high-resolution Timestamp (#63278)
1 parent 944c527 commit b6b0af1

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

pandas/_libs/tslibs/period.pyx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,45 @@ cdef int64_t get_period_ordinal(npy_datetimestruct *dts, int freq) noexcept nogi
812812
unit = freq_group_code_to_npy_unit(freq)
813813
return npy_datetimestruct_to_datetime(unit, dts)
814814

815+
cdef int64_t _period_ordinal_safe(npy_datetimestruct *dts, int freq) except? -1:
816+
"""
817+
Safe variant of get_period_ordinal used by the Python API (period_ordinal).
818+
819+
It mirrors get_period_ordinal's logic but is allowed to raise Python
820+
exceptions instead of leaking OverflowError from numpy's datetime code.
821+
"""
822+
cdef:
823+
int64_t unix_date
824+
int freq_group, fmonth
825+
NPY_DATETIMEUNIT unit
826+
827+
freq_group = get_freq_group(freq)
828+
829+
try:
830+
if freq_group == FR_ANN:
831+
fmonth = get_anchor_month(freq, freq_group)
832+
return dts_to_year_ordinal(dts, fmonth)
833+
834+
elif freq_group == FR_QTR:
835+
fmonth = get_anchor_month(freq, freq_group)
836+
return dts_to_qtr_ordinal(dts, fmonth)
837+
838+
elif freq_group == FR_WK:
839+
unix_date = npy_datetimestruct_to_datetime(NPY_FR_D, dts)
840+
return unix_date_to_week(unix_date, freq - FR_WK)
841+
842+
elif freq == FR_BUS:
843+
unix_date = npy_datetimestruct_to_datetime(NPY_FR_D, dts)
844+
return DtoB(dts, 0, unix_date)
845+
846+
unit = freq_group_code_to_npy_unit(freq)
847+
return npy_datetimestruct_to_datetime(unit, dts)
848+
849+
except OverflowError as err:
850+
# Translate low-level overflow into a user-facing OutOfBoundsDatetime.
851+
fmt = dts_to_iso_string(dts)
852+
raise OutOfBoundsDatetime(f"Out of bounds datetime for Period with freq {freq}: {fmt}") from err
853+
815854

816855
cdef void get_date_info(int64_t ordinal,
817856
int freq, npy_datetimestruct *dts) noexcept nogil:
@@ -1150,7 +1189,8 @@ cpdef int64_t period_ordinal(int y, int m, int d, int h, int min,
11501189
dts.sec = s
11511190
dts.us = us
11521191
dts.ps = ps
1153-
return get_period_ordinal(&dts, freq)
1192+
#return get_period_ordinal(&dts, freq)
1193+
return _period_ordinal_safe(&dts, freq)
11541194

11551195

11561196
cdef int64_t period_ordinal_to_dt64(int64_t ordinal, int freq) except? -1:

pandas/tests/tslibs/test_period.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import numpy as np
22
import pytest
3-
3+
import pandas as pd
44
from pandas._libs.tslibs import (
55
iNaT,
66
to_offset,
7+
OutOfBoundsDatetime,
78
)
89
from pandas._libs.tslibs.period import (
910
extract_ordinals,
@@ -121,3 +122,10 @@ def test_get_period_field_array_raises_on_out_of_range():
121122
msg = "Buffer dtype mismatch, expected 'const int64_t' but got 'double'"
122123
with pytest.raises(ValueError, match=msg):
123124
get_period_field_arr(-1, np.empty(1), 0)
125+
126+
def test_period_from_overflow_timestamp_raises():
127+
# Construct a deliberately broken Timestamp
128+
ts = pd.Timestamp(pd.Timestamp.min.value, unit="us")
129+
130+
with pytest.raises(OutOfBoundsDatetime):
131+
pd.Period(ts, freq="us")

0 commit comments

Comments
 (0)