@@ -541,7 +541,14 @@ def _as_range_index(self) -> RangeIndex:
541541 return RangeIndex (rng )
542542
543543 def _can_range_setop (self , other ) -> bool :
544- return isinstance (self .freq , Tick ) and isinstance (other .freq , Tick )
544+ # Only allow range-based setops when both objects are tick-based AND
545+ # not timezone-aware. For tz-aware DatetimeIndex, constant i8 stepping
546+ # does not hold across DST transitions in local time, so avoid range path.
547+ if not (isinstance (self .freq , Tick ) and isinstance (other .freq , Tick )):
548+ return False
549+ self_tz = getattr (self .dtype , "tz" , None )
550+ other_tz = getattr (other .dtype , "tz" , None )
551+ return self_tz is None and other_tz is None
545552
546553 def _wrap_range_setop (self , other , res_i8 ) -> Self :
547554 new_freq = None
@@ -726,6 +733,15 @@ def _union(self, other, sort):
726733 # that result.freq == self.freq
727734 return result
728735 else :
736+ # For tz-aware DatetimeIndex, perform union in UTC to avoid
737+ # local-time irregularities across DST transitions, then convert back.
738+ tz = getattr (self .dtype , "tz" , None )
739+ if tz is not None :
740+ left_utc = self .tz_convert ("UTC" )
741+ right_utc = other .tz_convert ("UTC" )
742+ res_utc = super (type (left_utc ), left_utc )._union (right_utc , sort )
743+ res = res_utc .tz_convert (tz )
744+ return res ._with_freq ("infer" )
729745 return super ()._union (other , sort )._with_freq ("infer" )
730746
731747 # --------------------------------------------------------------------
0 commit comments