Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ Timedelta

Timezones
^^^^^^^^^
-
- Bug in :meth:`DatetimeIndex.union`, :meth:`DatetimeIndex.intersection`, and :meth:`DatetimeIndex.symmetric_difference` changing timezone to UTC when merging two DatetimeIndex objects with the same timezone but different units (:issue:`60080`)
-

Numeric
Expand Down
12 changes: 8 additions & 4 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2961,10 +2961,14 @@ def _dti_setop_align_tzs(self, other: Index, setop: str_t) -> tuple[Index, Index
and self.tz is not None
and other.tz is not None
):
# GH#39328, GH#45357
left = self.tz_convert("UTC")
right = other.tz_convert("UTC")
return left, right
# GH#39328, GH#45357, GH#60080
# If both timezones are the same, no need to convert to UTC
if self.tz == other.tz:
return self, other
else:
left = self.tz_convert("UTC")
right = other.tz_convert("UTC")
return left, right
return self, other

@final
Expand Down
67 changes: 67 additions & 0 deletions pandas/tests/indexes/datetimes/test_setops.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,73 @@ def test_union_same_timezone_different_units(self):
expected = date_range("2000-01-01", periods=3, tz="UTC").as_unit("us")
tm.assert_index_equal(result, expected)

def test_union_same_nonzero_timezone_different_units(self):
# GH 60080 - fix timezone being changed to UTC when units differ
# but timezone is the same
tz = "UTC+05:00"
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
idx2 = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")

# Check pre-conditions
assert idx1.tz == idx2.tz
assert idx1.dtype != idx2.dtype # Different units

# Test union preserves timezone when units differ
result = idx1.union(idx2)
expected = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
tm.assert_index_equal(result, expected)
assert result.tz == idx1.tz # Original timezone is preserved

def test_union_different_dates_same_timezone_different_units(self):
# GH 60080 - fix timezone being changed to UTC when units differ
# but timezone is the same
tz = "UTC+05:00"
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
idx3 = date_range("2000-01-03", periods=3, tz=tz).as_unit("us")

# Test with different dates to ensure it's not just returning one of the inputs
result = idx1.union(idx3)
expected = DatetimeIndex(
["2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05"],
tz=tz,
).as_unit("us")
tm.assert_index_equal(result, expected)
assert result.tz == idx1.tz # Original timezone is preserved

def test_intersection_same_timezone_different_units(self):
# GH 60080 - fix timezone being changed to UTC when units differ
# but timezone is the same
tz = "UTC+05:00"
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
idx2 = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")

# Check pre-conditions
assert idx1.tz == idx2.tz
assert idx1.dtype != idx2.dtype # Different units

# Test intersection
result = idx1.intersection(idx2)
expected = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
tm.assert_index_equal(result, expected)
assert result.tz == idx1.tz # Original timezone is preserved

def test_symmetric_difference_same_timezone_different_units(self):
# GH 60080 - fix timezone being changed to UTC when units differ
# but timezone is the same
tz = "UTC+05:00"
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
idx4 = date_range("2000-01-02", periods=3, tz=tz).as_unit("ns")

# Check pre-conditions
assert idx1.tz == idx4.tz
assert idx1.dtype != idx4.dtype # Different units

# Test symmetric_difference
result = idx1.symmetric_difference(idx4)
expected = DatetimeIndex(["2000-01-01", "2000-01-04"], tz=tz).as_unit("ns")
tm.assert_index_equal(result, expected)
assert result.tz == idx1.tz # Original timezone is preserved

# TODO: moved from test_datetimelike; de-duplicate with version below
def test_intersection2(self):
first = date_range("2020-01-01", periods=10)
Expand Down
Loading