Skip to content

Commit b970007

Browse files
committed
BUG: tz-aware DatetimeIndex.union - perform UTC-naive base union when tz matches to avoid recursion; preserve semantics for differing tz; mypy-safe ops; wrap long comment (GH#62915)
1 parent c90c78d commit b970007

File tree

1 file changed

+24
-10
lines changed

1 file changed

+24
-10
lines changed

pandas/core/indexes/datetimelike.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -736,22 +736,36 @@ def _union(self, other, sort):
736736
# For tz-aware DatetimeIndex, perform union in UTC to avoid
737737
# local-time irregularities across DST transitions, then convert back.
738738
tz = getattr(self.dtype, "tz", None)
739-
if tz is not None:
739+
other_tz = getattr(other.dtype, "tz", None)
740+
if tz is not None and tz == other_tz:
740741
# Narrow to DatetimeArray to access tz_convert without mypy errors
741742
if isinstance(self._data, DatetimeArray) and isinstance(
742743
other._data, DatetimeArray
743744
):
744-
left_utc_arr = self._data.tz_convert("UTC")
745-
right_utc_arr = other._data.tz_convert("UTC")
746-
left_utc = type(self)._simple_new(left_utc_arr, name=self.name)
747-
right_utc = type(other)._simple_new(right_utc_arr, name=other.name)
748-
res_utc = super(type(left_utc), left_utc)._union(right_utc, sort)
749-
# res_utc is DatetimeIndex; convert its underlying array back to tz
750-
res_arr = cast(DatetimeArray, res_utc._data).tz_convert(tz)
751-
res = type(self)._simple_new(res_arr, name=res_utc.name)
745+
# Convert both to UTC, then drop tz to avoid re-entering
746+
# tz-aware path
747+
left_utc_naive = self._data.tz_convert("UTC").tz_localize(None)
748+
right_utc_naive = other._data.tz_convert("UTC").tz_localize(None)
749+
left_naive = type(self)._simple_new(left_utc_naive, name=self.name)
750+
right_naive = type(other)._simple_new(
751+
right_utc_naive, name=other.name
752+
)
753+
# Perform base union on tz-naive indices to avoid DST complications
754+
res_naive = super(type(left_naive), left_naive)._union(
755+
right_naive, sort
756+
)
757+
# Localize back to UTC and then convert to original tz
758+
if isinstance(res_naive, DatetimeArray):
759+
base_arr = res_naive
760+
name = self.name
761+
else:
762+
base_arr = cast(DatetimeArray, res_naive._data)
763+
name = res_naive.name
764+
res_arr = base_arr.tz_localize("UTC").tz_convert(tz)
765+
res = type(self)._simple_new(res_arr, name=name)
752766
return res._with_freq("infer")
753767
# Defensive fallback if types are unexpected
754-
return super()._union(other, sort)._with_freq("infer")
768+
return super()._union(other, sort)
755769
return super()._union(other, sort)._with_freq("infer")
756770

757771
# --------------------------------------------------------------------

0 commit comments

Comments
 (0)