21
21
unpack_for_encoding ,
22
22
)
23
23
from xarray .core import indexing
24
+ from xarray .core .array_api_compat import get_array_namespace
24
25
from xarray .core .common import contains_cftime_datetimes , is_np_datetime_like
25
- from xarray .core .duck_array_ops import array_all , array_any , asarray , ravel , reshape
26
+ from xarray .core .duck_array_ops import (
27
+ array_all ,
28
+ array_any ,
29
+ asarray ,
30
+ astype ,
31
+ concatenate ,
32
+ isnull ,
33
+ ravel ,
34
+ reshape ,
35
+ )
26
36
from xarray .core .formatting import first_n_items , format_timestamp , last_item
27
37
from xarray .core .pdcompat import default_precision_timestamp , timestamp_as_unit
28
38
from xarray .core .utils import attempt_import , emit_user_level_warning
29
39
from xarray .core .variable import Variable
30
40
from xarray .namedarray .parallelcompat import T_ChunkedArray , get_chunked_array_type
31
- from xarray .namedarray .pycompat import is_chunked_array , to_numpy
41
+ from xarray .namedarray .pycompat import is_chunked_array , to_duck_array , to_numpy
32
42
from xarray .namedarray .utils import is_duck_dask_array
33
43
34
44
try :
@@ -100,7 +110,7 @@ def _is_numpy_compatible_time_range(times):
100
110
if is_np_datetime_like (times .dtype ):
101
111
return True
102
112
# times array contains cftime objects
103
- times = np . asarray (times )
113
+ times = to_duck_array (times )
104
114
tmin = times .min ()
105
115
tmax = times .max ()
106
116
try :
@@ -309,8 +319,9 @@ def _decode_cf_datetime_dtype(
309
319
# successfully. Otherwise, tracebacks end up swallowed by
310
320
# Dataset.__repr__ when users try to view their lazily decoded array.
311
321
values = indexing .ImplicitToExplicitIndexingAdapter (indexing .as_indexable (data ))
312
- example_value = np .concatenate (
313
- [to_numpy (first_n_items (values , 1 ) or [0 ]), to_numpy (last_item (values ) or [0 ])]
322
+ zero = asarray ([0 ], xp = get_array_namespace (values ))
323
+ example_value = concatenate (
324
+ [first_n_items (values , 1 ) or zero , last_item (values ) or zero ]
314
325
)
315
326
316
327
try :
@@ -342,7 +353,13 @@ def _decode_datetime_with_cftime(
342
353
cftime = attempt_import ("cftime" )
343
354
if num_dates .size > 0 :
344
355
return np .asarray (
345
- cftime .num2date (num_dates , units , calendar , only_use_cftime_datetimes = True )
356
+ cftime .num2date (
357
+ # cftime uses Cython so we must convert to numpy here.
358
+ to_numpy (num_dates ),
359
+ units ,
360
+ calendar ,
361
+ only_use_cftime_datetimes = True ,
362
+ )
346
363
)
347
364
else :
348
365
return np .array ([], dtype = object )
@@ -357,7 +374,7 @@ def _check_date_for_units_since_refdate(
357
374
f"Value { date } can't be represented as Datetime/Timedelta."
358
375
)
359
376
delta = date * np .timedelta64 (1 , unit )
360
- if not np . isnan (delta ):
377
+ if not isnull (delta ):
361
378
# this will raise on dtype overflow for integer dtypes
362
379
if date .dtype .kind in "u" and not np .int64 (delta ) == date :
363
380
raise OutOfBoundsTimedelta (
@@ -381,7 +398,7 @@ def _check_timedelta_range(value, data_unit, time_unit):
381
398
"ignore" , "invalid value encountered in multiply" , RuntimeWarning
382
399
)
383
400
delta = value * np .timedelta64 (1 , data_unit )
384
- if not np . isnan (delta ):
401
+ if not isnull (delta ):
385
402
# this will raise on dtype overflow for integer dtypes
386
403
if value .dtype .kind in "u" and not np .int64 (delta ) == value :
387
404
raise OutOfBoundsTimedelta (
@@ -449,9 +466,9 @@ def _decode_datetime_with_pandas(
449
466
# respectively. See https://github.com/pandas-dev/pandas/issues/56996 for
450
467
# more details.
451
468
if flat_num_dates .dtype .kind == "i" :
452
- flat_num_dates = flat_num_dates . astype (np .int64 )
469
+ flat_num_dates = astype (flat_num_dates , np .int64 )
453
470
elif flat_num_dates .dtype .kind == "u" :
454
- flat_num_dates = flat_num_dates . astype (np .uint64 )
471
+ flat_num_dates = astype (flat_num_dates , np .uint64 )
455
472
456
473
try :
457
474
time_unit , ref_date = _unpack_time_unit_and_ref_date (units )
@@ -483,9 +500,9 @@ def _decode_datetime_with_pandas(
483
500
# overflow when converting to np.int64 would not be representable with a
484
501
# timedelta64 value, and therefore would raise an error in the lines above.
485
502
if flat_num_dates .dtype .kind in "iu" :
486
- flat_num_dates = flat_num_dates . astype (np .int64 )
503
+ flat_num_dates = astype (flat_num_dates , np .int64 )
487
504
elif flat_num_dates .dtype .kind in "f" :
488
- flat_num_dates = flat_num_dates . astype (np .float64 )
505
+ flat_num_dates = astype (flat_num_dates , np .float64 )
489
506
490
507
timedeltas = _numbers_to_timedelta (
491
508
flat_num_dates , time_unit , ref_date .unit , "datetime"
@@ -528,8 +545,12 @@ def decode_cf_datetime(
528
545
)
529
546
except (KeyError , OutOfBoundsDatetime , OutOfBoundsTimedelta , OverflowError ):
530
547
dates = _decode_datetime_with_cftime (
531
- flat_num_dates . astype (float ), units , calendar
548
+ astype (flat_num_dates , float ), units , calendar
532
549
)
550
+ # This conversion to numpy is only needed for nanarg* below.
551
+ # TODO: explore removing it.
552
+ # Note that `dates` is already a numpy object array of cftime objects.
553
+ num_dates = to_numpy (num_dates )
533
554
# retrieve cftype
534
555
dates_min = dates [np .nanargmin (num_dates )]
535
556
dates_max = dates [np .nanargmax (num_dates )]
@@ -586,16 +607,16 @@ def _numbers_to_timedelta(
586
607
"""Transform numbers to np.timedelta64."""
587
608
# keep NaT/nan mask
588
609
if flat_num .dtype .kind == "f" :
589
- nan = np . asarray ( np . isnan ( flat_num ) )
610
+ nan = isnull ( flat_num )
590
611
elif flat_num .dtype .kind == "i" :
591
- nan = np . asarray ( flat_num == np .iinfo (np .int64 ).min )
612
+ nan = flat_num == np .iinfo (np .int64 ).min
592
613
593
614
# in case we need to change the unit, we fix the numbers here
594
615
# this should be safe, as errors would have been raised above
595
616
ns_time_unit = _NS_PER_TIME_DELTA [time_unit ]
596
617
ns_ref_date_unit = _NS_PER_TIME_DELTA [ref_unit ]
597
618
if ns_time_unit > ns_ref_date_unit :
598
- flat_num = np . asarray ( flat_num * np .int64 (ns_time_unit / ns_ref_date_unit ) )
619
+ flat_num = flat_num * np .int64 (ns_time_unit / ns_ref_date_unit )
599
620
time_unit = ref_unit
600
621
601
622
# estimate fitting resolution for floating point values
@@ -618,12 +639,12 @@ def _numbers_to_timedelta(
618
639
# to prevent casting NaN to int
619
640
with warnings .catch_warnings ():
620
641
warnings .simplefilter ("ignore" , RuntimeWarning )
621
- flat_num = flat_num . astype (np .int64 )
622
- if nan . any ( ):
642
+ flat_num = astype (flat_num , np .int64 )
643
+ if array_any ( nan ):
623
644
flat_num [nan ] = np .iinfo (np .int64 ).min
624
645
625
646
# cast to wanted type
626
- return flat_num . astype (f"timedelta64[{ time_unit } ]" )
647
+ return astype (flat_num , f"timedelta64[{ time_unit } ]" )
627
648
628
649
629
650
def decode_cf_timedelta (
@@ -712,8 +733,8 @@ def infer_datetime_units(dates) -> str:
712
733
'hours', 'minutes' or 'seconds' (the first one that can evenly divide all
713
734
unique time deltas in `dates`)
714
735
"""
715
- dates = ravel (np . asarray (dates ))
716
- if np .issubdtype (np . asarray ( dates ) .dtype , "datetime64" ):
736
+ dates = ravel (to_duck_array (dates ))
737
+ if np .issubdtype (dates .dtype , "datetime64" ):
717
738
dates = to_datetime_unboxed (dates )
718
739
dates = dates [pd .notnull (dates )]
719
740
reference_date = dates [0 ] if len (dates ) > 0 else "1970-01-01"
0 commit comments