@@ -32,6 +32,7 @@ import_datetime()
32
32
33
33
cimport pandas._libs.tslibs.util as util
34
34
from pandas._libs cimport ops
35
+ from pandas._libs.missing cimport C_NA
35
36
from pandas._libs.tslibs.base cimport ABCTimestamp
36
37
from pandas._libs.tslibs.conversion cimport (
37
38
cast_from_unit,
@@ -308,21 +309,6 @@ cdef convert_to_timedelta64(object ts, str unit):
308
309
return ts.astype(" timedelta64[ns]" )
309
310
310
311
311
- cpdef to_timedelta64(object value, str unit):
312
- """
313
- Wrapper around convert_to_timedelta64() that does overflow checks.
314
- TODO: also construct non-nano
315
- TODO: do all overflow-unsafe operations here
316
- TODO: constrain unit to a more specific type
317
- """
318
- with cython.overflowcheck(True ):
319
- try :
320
- return convert_to_timedelta64(value, unit)
321
- except OverflowError as ex:
322
- msg = f" {value} outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
323
- raise OutOfBoundsTimedelta(msg) from ex
324
-
325
-
326
312
@ cython.boundscheck (False )
327
313
@ cython.wraparound (False )
328
314
def array_to_timedelta64 (
@@ -682,8 +668,7 @@ cdef bint _validate_ops_compat(other):
682
668
683
669
def _op_unary_method (func , name ):
684
670
def f (self ):
685
- new_value = func(self .value)
686
- return _timedelta_from_value_and_reso(new_value, self ._reso)
671
+ return create_timedelta(func(self .value), " ignore" , self ._reso)
687
672
f.__name__ = name
688
673
return f
689
674
@@ -700,20 +685,6 @@ cpdef int64_t calc_int_int(object op, object a, object b) except? -1:
700
685
raise OutOfBoundsTimedelta(msg) from ex
701
686
702
687
703
- cpdef int64_t calc_int_float(object op, object a, object b) except ? - 1 :
704
- """
705
- Calculate op(int, double), raising if any of the following aren't safe conversions:
706
- - a to int64_t
707
- - b to double
708
- - result to int64_t
709
- """
710
- try :
711
- return ops.calc_int_float(op, a, b)
712
- except OverflowError as ex:
713
- msg = f" outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
714
- raise OutOfBoundsTimedelta(msg) from ex
715
-
716
-
717
688
def _binary_op_method_timedeltalike (op , name ):
718
689
# define a binary operation that only works if the other argument is
719
690
# timedelta like or an array of timedeltalike
@@ -758,10 +729,7 @@ def _binary_op_method_timedeltalike(op, name):
758
729
if self ._reso != other._reso:
759
730
raise NotImplementedError
760
731
761
- result = calc_int_int(op, self .value, other.value)
762
- if result == NPY_NAT:
763
- return NaT
764
- return _timedelta_from_value_and_reso(result, self ._reso)
732
+ return create_timedelta(op(self .value, other.value), " ignore" , self ._reso)
765
733
766
734
f.__name__ = name
767
735
return f
@@ -892,7 +860,7 @@ cdef _to_py_int_float(v):
892
860
893
861
894
862
def _timedelta_unpickle (value , reso ):
895
- return _timedelta_from_value_and_reso (value, reso)
863
+ return create_timedelta (value, " ignore " , reso)
896
864
897
865
898
866
cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso):
@@ -923,6 +891,44 @@ cdef _timedelta_from_value_and_reso(int64_t value, NPY_DATETIMEUNIT reso):
923
891
return td_base
924
892
925
893
894
+ @ cython.overflowcheck (True )
895
+ cdef object create_timedelta(object value, str in_unit, NPY_DATETIMEUNIT out_reso):
896
+ """
897
+ Timedelta factory.
898
+
899
+ Timedelta.__new__ just does arg validation (at least currently). Also, some internal
900
+ functions expect to be able to create non-nano reso Timedeltas, but Timedelta.__new__
901
+ doesn't yet expose that.
902
+
903
+ _timedelta_from_value_and_reso does, but only accepts limited args, and doesn't check for overflow.
904
+ """
905
+ cdef:
906
+ int64_t out_value
907
+
908
+ if isinstance (value, _Timedelta):
909
+ return value
910
+ if value is C_NA:
911
+ raise ValueError (" Not supported" )
912
+
913
+ try :
914
+ # if unit == "ns", no need to create an m8[ns] just to read the (same) value back
915
+ # if unit == "ignore", assume caller wants to invoke an overflow-safe version of
916
+ # _timedelta_from_value_and_reso, and that any float rounding is acceptable
917
+ if (is_integer_object(value) or is_float_object(value)) and in_unit in (" ns" , " ignore" ):
918
+ if util.is_nan(value):
919
+ return NaT
920
+ out_value = < int64_t> value
921
+ else :
922
+ out_value = convert_to_timedelta64(value, in_unit).view(np.int64)
923
+ except OverflowError as ex:
924
+ msg = f" {value} outside allowed range [{TIMEDELTA_MIN_NS}ns, {TIMEDELTA_MAX_NS}ns]"
925
+ raise OutOfBoundsTimedelta(msg) from ex
926
+
927
+ if out_value == NPY_NAT:
928
+ return NaT
929
+ return _timedelta_from_value_and_reso(out_value, out_reso)
930
+
931
+
926
932
# Similar to Timestamp/datetime, this is a construction requirement for
927
933
# timedeltas that we need to do object instantiation in python. This will
928
934
# serve as a C extension type that shadows the Python class, where we do any
@@ -1406,7 +1412,7 @@ cdef class _Timedelta(timedelta):
1406
1412
@classmethod
1407
1413
def _from_value_and_reso(cls , int64_t value , NPY_DATETIMEUNIT reso ):
1408
1414
# exposing as classmethod for testing
1409
- return _timedelta_from_value_and_reso (value, reso)
1415
+ return create_timedelta (value, " ignore " , reso)
1410
1416
1411
1417
1412
1418
# Python front end to C extension type _Timedelta
@@ -1474,37 +1480,27 @@ class Timedelta(_Timedelta):
1474
1480
)
1475
1481
1476
1482
def __new__ (cls , object value = _no_input, unit = None , **kwargs ):
1477
- cdef _Timedelta td_base
1478
-
1479
- if isinstance (value, _Timedelta):
1480
- return value
1481
- if checknull_with_nat(value):
1482
- return NaT
1483
-
1484
- if unit in {" Y" , " y" , " M" }:
1485
- raise ValueError (
1486
- " Units 'M', 'Y', and 'y' are no longer supported, as they do not "
1487
- " represent unambiguous timedelta values durations."
1488
- )
1489
- if isinstance (value, str ) and unit is not None :
1490
- raise ValueError (" unit must not be specified if the value is a str" )
1491
- elif value is _no_input and not kwargs:
1492
- raise ValueError (
1493
- " cannot construct a Timedelta without a value/unit "
1494
- " or descriptive keywords (days,seconds....)"
1495
- )
1496
- if not kwargs.keys() <= set (cls ._allowed_kwargs):
1497
- raise ValueError (
1498
- " cannot construct a Timedelta from the passed arguments, "
1499
- f" allowed keywords are {cls._allowed_kwargs}"
1500
- )
1483
+ cdef:
1484
+ _Timedelta td_base
1485
+ NPY_DATETIMEUNIT out_reso = NPY_FR_ns
1501
1486
1502
- # GH43764, convert any input to nanoseconds first, to ensure any potential
1503
- # nanosecond contributions from kwargs parsed as floats are included
1504
- kwargs = collections.defaultdict(int , {key: _to_py_int_float(val) for key, val in kwargs.items()})
1505
- if kwargs:
1506
- value = to_timedelta64(
1507
- sum ((
1487
+ # process kwargs iff no value passed
1488
+ if value is _no_input:
1489
+ if not kwargs:
1490
+ raise ValueError (
1491
+ " cannot construct a Timedelta without a value/unit "
1492
+ " or descriptive keywords (days,seconds....)"
1493
+ )
1494
+ if not kwargs.keys() <= set (cls ._allowed_kwargs):
1495
+ raise ValueError (
1496
+ " cannot construct a Timedelta from the passed arguments, "
1497
+ f" allowed keywords are {cls._allowed_kwargs}"
1498
+ )
1499
+ # GH43764, convert any input to nanoseconds first, to ensure any potential
1500
+ # nanosecond contributions from kwargs parsed as floats are included
1501
+ kwargs = collections.defaultdict(int , {key: _to_py_int_float(val) for key, val in kwargs.items()})
1502
+ ns = sum (
1503
+ (
1508
1504
kwargs[" weeks" ] * 7 * 24 * 3600 * 1 _000_000_000,
1509
1505
kwargs[" days" ] * 24 * 3600 * 1 _000_000_000,
1510
1506
kwargs[" hours" ] * 3600 * 1 _000_000_000,
@@ -1513,19 +1509,18 @@ class Timedelta(_Timedelta):
1513
1509
kwargs[" milliseconds" ] * 1 _000_000,
1514
1510
kwargs[" microseconds" ] * 1 _000,
1515
1511
kwargs[" nanoseconds" ],
1516
- )),
1517
- " ns" ,
1512
+ )
1518
1513
)
1519
- else :
1520
- if is_integer_object(value) or is_float_object(value):
1521
- unit = parse_timedelta_unit(unit)
1522
- else :
1523
- unit = " ns"
1524
- value = to_timedelta64(value, unit)
1514
+ return create_timedelta(ns, " ns" , out_reso)
1525
1515
1526
- if is_td64nat(value):
1527
- return NaT
1528
- return _timedelta_from_value_and_reso(value.view(" i8" ), NPY_FR_ns)
1516
+ if isinstance (value, str ) and unit is not None :
1517
+ raise ValueError (" unit must not be specified if the value is a str" )
1518
+ elif unit in {" Y" , " y" , " M" }:
1519
+ raise ValueError (
1520
+ " Units 'M', 'Y', and 'y' are no longer supported, as they do not "
1521
+ " represent unambiguous timedelta values durations."
1522
+ )
1523
+ return create_timedelta(value, parse_timedelta_unit(unit), out_reso)
1529
1524
1530
1525
def __setstate__ (self , state ):
1531
1526
if len (state) == 1 :
@@ -1602,14 +1597,14 @@ class Timedelta(_Timedelta):
1602
1597
# Arithmetic Methods
1603
1598
# TODO: Can some of these be defined in the cython class?
1604
1599
1605
- __neg__ = _op_unary_method(lambda x : - x, ' __neg__' )
1606
- __pos__ = _op_unary_method(lambda x : x, ' __pos__' )
1607
- __abs__ = _op_unary_method(lambda x : abs (x), ' __abs__' )
1600
+ __neg__ = _op_unary_method(operator.neg, " __neg__" )
1601
+ __pos__ = _op_unary_method(operator.pos, " __pos__" )
1602
+ __abs__ = _op_unary_method(operator. abs, " __abs__" )
1608
1603
1609
- __add__ = _binary_op_method_timedeltalike(lambda x , y : x + y, ' __add__' )
1610
- __radd__ = _binary_op_method_timedeltalike(lambda x , y : x + y, ' __radd__' )
1611
- __sub__ = _binary_op_method_timedeltalike(lambda x , y : x - y, ' __sub__' )
1612
- __rsub__ = _binary_op_method_timedeltalike(lambda x , y : y - x, ' __rsub__' )
1604
+ __add__ = _binary_op_method_timedeltalike(operator.add, " __add__" )
1605
+ __radd__ = _binary_op_method_timedeltalike(operator.add, " __radd__" )
1606
+ __sub__ = _binary_op_method_timedeltalike(operator.sub, " __sub__" )
1607
+ __rsub__ = _binary_op_method_timedeltalike(lambda x , y : y - x, " __rsub__" )
1613
1608
1614
1609
def __mul__ (self , other ):
1615
1610
if util.is_nan(other):
@@ -1618,13 +1613,9 @@ class Timedelta(_Timedelta):
1618
1613
if is_array(other):
1619
1614
# ndarray-like
1620
1615
return other * self .to_timedelta64()
1621
- if is_integer_object(other):
1622
- value = calc_int_int(operator.mul, self .value, other)
1623
- return _timedelta_from_value_and_reso(value, self ._reso)
1624
- if is_float_object(other):
1625
- value = calc_int_float(operator.mul, self .value, other)
1626
- return _timedelta_from_value_and_reso(value, self ._reso)
1627
-
1616
+ if is_integer_object(other) or is_float_object(other):
1617
+ # can't call Timedelta b/c it doesn't (yet) expose reso
1618
+ return create_timedelta(self .value * other, " ignore" , self ._reso)
1628
1619
return NotImplemented
1629
1620
1630
1621
__rmul__ = __mul__
0 commit comments