Repository: pandas-dev/pandas (Python)

Issue:
BUG: Timezone upon DatetimeIndex union is changed to UTC
### Pandas version checks

- [X] I have checked that this issue has not already been reported.

- [X] I have confirmed this bug exists on the [latest version](https://pandas.pydata.org/docs/whatsnew/index.html) of pandas.

- [ ] I have confirmed this bug exists on the [main branch](https://pandas.pydata.org/docs/dev/getting_started/install.html#installing-the-development-version-of-pandas) of pandas.


### Reproducible Example

```python
import pandas as pd

i1 = pd.DatetimeIndex(['2020-01-01 10:00:00+05:00']).astype('datetime64[us, UTC+05:00]')
i2 = pd.DatetimeIndex(['2020-01-01 10:00:00+05:00']).astype('datetime64[ns, UTC+05:00]')
i1.union(i2) # returns DatetimeIndex(['2020-01-01 05:00:00+00:00'], dtype='datetime64[ns, UTC]', freq=None)
```


### Issue Description

@lukemanley @mroeschke looks like test case from https://github.com/pandas-dev/pandas/issues/55238 could be extended to different timezones, currently if taking the union of two `DatetimeIndex` with the same timezone, but different `unit`, then it results in an UTC timezone.

### Expected Behavior

The result should be the higher resolution unit and the original timezone

### Installed Versions

<details>
python : 3.10.14
pandas : 2.2.2
</details>



Discussion:
take
take



Patch:
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index a8a8f0cf84501..148e522dda6a6 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -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
diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py
index ff3879018674e..8f2d72b6fbc64 100644
--- a/pandas/core/indexes/base.py
+++ b/pandas/core/indexes/base.py
@@ -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


Test patch:
diff --git a/pandas/tests/indexes/datetimes/test_setops.py b/pandas/tests/indexes/datetimes/test_setops.py
index 7ef6efad0ff6f..7a68cb867c94e 100644
--- a/pandas/tests/indexes/datetimes/test_setops.py
+++ b/pandas/tests/indexes/datetimes/test_setops.py
@@ -201,6 +201,69 @@ 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)
+
+    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)
+
+    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)
+
+    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)
+
     # TODO: moved from test_datetimelike; de-duplicate with version below
     def test_intersection2(self):
         first = date_range("2020-01-01", periods=10)