Skip to content

Commit 33d4d7b

Browse files
BUG: Fix index.union failure at DST boundary (GH#62915)
When concatenating DatetimeIndex objects across DST transitions, the frequency preservation logic was using naive addition that didn't account for timezone offsets changing at DST boundaries. This caused the assertion `pair[0][-1] + obj.freq == pair[1][0]` to fail even when the indexes were legitimately consecutive. The fix compares the underlying int64 values (UTC nanoseconds) instead of relying on timezone-aware arithmetic. This correctly identifies consecutive timestamps regardless of DST transitions. Closes #62915
1 parent 5641979 commit 33d4d7b

File tree

2 files changed

+21
-1
lines changed

2 files changed

+21
-1
lines changed

pandas/core/arrays/datetimelike.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2383,7 +2383,15 @@ def _concat_same_type(
23832383

23842384
if obj.freq is not None and all(x.freq == obj.freq for x in to_concat):
23852385
pairs = zip(to_concat[:-1], to_concat[1:], strict=True)
2386-
if all(pair[0][-1] + obj.freq == pair[1][0] for pair in pairs):
2386+
# GH#62915: For timezone-aware datetimes, DST transitions can cause
2387+
# naive addition (pair[0][-1] + freq) to not equal pair[1][0] even
2388+
# when they're legitimately consecutive. Compare the actual time
2389+
# difference using the underlying int64 values (UTC nanoseconds).
2390+
freq_nanos = obj.freq.nanos
2391+
if all(
2392+
pair[1][0]._value - pair[0][-1]._value == freq_nanos
2393+
for pair in pairs
2394+
):
23872395
new_freq = obj.freq
23882396
new_obj._freq = new_freq
23892397
return new_obj

pandas/tests/indexes/datetimes/test_setops.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,18 @@ def test_intersection_dst_transition(self, tz):
729729
expected = date_range("2021-10-28", periods=6, freq="D", tz="Europe/London")
730730
tm.assert_index_equal(result, expected)
731731

732+
def test_union_dst_boundary(self):
733+
# GH#62915: index.union fails at DST boundary
734+
# When one index ends at DST transition and the other crosses it,
735+
# the union should succeed and preserve frequency
736+
index1 = date_range("2025-10-25", "2025-10-26", freq="D", tz="Europe/Helsinki")
737+
index2 = date_range("2025-10-25", "2025-10-28", freq="D", tz="Europe/Helsinki")
738+
739+
result = index1.union(index2)
740+
expected = date_range("2025-10-25", "2025-10-28", freq="D", tz="Europe/Helsinki")
741+
tm.assert_index_equal(result, expected)
742+
assert result.freq == expected.freq
743+
732744

733745
def test_union_non_nano_rangelike():
734746
# GH 59036

0 commit comments

Comments
 (0)