diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index fd061666c1f00..dc13ba9700a0d 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -245,8 +245,27 @@ def equals(self, other: Any) -> bool: if not isinstance(other, Index): return False - elif other.dtype.kind in "iufc": + + if hasattr(other, "dtype") and other.dtype.kind in "iufc": + return False + + if len(self) != len(other): return False + + self_unit = getattr(self, "unit", None) + other_unit = getattr(other, "unit", None) + + if self_unit is not None and other_unit is not None and self_unit != other_unit: + if getattr(self.dtype, "tz", None) == getattr(other.dtype, "tz", None): + try: + other_values = other._values + if hasattr(other_values, "as_unit") and hasattr( + self._values, "equals" + ): + return self._values.equals(other_values.as_unit(self_unit)) + except (ValueError, TypeError, AttributeError): + return False + elif not isinstance(other, type(self)): should_try = False inferable = self._data._infer_matches diff --git a/pandas/tests/indexes/datetimelike_/test_equals.py b/pandas/tests/indexes/datetimelike_/test_equals.py index df9182b2dd1c2..18ed45b8662c3 100644 --- a/pandas/tests/indexes/datetimelike_/test_equals.py +++ b/pandas/tests/indexes/datetimelike_/test_equals.py @@ -136,6 +136,19 @@ def test_equals2(self): assert not idx2.equals(oob2) assert not idx3.equals(oob2) + def test_equals_different_units(self): + # GH#63459 + idx1 = date_range("2013-01-01", periods=5).as_unit("ns") + idx2 = idx1.as_unit("us") + + assert idx1.equals(idx2) is True + assert idx2.equals(idx1) is True + + # Test with mismatching values but same units to ensure we don't + # get false positives + idx3 = date_range("2013-01-02", periods=5).as_unit("us") + assert not idx1.equals(idx3) + @pytest.mark.parametrize("freq", ["B", "C"]) def test_not_equals_bday(self, freq): rng = date_range("2009-01-01", "2010-01-01", freq=freq) @@ -183,3 +196,16 @@ def test_equals2(self): assert (oob3 == oob).all() assert not idx.equals(oob3) assert not idx2.equals(oob3) + + def test_equals_different_units(self): + # GH#63459 + idx1 = timedelta_range("1 day", periods=10).as_unit("ns") + idx2 = idx1.as_unit("us") + + assert idx1.equals(idx2) is True + assert idx2.equals(idx1) is True + + # Ensure that NaT equality still holds across units + idx3 = TimedeltaIndex(["1 days", "NaT"]).as_unit("ns") + idx4 = TimedeltaIndex(["1 days", "NaT"]).as_unit("us") + assert idx3.equals(idx4) is True diff --git a/pandas/tests/indexes/datetimes/test_setops.py b/pandas/tests/indexes/datetimes/test_setops.py index 397aab80531f0..32dbacccbd251 100644 --- a/pandas/tests/indexes/datetimes/test_setops.py +++ b/pandas/tests/indexes/datetimes/test_setops.py @@ -246,7 +246,7 @@ def test_intersection_same_timezone_different_units(self): # 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) + tm.assert_index_equal(result, expected, exact=False) def test_symmetric_difference_same_timezone_different_units(self): # GH 60080 - fix timezone being changed to UTC when units differ