@@ -333,58 +333,39 @@ cdef convert_to_timedelta64(object ts, str unit):
333333
334334 Handle these types of objects:
335335 - timedelta/Timedelta
336- - timedelta64
337- - an offset
338- - np.int64 (with unit providing a possible modifier)
339- - None/NaT
340336
341- Return an ns based int64
337+ Return an timedelta64[ns] object
342338 """
343339 # Caller is responsible for checking unit not in ["Y", "y", "M"]
344- if checknull_with_nat_and_na(ts):
345- return np.timedelta64(NPY_NAT, " ns" )
346- elif isinstance (ts, _Timedelta):
340+ if isinstance (ts, _Timedelta):
347341 # already in the proper format
348342 if ts._creso != NPY_FR_ns:
349343 ts = ts.as_unit(" ns" ).asm8
350344 else :
351345 ts = np.timedelta64(ts._value, " ns" )
352- elif cnp.is_timedelta64_object(ts):
353- ts = ensure_td64ns(ts)
354- elif is_integer_object(ts):
355- if ts == NPY_NAT:
356- return np.timedelta64(NPY_NAT, " ns" )
357- else :
358- ts = _maybe_cast_from_unit(ts, unit)
359- elif is_float_object(ts):
360- ts = _maybe_cast_from_unit(ts, unit)
361- elif isinstance (ts, str ):
362- if (len (ts) > 0 and ts[0 ] == " P" ) or (len (ts) > 1 and ts[:2 ] == " -P" ):
363- ts = parse_iso_format_string(ts)
364- else :
365- ts = parse_timedelta_string(ts)
366- ts = np.timedelta64(ts, " ns" )
367- elif is_tick_object(ts):
368- ts = np.timedelta64(ts.nanos, " ns" )
369346
370- if PyDelta_Check(ts):
347+ elif PyDelta_Check(ts):
371348 ts = np.timedelta64(delta_to_nanoseconds(ts), " ns" )
372349 elif not cnp.is_timedelta64_object(ts):
373350 raise TypeError (f" Invalid type for timedelta scalar: {type(ts)}" )
374351 return ts.astype(" timedelta64[ns]" )
375352
376353
377- cdef _maybe_cast_from_unit(ts , str unit):
354+ cdef _numeric_to_td64ns( object item , str unit):
378355 # caller is responsible for checking
379356 # assert unit not in ["Y", "y", "M"]
357+ # assert is_integer_object(item) or is_float_object(item)
358+ if is_integer_object(item) and item == NPY_NAT:
359+ return np.timedelta64(NPY_NAT, " ns" )
360+
380361 try :
381- ts = cast_from_unit(ts , unit)
362+ item = cast_from_unit(item , unit)
382363 except OutOfBoundsDatetime as err:
383364 raise OutOfBoundsTimedelta(
384- f" Cannot cast {ts } from {unit} to 'ns' without overflow."
365+ f" Cannot cast {item } from {unit} to 'ns' without overflow."
385366 ) from err
386367
387- ts = np.timedelta64(ts , " ns" )
368+ ts = np.timedelta64(item , " ns" )
388369 return ts
389370
390371
@@ -408,10 +389,11 @@ def array_to_timedelta64(
408389 cdef:
409390 Py_ssize_t i , n = values.size
410391 ndarray result = np.empty((< object > values).shape, dtype = " m8[ns]" )
411- object item
392+ object item , td64ns_obj
412393 int64_t ival
413394 cnp.broadcast mi = cnp.PyArray_MultiIterNew2(result, values)
414395 cnp.flatiter it
396+ str parsed_unit = parse_timedelta_unit(unit or " ns" )
415397
416398 if values.descr.type_num != cnp.NPY_OBJECT:
417399 # raise here otherwise we segfault below
@@ -431,70 +413,63 @@ def array_to_timedelta64(
431413 )
432414 cnp.PyArray_ITER_NEXT(it)
433415
434- # Usually, we have all strings. If so, we hit the fast path.
435- # If this path fails, we try conversion a different way, and
436- # this is where all of the error handling will take place.
437- try :
438- for i in range (n):
439- # Analogous to: item = values[i]
440- item = < object > (< PyObject** > cnp.PyArray_MultiIter_DATA(mi, 1 ))[0 ]
416+ for i in range (n):
417+ item = < object > (< PyObject** > cnp.PyArray_MultiIter_DATA(mi, 1 ))[0 ]
441418
442- ival = _item_to_timedelta64_fastpath(item)
419+ try :
420+ if checknull_with_nat_and_na(item):
421+ ival = NPY_NAT
443422
444- # Analogous to: iresult[i] = ival
445- (< int64_t* > cnp.PyArray_MultiIter_DATA(mi, 0 ))[0 ] = ival
423+ elif cnp.is_timedelta64_object(item):
424+ td64ns_obj = ensure_td64ns(item)
425+ ival = cnp.get_timedelta64_value(td64ns_obj)
446426
447- cnp.PyArray_MultiIter_NEXT(mi)
427+ elif isinstance (item, _Timedelta):
428+ if item._creso != NPY_FR_ns:
429+ ival = item.as_unit(" ns" )._value
430+ else :
431+ ival = item._value
432+
433+ elif PyDelta_Check(item):
434+ # i.e. isinstance(item, timedelta)
435+ ival = delta_to_nanoseconds(item)
436+
437+ elif isinstance (item, str ):
438+ if (
439+ (len (item) > 0 and item[0 ] == " P" )
440+ or (len (item) > 1 and item[:2 ] == " -P" )
441+ ):
442+ ival = parse_iso_format_string(item)
443+ else :
444+ ival = parse_timedelta_string(item)
448445
449- except ( TypeError , ValueError ):
450- cnp.PyArray_MultiIter_RESET(mi)
446+ elif is_tick_object(item ):
447+ ival = item.nanos
451448
452- parsed_unit = parse_timedelta_unit(unit or " ns " )
453- for i in range (n):
454- item = < object > ( < PyObject ** > cnp.PyArray_MultiIter_DATA(mi, 1 ))[ 0 ]
449+ elif is_integer_object(item) or is_float_object(item):
450+ td64ns_obj = _numeric_to_td64ns(item, parsed_unit)
451+ ival = cnp.get_timedelta64_value(td64ns_obj)
455452
456- ival = _item_to_timedelta64(item, parsed_unit, errors)
453+ else :
454+ raise TypeError (f" Invalid type for timedelta scalar: {type(item)}" )
455+
456+ except ValueError as err:
457+ if errors == " coerce" :
458+ ival = NPY_NAT
459+ elif " unit abbreviation w/o a number" in str (err):
460+ # re-raise with more pertinent message
461+ msg = f" Could not convert '{item}' to NumPy timedelta"
462+ raise ValueError (msg) from err
463+ else :
464+ raise
457465
458- (< int64_t* > cnp.PyArray_MultiIter_DATA(mi, 0 ))[0 ] = ival
466+ (< int64_t* > cnp.PyArray_MultiIter_DATA(mi, 0 ))[0 ] = ival
459467
460- cnp.PyArray_MultiIter_NEXT(mi)
468+ cnp.PyArray_MultiIter_NEXT(mi)
461469
462470 return result
463471
464472
465- cdef int64_t _item_to_timedelta64_fastpath(object item) except ? - 1 :
466- """
467- See array_to_timedelta64.
468- """
469- if item is NaT:
470- # we allow this check in the fast-path because NaT is a C-object
471- # so this is an inexpensive check
472- return NPY_NAT
473- else :
474- return parse_timedelta_string(item)
475-
476-
477- cdef int64_t _item_to_timedelta64(
478- object item,
479- str parsed_unit,
480- str errors
481- ) except ? - 1 :
482- """
483- See array_to_timedelta64.
484- """
485- try :
486- return cnp.get_timedelta64_value(convert_to_timedelta64(item, parsed_unit))
487- except ValueError as err:
488- if errors == " coerce" :
489- return NPY_NAT
490- elif " unit abbreviation w/o a number" in str (err):
491- # re-raise with more pertinent message
492- msg = f" Could not convert '{item}' to NumPy timedelta"
493- raise ValueError (msg) from err
494- else :
495- raise
496-
497-
498473@ cython.cpow (True )
499474cdef int64_t parse_timedelta_string(str ts) except ? - 1 :
500475 """
@@ -798,7 +773,7 @@ def _binary_op_method_timedeltalike(op, name):
798773 return NotImplemented
799774
800775 try :
801- other = Timedelta (other)
776+ other = _wrapped_to_timedelta (other)
802777 except ValueError :
803778 # failed to parse as timedelta
804779 return NotImplemented
@@ -2154,12 +2129,14 @@ class Timedelta(_Timedelta):
21542129 new_value = delta_to_nanoseconds(value, reso = new_reso)
21552130 return cls ._from_value_and_reso(new_value, reso = new_reso)
21562131
2132+ elif checknull_with_nat_and_na(value):
2133+ return NaT
2134+
21572135 elif is_integer_object(value) or is_float_object(value):
21582136 # unit=None is de-facto 'ns'
21592137 unit = parse_timedelta_unit(unit)
2160- value = convert_to_timedelta64(value, unit)
2161- elif checknull_with_nat_and_na(value):
2162- return NaT
2138+ value = _numeric_to_td64ns(value, unit)
2139+
21632140 else :
21642141 raise ValueError (
21652142 " Value must be Timedelta, string, integer, "
@@ -2341,7 +2318,7 @@ class Timedelta(_Timedelta):
23412318 def __truediv__ (self , other ):
23422319 if _should_cast_to_timedelta(other):
23432320 # We interpret NaT as timedelta64("NaT")
2344- other = Timedelta (other)
2321+ other = _wrapped_to_timedelta (other)
23452322 if other is NaT:
23462323 return np.nan
23472324 if other._creso != self ._creso:
@@ -2374,7 +2351,7 @@ class Timedelta(_Timedelta):
23742351 def __rtruediv__ (self , other ):
23752352 if _should_cast_to_timedelta(other):
23762353 # We interpret NaT as timedelta64("NaT")
2377- other = Timedelta (other)
2354+ other = _wrapped_to_timedelta (other)
23782355 if other is NaT:
23792356 return np.nan
23802357 if self ._creso != other._creso:
@@ -2402,7 +2379,7 @@ class Timedelta(_Timedelta):
24022379 # just defer
24032380 if _should_cast_to_timedelta(other):
24042381 # We interpret NaT as timedelta64("NaT")
2405- other = Timedelta (other)
2382+ other = _wrapped_to_timedelta (other)
24062383 if other is NaT:
24072384 return np.nan
24082385 if self ._creso != other._creso:
@@ -2457,7 +2434,7 @@ class Timedelta(_Timedelta):
24572434 # just defer
24582435 if _should_cast_to_timedelta(other):
24592436 # We interpret NaT as timedelta64("NaT")
2460- other = Timedelta (other)
2437+ other = _wrapped_to_timedelta (other)
24612438 if other is NaT:
24622439 return np.nan
24632440 if self ._creso != other._creso:
@@ -2525,6 +2502,7 @@ def truediv_object_array(ndarray left, ndarray right):
25252502 if cnp.get_timedelta64_value(td64) == NPY_NAT:
25262503 # td here should be interpreted as a td64 NaT
25272504 if _should_cast_to_timedelta(obj):
2505+ _wrapped_to_timedelta(obj) # deprecate if allowing string
25282506 res_value = np.nan
25292507 else :
25302508 # if its a number then let numpy handle division, otherwise
@@ -2554,6 +2532,7 @@ def floordiv_object_array(ndarray left, ndarray right):
25542532 if cnp.get_timedelta64_value(td64) == NPY_NAT:
25552533 # td here should be interpreted as a td64 NaT
25562534 if _should_cast_to_timedelta(obj):
2535+ _wrapped_to_timedelta(obj) # deprecate allowing string
25572536 res_value = np.nan
25582537 else :
25592538 # if its a number then let numpy handle division, otherwise
@@ -2585,6 +2564,23 @@ cdef bint is_any_td_scalar(object obj):
25852564 )
25862565
25872566
2567+ cdef inline _wrapped_to_timedelta(object other):
2568+ # Helper for deprecating cases where we cast str to Timedelta
2569+ td = Timedelta(other)
2570+ if isinstance (other, str ):
2571+ from pandas.errors import Pandas4Warning
2572+ warnings.warn(
2573+ # GH#59653
2574+ " Scalar operations between Timedelta and string are "
2575+ " deprecated and will raise in a future version. "
2576+ " Explicitly cast to Timedelta first." ,
2577+ Pandas4Warning,
2578+ stacklevel = find_stack_level(),
2579+ )
2580+ # When this is enforced, remove str from _should_cast_to_timedelta
2581+ return td
2582+
2583+
25882584cdef bint _should_cast_to_timedelta(object obj):
25892585 """
25902586 Should we treat this object as a Timedelta for the purpose of a binary op
0 commit comments