@@ -136,6 +136,9 @@ cdef dict timedelta_abbrevs = {
136
136
137
137
_no_input = object ()
138
138
139
+ TIMEDELTA_MIN_NS = np.iinfo(np.int64).min + 1
140
+ TIMEDELTA_MAX_NS = np.iinfo(np.int64).max
141
+
139
142
140
143
# ----------------------------------------------------------------------
141
144
# API
@@ -217,7 +220,8 @@ cpdef int64_t delta_to_nanoseconds(delta) except? -1:
217
220
+ delta.microseconds
218
221
) * 1000
219
222
except OverflowError as err:
220
- raise OutOfBoundsTimedelta(* err.args) from err
223
+ msg = f" {delta} outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
224
+ raise OutOfBoundsTimedelta(msg) from err
221
225
222
226
raise TypeError (type (delta))
223
227
@@ -254,7 +258,8 @@ cdef object ensure_td64ns(object ts):
254
258
# NB: cython#1381 this cannot be *=
255
259
td64_value = td64_value * mult
256
260
except OverflowError as err:
257
- raise OutOfBoundsTimedelta(ts) from err
261
+ msg = f" {ts} outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
262
+ raise OutOfBoundsTimedelta(msg) from err
258
263
259
264
return np.timedelta64(td64_value, " ns" )
260
265
@@ -679,6 +684,18 @@ def _op_unary_method(func, name):
679
684
return f
680
685
681
686
687
+ cpdef int64_t calculate(object op, int64_t a, int64_t b) except ? - 1 :
688
+ """
689
+ Calculate op(a, b) and return the result, or raise if the operation would overflow.
690
+ """
691
+ try :
692
+ with cython.overflowcheck(True ):
693
+ return op(a, b)
694
+ except OverflowError as ex:
695
+ msg = f" outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
696
+ raise OutOfBoundsTimedelta(msg) from ex
697
+
698
+
682
699
def _binary_op_method_timedeltalike (op , name ):
683
700
# define a binary operation that only works if the other argument is
684
701
# timedelta like or an array of timedeltalike
@@ -723,13 +740,10 @@ def _binary_op_method_timedeltalike(op, name):
723
740
if self ._reso != other._reso:
724
741
raise NotImplementedError
725
742
726
- res = op(self .value, other.value)
727
- if res == NPY_NAT:
728
- # e.g. test_implementation_limits
729
- # TODO: more generally could do an overflowcheck in op?
743
+ result = calculate(op, self .value, other.value)
744
+ if result == NPY_NAT:
730
745
return NaT
731
-
732
- return _timedelta_from_value_and_reso(res, reso = self ._reso)
746
+ return _timedelta_from_value_and_reso(result, self ._reso)
733
747
734
748
f.__name__ = name
735
749
return f
@@ -1443,91 +1457,96 @@ class Timedelta(_Timedelta):
1443
1457
def __new__ (cls , object value = _no_input, unit = None , **kwargs ):
1444
1458
cdef _Timedelta td_base
1445
1459
1446
- if value is _no_input:
1447
- if not len (kwargs):
1448
- raise ValueError (" cannot construct a Timedelta without a "
1449
- " value/unit or descriptive keywords "
1450
- " (days,seconds....)" )
1460
+ try :
1461
+ if value is _no_input:
1462
+ if not len (kwargs):
1463
+ raise ValueError (" cannot construct a Timedelta without a "
1464
+ " value/unit or descriptive keywords "
1465
+ " (days,seconds....)" )
1466
+
1467
+ kwargs = {key: _to_py_int_float(kwargs[key]) for key in kwargs}
1468
+
1469
+ unsupported_kwargs = set (kwargs)
1470
+ unsupported_kwargs.difference_update(cls ._req_any_kwargs_new)
1471
+ if unsupported_kwargs or not cls ._req_any_kwargs_new.intersection(kwargs):
1472
+ raise ValueError (
1473
+ " cannot construct a Timedelta from the passed arguments, "
1474
+ " allowed keywords are "
1475
+ " [weeks, days, hours, minutes, seconds, "
1476
+ " milliseconds, microseconds, nanoseconds]"
1477
+ )
1478
+
1479
+ # GH43764, convert any input to nanoseconds first and then
1480
+ # create the timestamp. This ensures that any potential
1481
+ # nanosecond contributions from kwargs parsed as floats
1482
+ # are taken into consideration.
1483
+ seconds = int ((
1484
+ (
1485
+ (kwargs.get(' days' , 0 ) + kwargs.get(' weeks' , 0 ) * 7 ) * 24
1486
+ + kwargs.get(' hours' , 0 )
1487
+ ) * 3600
1488
+ + kwargs.get(' minutes' , 0 ) * 60
1489
+ + kwargs.get(' seconds' , 0 )
1490
+ ) * 1 _000_000_000
1491
+ )
1451
1492
1452
- kwargs = {key: _to_py_int_float(kwargs[key]) for key in kwargs}
1493
+ value = np.timedelta64(
1494
+ int (kwargs.get(' nanoseconds' , 0 ))
1495
+ + int (kwargs.get(' microseconds' , 0 ) * 1 _000)
1496
+ + int (kwargs.get(' milliseconds' , 0 ) * 1 _000_000)
1497
+ + seconds
1498
+ )
1453
1499
1454
- unsupported_kwargs = set (kwargs)
1455
- unsupported_kwargs.difference_update(cls ._req_any_kwargs_new)
1456
- if unsupported_kwargs or not cls ._req_any_kwargs_new.intersection(kwargs):
1500
+ if unit in {' Y' , ' y' , ' M' }:
1457
1501
raise ValueError (
1458
- " cannot construct a Timedelta from the passed arguments, "
1459
- " allowed keywords are "
1460
- " [weeks, days, hours, minutes, seconds, "
1461
- " milliseconds, microseconds, nanoseconds]"
1502
+ " Units 'M', 'Y', and 'y' are no longer supported, as they do not "
1503
+ " represent unambiguous timedelta values durations."
1462
1504
)
1463
1505
1464
- # GH43764, convert any input to nanoseconds first and then
1465
- # create the timestamp. This ensures that any potential
1466
- # nanosecond contributions from kwargs parsed as floats
1467
- # are taken into consideration.
1468
- seconds = int ((
1469
- (
1470
- (kwargs.get(' days' , 0 ) + kwargs.get(' weeks' , 0 ) * 7 ) * 24
1471
- + kwargs.get(' hours' , 0 )
1472
- ) * 3600
1473
- + kwargs.get(' minutes' , 0 ) * 60
1474
- + kwargs.get(' seconds' , 0 )
1475
- ) * 1 _000_000_000
1476
- )
1477
-
1478
- value = np.timedelta64(
1479
- int (kwargs.get(' nanoseconds' , 0 ))
1480
- + int (kwargs.get(' microseconds' , 0 ) * 1 _000)
1481
- + int (kwargs.get(' milliseconds' , 0 ) * 1 _000_000)
1482
- + seconds
1483
- )
1484
-
1485
- if unit in {' Y' , ' y' , ' M' }:
1486
- raise ValueError (
1487
- " Units 'M', 'Y', and 'y' are no longer supported, as they do not "
1488
- " represent unambiguous timedelta values durations."
1489
- )
1490
-
1491
- # GH 30543 if pd.Timedelta already passed, return it
1492
- # check that only value is passed
1493
- if isinstance (value, _Timedelta) and unit is None and len (kwargs) == 0 :
1494
- return value
1495
- elif isinstance (value, _Timedelta):
1496
- value = value.value
1497
- elif isinstance (value, str ):
1498
- if unit is not None :
1499
- raise ValueError (" unit must not be specified if the value is a str" )
1500
- if (len (value) > 0 and value[0 ] == ' P' ) or (
1501
- len (value) > 1 and value[:2 ] == ' -P'
1502
- ):
1503
- value = parse_iso_format_string(value)
1506
+ # GH 30543 if pd.Timedelta already passed, return it
1507
+ # check that only value is passed
1508
+ if isinstance (value, _Timedelta) and unit is None and len (kwargs) == 0 :
1509
+ return value
1510
+ elif isinstance (value, _Timedelta):
1511
+ value = value.value
1512
+ elif isinstance (value, str ):
1513
+ if unit is not None :
1514
+ raise ValueError (" unit must not be specified if the value is a str" )
1515
+ if (len (value) > 0 and value[0 ] == ' P' ) or (
1516
+ len (value) > 1 and value[:2 ] == ' -P'
1517
+ ):
1518
+ value = parse_iso_format_string(value)
1519
+ else :
1520
+ value = parse_timedelta_string(value)
1521
+ value = np.timedelta64(value)
1522
+ elif PyDelta_Check(value):
1523
+ value = convert_to_timedelta64(value, ' ns' )
1524
+ elif is_timedelta64_object(value):
1525
+ value = ensure_td64ns(value)
1526
+ elif is_tick_object(value):
1527
+ value = np.timedelta64(value.nanos, ' ns' )
1528
+ elif is_integer_object(value) or is_float_object(value):
1529
+ # unit=None is de-facto 'ns'
1530
+ unit = parse_timedelta_unit(unit)
1531
+ value = convert_to_timedelta64(value, unit)
1532
+ elif checknull_with_nat(value):
1533
+ return NaT
1504
1534
else :
1505
- value = parse_timedelta_string(value)
1506
- value = np.timedelta64(value)
1507
- elif PyDelta_Check(value):
1508
- value = convert_to_timedelta64(value, ' ns' )
1509
- elif is_timedelta64_object(value):
1510
- value = ensure_td64ns(value)
1511
- elif is_tick_object(value):
1512
- value = np.timedelta64(value.nanos, ' ns' )
1513
- elif is_integer_object(value) or is_float_object(value):
1514
- # unit=None is de-facto 'ns'
1515
- unit = parse_timedelta_unit(unit)
1516
- value = convert_to_timedelta64(value, unit)
1517
- elif checknull_with_nat(value):
1518
- return NaT
1519
- else :
1520
- raise ValueError (
1521
- " Value must be Timedelta, string, integer, "
1522
- f" float, timedelta or convertible, not {type(value).__name__}"
1523
- )
1535
+ raise ValueError (
1536
+ " Value must be Timedelta, string, integer, "
1537
+ f" float, timedelta or convertible, not {type(value).__name__}"
1538
+ )
1524
1539
1525
- if is_timedelta64_object(value):
1526
- value = value.view(' i8' )
1540
+ if is_timedelta64_object(value):
1541
+ value = value.view(' i8' )
1527
1542
1528
- # nat
1529
- if value == NPY_NAT:
1530
- return NaT
1543
+ # nat
1544
+ if value == NPY_NAT:
1545
+ return NaT
1546
+
1547
+ except OverflowError as ex:
1548
+ msg = f" outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
1549
+ raise OutOfBoundsTimedelta(msg) from ex
1531
1550
1532
1551
return _timedelta_from_value_and_reso(value, NPY_FR_ns)
1533
1552
@@ -1824,6 +1843,6 @@ cdef _broadcast_floordiv_td64(
1824
1843
1825
1844
1826
1845
# resolution in ns
1827
- Timedelta.min = Timedelta(np.iinfo(np.int64).min + 1 )
1828
- Timedelta.max = Timedelta(np.iinfo(np.int64).max )
1846
+ Timedelta.min = Timedelta(TIMEDELTA_MIN_NS )
1847
+ Timedelta.max = Timedelta(TIMEDELTA_MAX_NS )
1829
1848
Timedelta.resolution = Timedelta(nanoseconds = 1 )
0 commit comments