Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion pandas/_libs/tslibs/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,47 @@ cdef int64_t get_period_ordinal(npy_datetimestruct *dts, int freq) noexcept nogi
unit = freq_group_code_to_npy_unit(freq)
return npy_datetimestruct_to_datetime(unit, dts)

cdef int64_t _period_ordinal_safe(npy_datetimestruct *dts, int freq) except? -1:
"""
Safe variant of get_period_ordinal used by the Python API (period_ordinal).

It mirrors get_period_ordinal's logic but is allowed to raise Python
exceptions instead of leaking OverflowError from numpy's datetime code.
"""
cdef:
int64_t unix_date
int freq_group, fmonth
NPY_DATETIMEUNIT unit

freq_group = get_freq_group(freq)

try:
if freq_group == FR_ANN:
fmonth = get_anchor_month(freq, freq_group)
return dts_to_year_ordinal(dts, fmonth)

elif freq_group == FR_QTR:
fmonth = get_anchor_month(freq, freq_group)
return dts_to_qtr_ordinal(dts, fmonth)

elif freq_group == FR_WK:
unix_date = npy_datetimestruct_to_datetime(NPY_FR_D, dts)
return unix_date_to_week(unix_date, freq - FR_WK)

elif freq == FR_BUS:
unix_date = npy_datetimestruct_to_datetime(NPY_FR_D, dts)
return DtoB(dts, 0, unix_date)

unit = freq_group_code_to_npy_unit(freq)
return npy_datetimestruct_to_datetime(unit, dts)

except OverflowError as err:
# Translate low-level overflow into a user-facing OutOfBoundsDatetime.
fmt = dts_to_iso_string(dts)
raise OutOfBoundsDatetime(
f"Out of bounds datetime for Period with freq {freq}: {fmt}"
) from err


cdef void get_date_info(int64_t ordinal,
int freq, npy_datetimestruct *dts) noexcept nogil:
Expand Down Expand Up @@ -1150,7 +1191,8 @@ cpdef int64_t period_ordinal(int y, int m, int d, int h, int min,
dts.sec = s
dts.us = us
dts.ps = ps
return get_period_ordinal(&dts, freq)
# return get_period_ordinal(&dts, freq)
return _period_ordinal_safe(&dts, freq)


cdef int64_t period_ordinal_to_dt64(int64_t ordinal, int freq) except? -1:
Expand Down
10 changes: 10 additions & 0 deletions pandas/tests/tslibs/test_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest

from pandas._libs.tslibs import (
OutOfBoundsDatetime,
iNaT,
to_offset,
)
Expand All @@ -12,6 +13,7 @@
period_ordinal,
)

import pandas as pd
import pandas._testing as tm


Expand Down Expand Up @@ -121,3 +123,11 @@ def test_get_period_field_array_raises_on_out_of_range():
msg = "Buffer dtype mismatch, expected 'const int64_t' but got 'double'"
with pytest.raises(ValueError, match=msg):
get_period_field_arr(-1, np.empty(1), 0)


def test_period_from_overflow_timestamp_raises():
# Construct a deliberately broken Timestamp
ts = pd.Timestamp(pd.Timestamp.min.value, unit="us")

with pytest.raises(OutOfBoundsDatetime):
pd.Period(ts, freq="us")
Loading