75
75
from pandas .core .construction import extract_array
76
76
from pandas .core .indexes .base import Index
77
77
from pandas .core .indexes .datetimes import DatetimeIndex
78
-
79
78
if TYPE_CHECKING :
80
79
from collections .abc import (
81
80
Callable ,
@@ -479,49 +478,70 @@ def _array_strptime_with_fallback(
479
478
return Index (result , dtype = result .dtype , name = name )
480
479
481
480
482
- def _to_datetime_with_unit (arg , unit , name , utc : bool , errors : str ) -> Index :
481
+
482
+
483
+
484
+ def _to_datetime_with_unit (arg , unit , name , utc : bool , errors : str ) -> DatetimeIndex :
483
485
"""
484
- to_datetime specalized to the case where a 'unit' is passed.
486
+ to_datetime specialized to the case where a 'unit' is passed.
487
+ Fixes a bug where scalar out-of-bounds values were not raising
488
+ an error consistently.
485
489
"""
490
+ # Ensure we handle both array-likes and scalars the same way.
491
+ # extract_array can return a scalar if 'arg' is scalar-like;
492
+ # so we force everything into at least 1D shape.
486
493
arg = extract_array (arg , extract_numpy = True )
494
+ arg = np .atleast_1d (arg )
487
495
488
496
# GH#30050 pass an ndarray to tslib.array_to_datetime
489
497
# because it expects an ndarray argument
490
498
if isinstance (arg , IntegerArray ):
499
+ # For IntegerArray, we can directly convert
491
500
arr = arg .astype (f"datetime64[{ unit } ]" )
492
501
tz_parsed = None
502
+
493
503
else :
504
+ # Now we have a guaranteed ndarray
494
505
arg = np .asarray (arg )
495
506
496
507
if arg .dtype .kind in "iu" :
497
508
# Note we can't do "f" here because that could induce unwanted
498
- # rounding GH#14156, GH#20445
509
+ # rounding GH#14156, GH#20445
499
510
arr = arg .astype (f"datetime64[{ unit } ]" , copy = False )
500
511
try :
501
512
arr = astype_overflowsafe (arr , np .dtype ("M8[ns]" ), copy = False )
502
513
except OutOfBoundsDatetime :
503
514
if errors == "raise" :
504
515
raise
516
+ # errors != "raise" => coerce to object and retry
505
517
arg = arg .astype (object )
506
518
return _to_datetime_with_unit (arg , unit , name , utc , errors )
507
519
tz_parsed = None
508
520
509
521
elif arg .dtype .kind == "f" :
522
+ # Floating dtypes
510
523
with np .errstate (over = "raise" ):
511
524
try :
512
525
arr = cast_from_unit_vectorized (arg , unit = unit )
513
526
except OutOfBoundsDatetime as err :
514
527
if errors != "raise" :
528
+ # coerce to object and retry
515
529
return _to_datetime_with_unit (
516
- arg .astype (object ), unit , name , utc , errors
530
+ arg .astype (object ),
531
+ unit ,
532
+ name ,
533
+ utc ,
534
+ errors ,
517
535
)
518
536
raise OutOfBoundsDatetime (
519
537
f"cannot convert input with unit '{ unit } '"
520
538
) from err
521
539
522
540
arr = arr .view ("M8[ns]" )
523
541
tz_parsed = None
542
+
524
543
else :
544
+ # Fallback: treat as object dtype
525
545
arg = arg .astype (object , copy = False )
526
546
arr , tz_parsed = tslib .array_to_datetime (
527
547
arg ,
@@ -531,23 +551,22 @@ def _to_datetime_with_unit(arg, unit, name, utc: bool, errors: str) -> Index:
531
551
creso = NpyDatetimeUnit .NPY_FR_ns .value ,
532
552
)
533
553
554
+ # Construct a DatetimeIndex from the array
534
555
result = DatetimeIndex (arr , name = name )
535
- if not isinstance (result , DatetimeIndex ):
536
- return result
537
556
538
- # GH#23758: We may still need to localize the result with tz
539
- # GH#25546: Apply tz_parsed first (from arg), then tz (from caller)
540
- # result will be naive but in UTC
557
+ # May need to localize result to parsed tz or convert to UTC if requested
541
558
result = result .tz_localize ("UTC" ).tz_convert (tz_parsed )
542
559
543
560
if utc :
544
561
if result .tz is None :
545
562
result = result .tz_localize ("utc" )
546
563
else :
547
564
result = result .tz_convert ("utc" )
565
+
548
566
return result
549
567
550
568
569
+
551
570
def _adjust_to_origin (arg , origin , unit ):
552
571
"""
553
572
Helper function for to_datetime.
0 commit comments