Skip to content

Commit 3555dbc

Browse files
authored
BUG: Fix DatetimeIndex timezone preservation when joining indexes with same timezone but different units (#61234)
* BUG: Fix DatetimeIndex timezone preservation when joining indexes with same timezone but different units * TST: Split timezone preservation test into separate tests Address review comments on PR #60080 by splitting the comprehensive test into separate focused tests for each set operation (union, intersection, symmetric_difference). * Remove unnecessary assert result.tz == idx1.tz
1 parent 8981a0f commit 3555dbc

File tree

3 files changed

+72
-5
lines changed

3 files changed

+72
-5
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ Timedelta
707707

708708
Timezones
709709
^^^^^^^^^
710-
-
710+
- 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`)
711711
-
712712

713713
Numeric

pandas/core/indexes/base.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,10 +2967,14 @@ def _dti_setop_align_tzs(self, other: Index, setop: str_t) -> tuple[Index, Index
29672967
and self.tz is not None
29682968
and other.tz is not None
29692969
):
2970-
# GH#39328, GH#45357
2971-
left = self.tz_convert("UTC")
2972-
right = other.tz_convert("UTC")
2973-
return left, right
2970+
# GH#39328, GH#45357, GH#60080
2971+
# If both timezones are the same, no need to convert to UTC
2972+
if self.tz == other.tz:
2973+
return self, other
2974+
else:
2975+
left = self.tz_convert("UTC")
2976+
right = other.tz_convert("UTC")
2977+
return left, right
29742978
return self, other
29752979

29762980
@final

pandas/tests/indexes/datetimes/test_setops.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,69 @@ 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_union_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+
220+
def test_union_different_dates_same_timezone_different_units(self):
221+
# GH 60080 - fix timezone being changed to UTC when units differ
222+
# but timezone is the same
223+
tz = "UTC+05:00"
224+
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
225+
idx3 = date_range("2000-01-03", periods=3, tz=tz).as_unit("us")
226+
227+
# Test with different dates to ensure it's not just returning one of the inputs
228+
result = idx1.union(idx3)
229+
expected = DatetimeIndex(
230+
["2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05"],
231+
tz=tz,
232+
).as_unit("us")
233+
tm.assert_index_equal(result, expected)
234+
235+
def test_intersection_same_timezone_different_units(self):
236+
# GH 60080 - fix timezone being changed to UTC when units differ
237+
# but timezone is the same
238+
tz = "UTC+05:00"
239+
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
240+
idx2 = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
241+
242+
# Check pre-conditions
243+
assert idx1.tz == idx2.tz
244+
assert idx1.dtype != idx2.dtype # Different units
245+
246+
# Test intersection
247+
result = idx1.intersection(idx2)
248+
expected = date_range("2000-01-01", periods=3, tz=tz).as_unit("ns")
249+
tm.assert_index_equal(result, expected)
250+
251+
def test_symmetric_difference_same_timezone_different_units(self):
252+
# GH 60080 - fix timezone being changed to UTC when units differ
253+
# but timezone is the same
254+
tz = "UTC+05:00"
255+
idx1 = date_range("2000-01-01", periods=3, tz=tz).as_unit("us")
256+
idx4 = date_range("2000-01-02", periods=3, tz=tz).as_unit("ns")
257+
258+
# Check pre-conditions
259+
assert idx1.tz == idx4.tz
260+
assert idx1.dtype != idx4.dtype # Different units
261+
262+
# Test symmetric_difference
263+
result = idx1.symmetric_difference(idx4)
264+
expected = DatetimeIndex(["2000-01-01", "2000-01-04"], tz=tz).as_unit("ns")
265+
tm.assert_index_equal(result, expected)
266+
204267
# TODO: moved from test_datetimelike; de-duplicate with version below
205268
def test_intersection2(self):
206269
first = date_range("2020-01-01", periods=10)

0 commit comments

Comments
 (0)