@@ -252,8 +252,11 @@ cdef class DatetimeParseState:
252252 # found_naive_str refers to a string that was parsed to a timezone-naive
253253 # datetime.
254254 self .found_naive_str = False
255+ self .found_aware_str = False
255256 self .found_other = False
256257
258+ self .out_tzoffset_vals = set ()
259+
257260 self .creso = creso
258261 self .creso_ever_changed = False
259262
@@ -292,6 +295,58 @@ cdef class DatetimeParseState:
292295 " tz-naive values" )
293296 return tz
294297
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+
295350
296351def array_strptime (
297352 ndarray[object] values ,
@@ -319,11 +374,8 @@ def array_strptime(
319374 npy_datetimestruct dts
320375 int64_t[::1 ] iresult
321376 object val
322- bint seen_datetime_offset = False
323377 bint is_raise = errors== " raise"
324378 bint is_coerce = errors== " coerce"
325- bint is_same_offsets
326- set out_tzoffset_vals = set ()
327379 tzinfo tz, tz_out = None
328380 bint iso_format = format_is_iso(fmt)
329381 NPY_DATETIMEUNIT out_bestunit, item_reso
@@ -418,15 +470,15 @@ def array_strptime(
418470 ) from err
419471 if out_local == 1 :
420472 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
423475 tz = timezone(timedelta(minutes = out_tzoffset))
424476 value = tz_localize_to_utc_single(
425477 value, tz, ambiguous = " raise" , nonexistent = None , creso = creso
426478 )
427479 else :
428480 tz = None
429- out_tzoffset_vals.add(" naive" )
481+ state. out_tzoffset_vals.add(" naive" )
430482 state.found_naive_str = True
431483 iresult[i] = value
432484 continue
@@ -475,12 +527,12 @@ def array_strptime(
475527 elif creso == NPY_DATETIMEUNIT.NPY_FR_ms:
476528 nsecs = nsecs // 10 ** 3
477529
478- out_tzoffset_vals.add(nsecs)
479- seen_datetime_offset = True
530+ state. out_tzoffset_vals.add(nsecs)
531+ state.found_aware_str = True
480532 else :
481533 state.found_naive_str = True
482534 tz = None
483- out_tzoffset_vals.add(" naive" )
535+ state. out_tzoffset_vals.add(" naive" )
484536
485537 except ValueError as ex:
486538 ex.args = (
@@ -499,35 +551,7 @@ def array_strptime(
499551 raise
500552 return values, None
501553
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)
531555
532556 if infer_reso:
533557 if state.creso_ever_changed:
0 commit comments