Skip to content

Commit 9901aa6

Browse files
committed
BUG: Fix DatetimeIndex timezone preservation when joining indexes with same timezone but different units
1 parent 5736b96 commit 9901aa6

File tree

3 files changed

+49
-5
lines changed

3 files changed

+49
-5
lines changed

doc/source/whatsnew/v3.0.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ Timedelta
668668

669669
Timezones
670670
^^^^^^^^^
671-
-
671+
- 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`)
672672
-
673673

674674
Numeric

pandas/core/indexes/base.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -2961,10 +2961,14 @@ def _dti_setop_align_tzs(self, other: Index, setop: str_t) -> tuple[Index, Index
29612961
and self.tz is not None
29622962
and other.tz is not None
29632963
):
2964-
# GH#39328, GH#45357
2965-
left = self.tz_convert("UTC")
2966-
right = other.tz_convert("UTC")
2967-
return left, right
2964+
# GH#39328, GH#45357, GH#60080
2965+
# If both timezones are the same, no need to convert to UTC
2966+
if self.tz == other.tz:
2967+
return self, other
2968+
else:
2969+
left = self.tz_convert("UTC")
2970+
right = other.tz_convert("UTC")
2971+
return left, right
29682972
return self, other
29692973

29702974
@final

pandas/tests/indexes/datetimes/test_setops.py

+40
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,46 @@ def test_union_same_timezone_different_units(self):
201201
expected = date_range("2000-01-01", periods=3, tz="UTC").as_unit("us")
202202
tm.assert_index_equal(result, expected)
203203

204+
def test_setops_same_nonzero_timezone_different_units(self):
205+
# GH 60080 - fix timezone being changed to UTC when units differ
206+
# but timezone is the same
207+
tz = "UTC+05:00"
208+
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
209+
idx2 = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
210+
211+
# Check pre-conditions
212+
assert idx1.tz == idx2.tz
213+
assert idx1.dtype != idx2.dtype # Different units
214+
215+
# Test union preserves timezone when units differ
216+
result = idx1.union(idx2)
217+
expected = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
218+
tm.assert_index_equal(result, expected)
219+
assert result.tz == idx1.tz # Original timezone is preserved
220+
221+
# Test with different dates to ensure it's not just returning one of the inputs
222+
idx3 = date_range("2000-01-03", periods=3, tz=tz).as_unit("us")
223+
result = idx1.union(idx3)
224+
expected = DatetimeIndex(
225+
["2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05"],
226+
tz=tz,
227+
).as_unit("us")
228+
tm.assert_index_equal(result, expected)
229+
assert result.tz == idx1.tz # Original timezone is preserved
230+
231+
# Test intersection
232+
result = idx1.intersection(idx2)
233+
expected = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
234+
tm.assert_index_equal(result, expected)
235+
assert result.tz == idx1.tz # Original timezone is preserved
236+
237+
# Test symmetric_difference
238+
idx4 = date_range("2000-01-02", periods=3, tz=tz).as_unit("ns")
239+
result = idx1.symmetric_difference(idx4)
240+
expected = DatetimeIndex(["2000-01-01", "2000-01-04"], tz=tz).as_unit("ns")
241+
tm.assert_index_equal(result, expected)
242+
assert result.tz == idx1.tz # Original timezone is preserved
243+
204244
# TODO: moved from test_datetimelike; de-duplicate with version below
205245
def test_intersection2(self):
206246
first = date_range("2020-01-01", periods=10)

0 commit comments

Comments
 (0)