Skip to content

Commit 03c1663

Browse files
authored
ENH: dont hard-code nanoseconds in overflow exception message (pandas-dev#55983)
1 parent 0199d58 commit 03c1663

File tree

6 files changed

+38
-26
lines changed

6 files changed

+38
-26
lines changed

pandas/_libs/tslibs/conversion.pyx

+14-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ from pandas._libs.tslibs.base cimport ABCTimestamp
3030
from pandas._libs.tslibs.dtypes cimport (
3131
abbrev_to_npy_unit,
3232
get_supported_reso,
33+
npy_unit_to_attrname,
3334
periods_per_second,
3435
)
3536
from pandas._libs.tslibs.np_datetime cimport (
@@ -38,6 +39,7 @@ from pandas._libs.tslibs.np_datetime cimport (
3839
NPY_FR_us,
3940
check_dts_bounds,
4041
convert_reso,
42+
dts_to_iso_string,
4143
get_conversion_factor,
4244
get_datetime64_unit,
4345
get_implementation_bounds,
@@ -214,8 +216,9 @@ cdef int64_t get_datetime64_nanos(object val, NPY_DATETIMEUNIT reso) except? -1:
214216
try:
215217
ival = npy_datetimestruct_to_datetime(reso, &dts)
216218
except OverflowError as err:
219+
attrname = npy_unit_to_attrname[reso]
217220
raise OutOfBoundsDatetime(
218-
"Out of bounds nanosecond timestamp: {val}"
221+
f"Out of bounds {attrname} timestamp: {val}"
219222
) from err
220223

221224
return ival
@@ -248,8 +251,9 @@ cdef class _TSObject:
248251
)
249252
except OverflowError as err:
250253
if val is not None:
254+
attrname = npy_unit_to_attrname[creso]
251255
raise OutOfBoundsDatetime(
252-
f"Out of bounds nanosecond timestamp: {val}"
256+
f"Out of bounds {attrname} timestamp: {val}"
253257
) from err
254258
raise OutOfBoundsDatetime from err
255259

@@ -419,7 +423,8 @@ cdef _TSObject convert_datetime_to_tsobject(
419423
try:
420424
obj.value = npy_datetimestruct_to_datetime(reso, &obj.dts)
421425
except OverflowError as err:
422-
raise OutOfBoundsDatetime("Out of bounds nanosecond timestamp") from err
426+
attrname = npy_unit_to_attrname[reso]
427+
raise OutOfBoundsDatetime(f"Out of bounds {attrname} timestamp") from err
423428

424429
if obj.tzinfo is not None and not is_utc(obj.tzinfo):
425430
offset = get_utcoffset(obj.tzinfo, ts)
@@ -589,18 +594,18 @@ cdef check_overflows(_TSObject obj, NPY_DATETIMEUNIT reso=NPY_FR_ns):
589594
if obj.dts.year == lb.year:
590595
if not (obj.value < 0):
591596
from pandas._libs.tslibs.timestamps import Timestamp
592-
fmt = (f"{obj.dts.year}-{obj.dts.month:02d}-{obj.dts.day:02d} "
593-
f"{obj.dts.hour:02d}:{obj.dts.min:02d}:{obj.dts.sec:02d}")
597+
fmt = dts_to_iso_string(&obj.dts)
598+
min_ts = (<_Timestamp>Timestamp(0))._as_creso(reso).min
594599
raise OutOfBoundsDatetime(
595-
f"Converting {fmt} underflows past {Timestamp.min}"
600+
f"Converting {fmt} underflows past {min_ts}"
596601
)
597602
elif obj.dts.year == ub.year:
598603
if not (obj.value > 0):
599604
from pandas._libs.tslibs.timestamps import Timestamp
600-
fmt = (f"{obj.dts.year}-{obj.dts.month:02d}-{obj.dts.day:02d} "
601-
f"{obj.dts.hour:02d}:{obj.dts.min:02d}:{obj.dts.sec:02d}")
605+
fmt = dts_to_iso_string(&obj.dts)
606+
max_ts = (<_Timestamp>Timestamp(0))._as_creso(reso).max
602607
raise OutOfBoundsDatetime(
603-
f"Converting {fmt} overflows past {Timestamp.max}"
608+
f"Converting {fmt} overflows past {max_ts}"
604609
)
605610

606611
# ----------------------------------------------------------------------

pandas/_libs/tslibs/np_datetime.pyx

+13-10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ from libc.stdint cimport INT64_MAX
2222
import_datetime()
2323
PandasDateTime_IMPORT
2424

25+
import operator
26+
2527
import numpy as np
2628

2729
cimport numpy as cnp
@@ -35,6 +37,10 @@ from numpy cimport (
3537
uint8_t,
3638
)
3739

40+
from pandas._libs.tslibs.dtypes cimport (
41+
npy_unit_to_abbrev,
42+
npy_unit_to_attrname,
43+
)
3844
from pandas._libs.tslibs.util cimport get_c_string_buf_and_size
3945

4046

@@ -217,8 +223,8 @@ cdef check_dts_bounds(npy_datetimestruct *dts, NPY_DATETIMEUNIT unit=NPY_FR_ns):
217223

218224
if error:
219225
fmt = dts_to_iso_string(dts)
220-
# TODO: "nanosecond" in the message assumes NPY_FR_ns
221-
raise OutOfBoundsDatetime(f"Out of bounds nanosecond timestamp: {fmt}")
226+
attrname = npy_unit_to_attrname[unit]
227+
raise OutOfBoundsDatetime(f"Out of bounds {attrname} timestamp: {fmt}")
222228

223229

224230
# ----------------------------------------------------------------------
@@ -261,8 +267,9 @@ cdef int64_t pydatetime_to_dt64(datetime val,
261267
try:
262268
result = npy_datetimestruct_to_datetime(reso, dts)
263269
except OverflowError as err:
270+
attrname = npy_unit_to_attrname[reso]
264271
raise OutOfBoundsDatetime(
265-
f"Out of bounds nanosecond timestamp: {val}"
272+
f"Out of bounds {attrname} timestamp: {val}"
266273
) from err
267274

268275
return result
@@ -286,7 +293,8 @@ cdef int64_t pydate_to_dt64(
286293
try:
287294
result = npy_datetimestruct_to_datetime(reso, dts)
288295
except OverflowError as err:
289-
raise OutOfBoundsDatetime(f"Out of bounds nanosecond timestamp: {val}") from err
296+
attrname = npy_unit_to_attrname[reso]
297+
raise OutOfBoundsDatetime(f"Out of bounds {attrname} timestamp: {val}") from err
290298

291299
return result
292300

@@ -425,7 +433,7 @@ cpdef ndarray astype_overflowsafe(
425433
return iresult.view(dtype)
426434

427435

428-
# TODO: try to upstream this fix to numpy
436+
# TODO(numpy#16352): try to upstream this fix to numpy
429437
def compare_mismatched_resolutions(ndarray left, ndarray right, op):
430438
"""
431439
Overflow-safe comparison of timedelta64/datetime64 with mismatched resolutions.
@@ -483,9 +491,6 @@ def compare_mismatched_resolutions(ndarray left, ndarray right, op):
483491
return result
484492

485493

486-
import operator
487-
488-
489494
cdef int op_to_op_code(op):
490495
if op is operator.eq:
491496
return Py_EQ
@@ -538,8 +543,6 @@ cdef ndarray _astype_overflowsafe_to_smaller_unit(
538543
else:
539544
new_value, mod = divmod(value, mult)
540545
if not round_ok and mod != 0:
541-
# TODO: avoid runtime import
542-
from pandas._libs.tslibs.dtypes import npy_unit_to_abbrev
543546
from_abbrev = npy_unit_to_abbrev(from_unit)
544547
to_abbrev = npy_unit_to_abbrev(to_unit)
545548
raise ValueError(

pandas/_libs/tslibs/strptime.pyx

+5-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ from pandas._libs.tslibs.conversion cimport get_datetime64_nanos
5151
from pandas._libs.tslibs.dtypes cimport (
5252
get_supported_reso,
5353
npy_unit_to_abbrev,
54+
npy_unit_to_attrname,
5455
)
5556
from pandas._libs.tslibs.nattype cimport (
5657
NPY_NAT,
@@ -412,8 +413,9 @@ def array_strptime(
412413
try:
413414
value = npy_datetimestruct_to_datetime(creso, &dts)
414415
except OverflowError as err:
416+
attrname = npy_unit_to_attrname[creso]
415417
raise OutOfBoundsDatetime(
416-
f"Out of bounds nanosecond timestamp: {val}"
418+
f"Out of bounds {attrname} timestamp: {val}"
417419
) from err
418420
if out_local == 1:
419421
# Store the out_tzoffset in seconds
@@ -451,8 +453,9 @@ def array_strptime(
451453
try:
452454
iresult[i] = npy_datetimestruct_to_datetime(creso, &dts)
453455
except OverflowError as err:
456+
attrname = npy_unit_to_attrname[creso]
454457
raise OutOfBoundsDatetime(
455-
f"Out of bounds nanosecond timestamp: {val}"
458+
f"Out of bounds {attrname} timestamp: {val}"
456459
) from err
457460
result_timezone[i] = tz
458461

pandas/_libs/tslibs/timestamps.pyx

+4-3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ from pandas._libs.tslibs.conversion cimport (
6060
)
6161
from pandas._libs.tslibs.dtypes cimport (
6262
npy_unit_to_abbrev,
63+
npy_unit_to_attrname,
6364
periods_per_day,
6465
periods_per_second,
6566
)
@@ -447,15 +448,15 @@ cdef class _Timestamp(ABCTimestamp):
447448
nanos = other._value
448449

449450
try:
450-
new_value = self._value+ nanos
451+
new_value = self._value + nanos
451452
result = type(self)._from_value_and_reso(
452453
new_value, reso=self._creso, tz=self.tzinfo
453454
)
454455
except OverflowError as err:
455-
# TODO: don't hard-code nanosecond here
456456
new_value = int(self._value) + int(nanos)
457+
attrname = npy_unit_to_attrname[self._creso]
457458
raise OutOfBoundsDatetime(
458-
f"Out of bounds nanosecond timestamp: {new_value}"
459+
f"Out of bounds {attrname} timestamp: {new_value}"
459460
) from err
460461

461462
return result

pandas/tests/scalar/timestamp/test_constructors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ def test_bounds_with_different_units(self):
814814

815815
# With more extreme cases, we can't even fit inside second resolution
816816
info = np.iinfo(np.int64)
817-
msg = "Out of bounds nanosecond timestamp:"
817+
msg = "Out of bounds second timestamp:"
818818
for value in [info.min + 1, info.max]:
819819
for unit in ["D", "h", "m"]:
820820
dt64 = np.datetime64(value, unit)

pandas/tests/tools/test_to_datetime.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,7 @@ def test_to_datetime_dt64s_out_of_ns_bounds(self, cache, dt, errors):
11431143
def test_to_datetime_dt64d_out_of_bounds(self, cache):
11441144
dt64 = np.datetime64(np.iinfo(np.int64).max, "D")
11451145

1146-
msg = "Out of bounds nanosecond timestamp"
1146+
msg = "Out of bounds second timestamp: 25252734927768524-07-27"
11471147
with pytest.raises(OutOfBoundsDatetime, match=msg):
11481148
Timestamp(dt64)
11491149
with pytest.raises(OutOfBoundsDatetime, match=msg):

0 commit comments

Comments
 (0)