Skip to content

ENH: dont hard-code nanoseconds in overflow exception message #55983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 16, 2023
Merged
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
23 changes: 14 additions & 9 deletions pandas/_libs/tslibs/conversion.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ from pandas._libs.tslibs.base cimport ABCTimestamp
from pandas._libs.tslibs.dtypes cimport (
abbrev_to_npy_unit,
get_supported_reso,
npy_unit_to_attrname,
periods_per_second,
)
from pandas._libs.tslibs.np_datetime cimport (
Expand All @@ -39,6 +40,7 @@ from pandas._libs.tslibs.np_datetime cimport (
NPY_FR_us,
check_dts_bounds,
convert_reso,
dts_to_iso_string,
get_conversion_factor,
get_datetime64_unit,
get_implementation_bounds,
Expand Down Expand Up @@ -215,8 +217,9 @@ cdef int64_t get_datetime64_nanos(object val, NPY_DATETIMEUNIT reso) except? -1:
try:
ival = npy_datetimestruct_to_datetime(reso, &dts)
except OverflowError as err:
attrname = npy_unit_to_attrname[reso]
raise OutOfBoundsDatetime(
"Out of bounds nanosecond timestamp: {val}"
f"Out of bounds {attrname} timestamp: {val}"
) from err

return ival
Expand Down Expand Up @@ -249,8 +252,9 @@ cdef class _TSObject:
)
except OverflowError as err:
if val is not None:
attrname = npy_unit_to_attrname[creso]
raise OutOfBoundsDatetime(
f"Out of bounds nanosecond timestamp: {val}"
f"Out of bounds {attrname} timestamp: {val}"
) from err
raise OutOfBoundsDatetime from err

Expand Down Expand Up @@ -420,7 +424,8 @@ cdef _TSObject convert_datetime_to_tsobject(
try:
obj.value = npy_datetimestruct_to_datetime(reso, &obj.dts)
except OverflowError as err:
raise OutOfBoundsDatetime("Out of bounds nanosecond timestamp") from err
attrname = npy_unit_to_attrname[reso]
raise OutOfBoundsDatetime(f"Out of bounds {attrname} timestamp") from err

if obj.tzinfo is not None and not is_utc(obj.tzinfo):
offset = get_utcoffset(obj.tzinfo, ts)
Expand Down Expand Up @@ -591,18 +596,18 @@ cdef check_overflows(_TSObject obj, NPY_DATETIMEUNIT reso=NPY_FR_ns):
if obj.dts.year == lb.year:
if not (obj.value < 0):
from pandas._libs.tslibs.timestamps import Timestamp
fmt = (f"{obj.dts.year}-{obj.dts.month:02d}-{obj.dts.day:02d} "
f"{obj.dts.hour:02d}:{obj.dts.min:02d}:{obj.dts.sec:02d}")
fmt = dts_to_iso_string(&obj.dts)
min_ts = (<_Timestamp>Timestamp(0))._as_creso(reso).min
raise OutOfBoundsDatetime(
f"Converting {fmt} underflows past {Timestamp.min}"
f"Converting {fmt} underflows past {min_ts}"
)
elif obj.dts.year == ub.year:
if not (obj.value > 0):
from pandas._libs.tslibs.timestamps import Timestamp
fmt = (f"{obj.dts.year}-{obj.dts.month:02d}-{obj.dts.day:02d} "
f"{obj.dts.hour:02d}:{obj.dts.min:02d}:{obj.dts.sec:02d}")
fmt = dts_to_iso_string(&obj.dts)
max_ts = (<_Timestamp>Timestamp(0))._as_creso(reso).max
raise OutOfBoundsDatetime(
f"Converting {fmt} overflows past {Timestamp.max}"
f"Converting {fmt} overflows past {max_ts}"
)

# ----------------------------------------------------------------------
Expand Down
23 changes: 13 additions & 10 deletions pandas/_libs/tslibs/np_datetime.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ from libc.stdint cimport INT64_MAX
import_datetime()
PandasDateTime_IMPORT

import operator

import numpy as np

cimport numpy as cnp
Expand All @@ -33,6 +35,10 @@ from numpy cimport (
uint8_t,
)

from pandas._libs.tslibs.dtypes cimport (
npy_unit_to_abbrev,
npy_unit_to_attrname,
)
from pandas._libs.tslibs.util cimport get_c_string_buf_and_size


Expand Down Expand Up @@ -215,8 +221,8 @@ cdef check_dts_bounds(npy_datetimestruct *dts, NPY_DATETIMEUNIT unit=NPY_FR_ns):

if error:
fmt = dts_to_iso_string(dts)
# TODO: "nanosecond" in the message assumes NPY_FR_ns
raise OutOfBoundsDatetime(f"Out of bounds nanosecond timestamp: {fmt}")
attrname = npy_unit_to_attrname[unit]
raise OutOfBoundsDatetime(f"Out of bounds {attrname} timestamp: {fmt}")


# ----------------------------------------------------------------------
Expand Down Expand Up @@ -259,8 +265,9 @@ cdef int64_t pydatetime_to_dt64(datetime val,
try:
result = npy_datetimestruct_to_datetime(reso, dts)
except OverflowError as err:
attrname = npy_unit_to_attrname[reso]
raise OutOfBoundsDatetime(
f"Out of bounds nanosecond timestamp: {val}"
f"Out of bounds {attrname} timestamp: {val}"
) from err

return result
Expand All @@ -284,7 +291,8 @@ cdef int64_t pydate_to_dt64(
try:
result = npy_datetimestruct_to_datetime(reso, dts)
except OverflowError as err:
raise OutOfBoundsDatetime(f"Out of bounds nanosecond timestamp: {val}") from err
attrname = npy_unit_to_attrname[reso]
raise OutOfBoundsDatetime(f"Out of bounds {attrname} timestamp: {val}") from err

return result

Expand Down Expand Up @@ -423,7 +431,7 @@ cpdef ndarray astype_overflowsafe(
return iresult.view(dtype)


# TODO: try to upstream this fix to numpy
# TODO(numpy#16352): try to upstream this fix to numpy
def compare_mismatched_resolutions(ndarray left, ndarray right, op):
"""
Overflow-safe comparison of timedelta64/datetime64 with mismatched resolutions.
Expand Down Expand Up @@ -481,9 +489,6 @@ def compare_mismatched_resolutions(ndarray left, ndarray right, op):
return result


import operator


cdef int op_to_op_code(op):
if op is operator.eq:
return Py_EQ
Expand Down Expand Up @@ -536,8 +541,6 @@ cdef ndarray _astype_overflowsafe_to_smaller_unit(
else:
new_value, mod = divmod(value, mult)
if not round_ok and mod != 0:
# TODO: avoid runtime import
from pandas._libs.tslibs.dtypes import npy_unit_to_abbrev
from_abbrev = npy_unit_to_abbrev(from_unit)
to_abbrev = npy_unit_to_abbrev(to_unit)
raise ValueError(
Expand Down
7 changes: 5 additions & 2 deletions pandas/_libs/tslibs/strptime.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ from pandas._libs.tslibs.conversion cimport get_datetime64_nanos
from pandas._libs.tslibs.dtypes cimport (
get_supported_reso,
npy_unit_to_abbrev,
npy_unit_to_attrname,
)
from pandas._libs.tslibs.nattype cimport (
NPY_NAT,
Expand Down Expand Up @@ -413,8 +414,9 @@ def array_strptime(
try:
value = npy_datetimestruct_to_datetime(creso, &dts)
except OverflowError as err:
attrname = npy_unit_to_attrname[creso]
raise OutOfBoundsDatetime(
f"Out of bounds nanosecond timestamp: {val}"
f"Out of bounds {attrname} timestamp: {val}"
) from err
if out_local == 1:
# Store the out_tzoffset in seconds
Expand Down Expand Up @@ -452,8 +454,9 @@ def array_strptime(
try:
iresult[i] = npy_datetimestruct_to_datetime(creso, &dts)
except OverflowError as err:
attrname = npy_unit_to_attrname[creso]
raise OutOfBoundsDatetime(
f"Out of bounds nanosecond timestamp: {val}"
f"Out of bounds {attrname} timestamp: {val}"
) from err
result_timezone[i] = tz

Expand Down
7 changes: 4 additions & 3 deletions pandas/_libs/tslibs/timestamps.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ from pandas._libs.tslibs.conversion cimport (
)
from pandas._libs.tslibs.dtypes cimport (
npy_unit_to_abbrev,
npy_unit_to_attrname,
periods_per_day,
periods_per_second,
)
Expand Down Expand Up @@ -448,15 +449,15 @@ cdef class _Timestamp(ABCTimestamp):
nanos = other._value

try:
new_value = self._value+ nanos
new_value = self._value + nanos
result = type(self)._from_value_and_reso(
new_value, reso=self._creso, tz=self.tzinfo
)
except OverflowError as err:
# TODO: don't hard-code nanosecond here
new_value = int(self._value) + int(nanos)
attrname = npy_unit_to_attrname[self._creso]
raise OutOfBoundsDatetime(
f"Out of bounds nanosecond timestamp: {new_value}"
f"Out of bounds {attrname} timestamp: {new_value}"
) from err

return result
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/scalar/timestamp/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ def test_bounds_with_different_units(self):

# With more extreme cases, we can't even fit inside second resolution
info = np.iinfo(np.int64)
msg = "Out of bounds nanosecond timestamp:"
msg = "Out of bounds second timestamp:"
for value in [info.min + 1, info.max]:
for unit in ["D", "h", "m"]:
dt64 = np.datetime64(value, unit)
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/tools/test_to_datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,7 @@ def test_to_datetime_dt64s_out_of_ns_bounds(self, cache, dt, errors):
def test_to_datetime_dt64d_out_of_bounds(self, cache):
dt64 = np.datetime64(np.iinfo(np.int64).max, "D")

msg = "Out of bounds nanosecond timestamp"
msg = "Out of bounds second timestamp: 25252734927768524-07-27"
with pytest.raises(OutOfBoundsDatetime, match=msg):
Timestamp(dt64)
with pytest.raises(OutOfBoundsDatetime, match=msg):
Expand Down