@@ -252,8 +252,11 @@ cdef class DatetimeParseState:
252
252
# found_naive_str refers to a string that was parsed to a timezone-naive
253
253
# datetime.
254
254
self .found_naive_str = False
255
+ self .found_aware_str = False
255
256
self .found_other = False
256
257
258
+ self .out_tzoffset_vals = set ()
259
+
257
260
self .creso = creso
258
261
self .creso_ever_changed = False
259
262
@@ -292,6 +295,58 @@ cdef class DatetimeParseState:
292
295
" tz-naive values" )
293
296
return tz
294
297
298
+ cdef tzinfo check_for_mixed_inputs(
299
+ self ,
300
+ tzinfo tz_out,
301
+ bint utc,
302
+ ):
303
+ cdef:
304
+ bint is_same_offsets
305
+ float tz_offset
306
+
307
+ if self .found_aware_str and not utc:
308
+ # GH#17697, GH#57275
309
+ # 1) If all the offsets are equal, return one offset for
310
+ # the parsed dates to (maybe) pass to DatetimeIndex
311
+ # 2) If the offsets are different, then do not force the parsing
312
+ # and raise a ValueError: "cannot parse datetimes with
313
+ # mixed time zones unless `utc=True`" instead
314
+ is_same_offsets = len (self .out_tzoffset_vals) == 1
315
+ if not is_same_offsets or (self .found_naive or self .found_other):
316
+ # e.g. test_to_datetime_mixed_awareness_mixed_types (array_to_datetime)
317
+ raise ValueError (
318
+ " Mixed timezones detected. Pass utc=True in to_datetime "
319
+ " or tz='UTC' in DatetimeIndex to convert to a common timezone."
320
+ )
321
+ elif tz_out is not None :
322
+ # GH#55693
323
+ tz_offset = self .out_tzoffset_vals.pop()
324
+ tz_out2 = timezone(timedelta(seconds = tz_offset))
325
+ if not tz_compare(tz_out, tz_out2):
326
+ # e.g. (array_strptime)
327
+ # test_to_datetime_mixed_offsets_with_utc_false_removed
328
+ # e.g. test_to_datetime_mixed_tzs_mixed_types (array_to_datetime)
329
+ raise ValueError (
330
+ " Mixed timezones detected. Pass utc=True in to_datetime "
331
+ " or tz='UTC' in DatetimeIndex to convert to a common timezone."
332
+ )
333
+ # e.g. (array_strptime)
334
+ # test_guess_datetime_format_with_parseable_formats
335
+ # e.g. test_to_datetime_mixed_types_matching_tzs (array_to_datetime)
336
+ else :
337
+ # e.g. test_to_datetime_iso8601_with_timezone_valid (array_strptime)
338
+ tz_offset = self .out_tzoffset_vals.pop()
339
+ tz_out = timezone(timedelta(seconds = tz_offset))
340
+ elif not utc:
341
+ if tz_out and (self .found_other or self .found_naive_str):
342
+ # found_other indicates a tz-naive int, float, dt64, or date
343
+ # e.g. test_to_datetime_mixed_awareness_mixed_types (array_to_datetime)
344
+ raise ValueError (
345
+ " Mixed timezones detected. Pass utc=True in to_datetime "
346
+ " or tz='UTC' in DatetimeIndex to convert to a common timezone."
347
+ )
348
+ return tz_out
349
+
295
350
296
351
def array_strptime (
297
352
ndarray[object] values ,
@@ -319,11 +374,8 @@ def array_strptime(
319
374
npy_datetimestruct dts
320
375
int64_t[::1 ] iresult
321
376
object val
322
- bint seen_datetime_offset = False
323
377
bint is_raise = errors== " raise"
324
378
bint is_coerce = errors== " coerce"
325
- bint is_same_offsets
326
- set out_tzoffset_vals = set ()
327
379
tzinfo tz, tz_out = None
328
380
bint iso_format = format_is_iso(fmt)
329
381
NPY_DATETIMEUNIT out_bestunit, item_reso
@@ -418,15 +470,15 @@ def array_strptime(
418
470
) from err
419
471
if out_local == 1 :
420
472
nsecs = out_tzoffset * 60
421
- out_tzoffset_vals.add(nsecs)
422
- seen_datetime_offset = True
473
+ state. out_tzoffset_vals.add(nsecs)
474
+ state.found_aware_str = True
423
475
tz = timezone(timedelta(minutes = out_tzoffset))
424
476
value = tz_localize_to_utc_single(
425
477
value, tz, ambiguous = " raise" , nonexistent = None , creso = creso
426
478
)
427
479
else :
428
480
tz = None
429
- out_tzoffset_vals.add(" naive" )
481
+ state. out_tzoffset_vals.add(" naive" )
430
482
state.found_naive_str = True
431
483
iresult[i] = value
432
484
continue
@@ -475,12 +527,12 @@ def array_strptime(
475
527
elif creso == NPY_DATETIMEUNIT.NPY_FR_ms:
476
528
nsecs = nsecs // 10 ** 3
477
529
478
- out_tzoffset_vals.add(nsecs)
479
- seen_datetime_offset = True
530
+ state. out_tzoffset_vals.add(nsecs)
531
+ state.found_aware_str = True
480
532
else :
481
533
state.found_naive_str = True
482
534
tz = None
483
- out_tzoffset_vals.add(" naive" )
535
+ state. out_tzoffset_vals.add(" naive" )
484
536
485
537
except ValueError as ex:
486
538
ex.args = (
@@ -499,35 +551,7 @@ def array_strptime(
499
551
raise
500
552
return values, None
501
553
502
- if seen_datetime_offset and not utc:
503
- is_same_offsets = len (out_tzoffset_vals) == 1
504
- if not is_same_offsets or (state.found_naive or state.found_other):
505
- raise ValueError (
506
- " Mixed timezones detected. Pass utc=True in to_datetime "
507
- " or tz='UTC' in DatetimeIndex to convert to a common timezone."
508
- )
509
- elif tz_out is not None :
510
- # GH#55693
511
- tz_offset = out_tzoffset_vals.pop()
512
- tz_out2 = timezone(timedelta(seconds = tz_offset))
513
- if not tz_compare(tz_out, tz_out2):
514
- # e.g. test_to_datetime_mixed_offsets_with_utc_false_removed
515
- raise ValueError (
516
- " Mixed timezones detected. Pass utc=True in to_datetime "
517
- " or tz='UTC' in DatetimeIndex to convert to a common timezone."
518
- )
519
- # e.g. test_guess_datetime_format_with_parseable_formats
520
- else :
521
- # e.g. test_to_datetime_iso8601_with_timezone_valid
522
- tz_offset = out_tzoffset_vals.pop()
523
- tz_out = timezone(timedelta(seconds = tz_offset))
524
- elif not utc:
525
- if tz_out and (state.found_other or state.found_naive_str):
526
- # found_other indicates a tz-naive int, float, dt64, or date
527
- raise ValueError (
528
- " Mixed timezones detected. Pass utc=True in to_datetime "
529
- " or tz='UTC' in DatetimeIndex to convert to a common timezone."
530
- )
554
+ tz_out = state.check_for_mixed_inputs(tz_out, utc)
531
555
532
556
if infer_reso:
533
557
if state.creso_ever_changed:
0 commit comments