@@ -6,6 +6,7 @@ import cython
6
6
from cpython.object cimport (
7
7
Py_EQ,
8
8
Py_NE,
9
+ PyObject,
9
10
PyObject_RichCompare,
10
11
)
11
12
@@ -312,8 +313,9 @@ cdef convert_to_timedelta64(object ts, str unit):
312
313
@ cython.boundscheck (False )
313
314
@ cython.wraparound (False )
314
315
def array_to_timedelta64 (
315
- ndarray[object] values , str unit = None , str errors = " raise"
316
+ ndarray values , str unit = None , str errors = " raise"
316
317
) -> ndarray:
318
+ # values is object-dtype , may be 2D
317
319
"""
318
320
Convert an ndarray to an array of timedeltas. If errors == 'coerce',
319
321
coerce non-convertible objects to NaT. Otherwise , raise.
@@ -324,19 +326,26 @@ def array_to_timedelta64(
324
326
"""
325
327
326
328
cdef:
327
- Py_ssize_t i , n
328
- int64_t[:] iresult
329
+ Py_ssize_t i , n = values.size
330
+ ndarray result = np.empty((< object > values).shape, dtype = " m8[ns]" )
331
+ object item
332
+ int64_t ival
333
+ cnp.broadcast mi = cnp.PyArray_MultiIterNew2(result, values)
334
+ cnp.flatiter it
335
+
336
+ if values.descr.type_num != cnp.NPY_OBJECT:
337
+ # raise here otherwise we segfault below
338
+ raise TypeError("array_to_timedelta64 'values' must have object dtype")
329
339
330
340
if errors not in {'ignore', 'raise', 'coerce'}:
331
341
raise ValueError("errors must be one of {'ignore', 'raise', or 'coerce'}")
332
342
333
- n = values.shape[0 ]
334
- result = np.empty(n, dtype = ' m8[ns]' )
335
- iresult = result.view(' i8' )
336
-
337
- if unit is not None:
343
+ if unit is not None and errors != "coerce":
344
+ it = cnp.PyArray_IterNew(values)
338
345
for i in range(n ):
339
- if isinstance (values[i], str ) and errors != " coerce" :
346
+ # Analogous to: item = values[i]
347
+ item = cnp.PyArray_GETITEM(values, cnp.PyArray_ITER_DATA(it))
348
+ if isinstance (item, str ):
340
349
raise ValueError (
341
350
" unit must not be specified if the input contains a str"
342
351
)
@@ -346,28 +355,59 @@ def array_to_timedelta64(
346
355
# this is where all of the error handling will take place.
347
356
try :
348
357
for i in range (n):
349
- if values[i] is NaT:
350
- # we allow this check in the fast-path because NaT is a C-object
351
- # so this is an inexpensive check
352
- iresult[i] = NPY_NAT
353
- else :
354
- result[i] = parse_timedelta_string(values[i])
358
+ # Analogous to: item = values[i]
359
+ item = < object > (< PyObject** > cnp.PyArray_MultiIter_DATA(mi, 1 ))[0 ]
360
+
361
+ ival = _item_to_timedelta64_fastpath(item)
362
+
363
+ # Analogous to: iresult[i] = ival
364
+ (< int64_t* > cnp.PyArray_MultiIter_DATA(mi, 0 ))[0 ] = ival
365
+
366
+ cnp.PyArray_MultiIter_NEXT(mi)
367
+
355
368
except (TypeError , ValueError ):
369
+ cnp.PyArray_MultiIter_RESET(mi)
370
+
356
371
parsed_unit = parse_timedelta_unit(unit or ' ns' )
357
372
for i in range (n):
358
- try :
359
- result[i] = convert_to_timedelta64(values[i], parsed_unit)
360
- except ValueError as err:
361
- if errors == ' coerce' :
362
- result[i] = NPY_NAT
363
- elif " unit abbreviation w/o a number" in str (err):
364
- # re-raise with more pertinent message
365
- msg = f" Could not convert '{values[i]}' to NumPy timedelta"
366
- raise ValueError (msg) from err
367
- else :
368
- raise
373
+ item = < object > (< PyObject** > cnp.PyArray_MultiIter_DATA(mi, 1 ))[0 ]
374
+
375
+ ival = _item_to_timedelta64(item, parsed_unit, errors)
369
376
370
- return iresult.base # .base to access underlying np.ndarray
377
+ (< int64_t* > cnp.PyArray_MultiIter_DATA(mi, 0 ))[0 ] = ival
378
+
379
+ cnp.PyArray_MultiIter_NEXT(mi)
380
+
381
+ return result
382
+
383
+
384
+ cdef inline int64_t _item_to_timedelta64_fastpath(object item) except ? - 1 :
385
+ """
386
+ See array_to_timedelta64.
387
+ """
388
+ if item is NaT:
389
+ # we allow this check in the fast-path because NaT is a C-object
390
+ # so this is an inexpensive check
391
+ return NPY_NAT
392
+ else :
393
+ return parse_timedelta_string(item)
394
+
395
+
396
+ cdef inline int64_t _item_to_timedelta64(object item, str parsed_unit, str errors) except ? - 1 :
397
+ """
398
+ See array_to_timedelta64.
399
+ """
400
+ try :
401
+ return get_timedelta64_value(convert_to_timedelta64(item, parsed_unit))
402
+ except ValueError as err:
403
+ if errors == " coerce" :
404
+ return NPY_NAT
405
+ elif " unit abbreviation w/o a number" in str (err):
406
+ # re-raise with more pertinent message
407
+ msg = f" Could not convert '{item}' to NumPy timedelta"
408
+ raise ValueError (msg) from err
409
+ else :
410
+ raise
371
411
372
412
373
413
cdef inline int64_t parse_timedelta_string(str ts) except ? - 1 :
0 commit comments