Skip to content

Commit 8927261

Browse files
committed
BUG: Fix timezone handling in DatetimeIndex.union (pandas-dev#60080)
1 parent a14a8be commit 8927261

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

pandas/core/indexes/base.py

+23
Original file line numberDiff line numberDiff line change
@@ -2958,6 +2958,29 @@ def _dti_setop_align_tzs(self, other: Index, setop: str_t) -> tuple[Index, Index
29582958
and self.tz is not None
29592959
and other.tz is not None
29602960
):
2961+
if self.tz == other.tz:
2962+
# GH #60080: Handle union of DatetimeIndex with
2963+
# the same timezone but different resolutions
2964+
resolution_order = {
2965+
"Y": 1, # Year
2966+
"M": 2, # Month
2967+
"W": 3, # Week
2968+
"D": 4, # Day
2969+
"h": 5, # Hour
2970+
"m": 6, # Minute
2971+
"s": 7, # Second
2972+
"ms": 8, # Millisecond
2973+
"us": 9, # Microsecond
2974+
"ns": 10, # Nanosecond
2975+
}
2976+
# Default to the lowest resolution if unit is unknown
2977+
self_res = resolution_order.get(self.dtype.unit, 0)
2978+
other_res = resolution_order.get(other.dtype.unit, 0)
2979+
# Choose the dtype with higher resolution
2980+
dtype = self.dtype if self_res >= other_res else other.dtype
2981+
left = self.astype(dtype, copy=False)
2982+
right = other.astype(dtype, copy=False)
2983+
return left, right
29612984
# GH#39328, GH#45357
29622985
left = self.tz_convert("UTC")
29632986
right = other.tz_convert("UTC")

pandas/tests/indexes/datetimes/test_setops.py

+76
Original file line numberDiff line numberDiff line change
@@ -694,3 +694,79 @@ def test_intersection_non_nano_rangelike():
694694
freq="D",
695695
)
696696
tm.assert_index_equal(result, expected)
697+
698+
699+
def test_union_preserves_timezone_and_resolution():
700+
"""
701+
GH 60080: Ensure union of DatetimeIndex with the same timezone
702+
and differing resolutions results in the higher resolution unit
703+
and preserves the timezone.
704+
"""
705+
idx1 = DatetimeIndex(["2020-01-01 10:00:00+05:00"]).astype(
706+
"datetime64[us, UTC+05:00]"
707+
)
708+
idx2 = DatetimeIndex(["2020-01-01 10:00:00+05:00"]).astype(
709+
"datetime64[ns, UTC+05:00]"
710+
)
711+
result = idx1.union(idx2)
712+
expected = DatetimeIndex(["2020-01-01 10:00:00+05:00"]).astype(
713+
"datetime64[ns, UTC+05:00]"
714+
)
715+
tm.assert_index_equal(result, expected)
716+
717+
718+
def test_union_multiple_entries_same_timezone():
719+
"""
720+
GH 60080: Test union with multiple DatetimeIndex entries having the same timezone
721+
and different units, ensuring correct alignment and resolution preservation.
722+
"""
723+
idx1 = DatetimeIndex(
724+
["2023-01-01 10:00:00+05:00", "2023-01-02 10:00:00+05:00"]
725+
).astype("datetime64[us, UTC+05:00]")
726+
idx2 = DatetimeIndex(
727+
["2023-01-01 10:00:00+05:00", "2023-01-03 10:00:00+05:00"]
728+
).astype("datetime64[ns, UTC+05:00]")
729+
result = idx1.union(idx2)
730+
expected = DatetimeIndex(
731+
[
732+
"2023-01-01 10:00:00+05:00",
733+
"2023-01-02 10:00:00+05:00",
734+
"2023-01-03 10:00:00+05:00",
735+
]
736+
).astype("datetime64[ns, UTC+05:00]")
737+
tm.assert_index_equal(result, expected)
738+
739+
740+
def test_union_same_timezone_same_resolution():
741+
"""
742+
GH 60080: Ensure union of DatetimeIndex with the same timezone and
743+
resolution is straightforward and retains the resolution.
744+
"""
745+
idx1 = DatetimeIndex(["2022-01-01 15:00:00+05:00"]).astype(
746+
"datetime64[ms, UTC+05:00]"
747+
)
748+
idx2 = DatetimeIndex(["2022-01-01 16:00:00+05:00"]).astype(
749+
"datetime64[ms, UTC+05:00]"
750+
)
751+
result = idx1.union(idx2)
752+
expected = DatetimeIndex(
753+
["2022-01-01 15:00:00+05:00", "2022-01-01 16:00:00+05:00"]
754+
).astype("datetime64[ms, UTC+05:00]")
755+
tm.assert_index_equal(result, expected)
756+
757+
def test_union_single_entry():
758+
"""
759+
GH 60080: Ensure union of single-entry DatetimeIndex works as expected
760+
with different units and same timezone.
761+
"""
762+
idx1 = DatetimeIndex(["2023-01-01 10:00:00+05:00"]).astype(
763+
"datetime64[ms, UTC+05:00]"
764+
)
765+
idx2 = DatetimeIndex(["2023-01-01 10:00:00+05:00"]).astype(
766+
"datetime64[us, UTC+05:00]"
767+
)
768+
result = idx1.union(idx2)
769+
expected = DatetimeIndex(["2023-01-01 10:00:00+05:00"]).astype(
770+
"datetime64[us, UTC+05:00]"
771+
)
772+
tm.assert_index_equal(result, expected)

0 commit comments

Comments
 (0)