diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 89b420b18a980..b123fcf9a6fd5 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -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 ( @@ -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, @@ -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 @@ -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 @@ -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) @@ -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}" ) # ---------------------------------------------------------------------- diff --git a/pandas/_libs/tslibs/np_datetime.pyx b/pandas/_libs/tslibs/np_datetime.pyx index fa9b82fe46634..491716c3a951d 100644 --- a/pandas/_libs/tslibs/np_datetime.pyx +++ b/pandas/_libs/tslibs/np_datetime.pyx @@ -22,6 +22,8 @@ from libc.stdint cimport INT64_MAX import_datetime() PandasDateTime_IMPORT +import operator + import numpy as np cimport numpy as cnp @@ -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 @@ -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}") # ---------------------------------------------------------------------- @@ -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 @@ -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 @@ -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. @@ -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 @@ -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( diff --git a/pandas/_libs/tslibs/strptime.pyx b/pandas/_libs/tslibs/strptime.pyx index 8966c9e81699b..0e197e00800e6 100644 --- a/pandas/_libs/tslibs/strptime.pyx +++ b/pandas/_libs/tslibs/strptime.pyx @@ -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, @@ -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 @@ -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 diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 56a6885d4a9e0..6ef57f630efe4 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -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, ) @@ -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 diff --git a/pandas/tests/scalar/timestamp/test_constructors.py b/pandas/tests/scalar/timestamp/test_constructors.py index b0019d2381e47..8798a8904e161 100644 --- a/pandas/tests/scalar/timestamp/test_constructors.py +++ b/pandas/tests/scalar/timestamp/test_constructors.py @@ -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) diff --git a/pandas/tests/tools/test_to_datetime.py b/pandas/tests/tools/test_to_datetime.py index 3d3ca99f4d26a..ef76d99260764 100644 --- a/pandas/tests/tools/test_to_datetime.py +++ b/pandas/tests/tools/test_to_datetime.py @@ -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):