diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx index 594de703258a4..1c2f80b832201 100644 --- a/pandas/_libs/lib.pyx +++ b/pandas/_libs/lib.pyx @@ -55,8 +55,7 @@ cimport pandas._libs.util as util from pandas._libs.util cimport is_nan, UINT64_MAX, INT64_MAX, INT64_MIN from pandas._libs.tslib import array_to_datetime -from pandas._libs.tslibs.nattype cimport NPY_NAT -from pandas._libs.tslibs.nattype import NaT +from pandas._libs.tslibs.nattype cimport NPY_NAT, c_NaT as NaT from pandas._libs.tslibs.conversion cimport convert_to_tsobject from pandas._libs.tslibs.timedeltas cimport convert_to_timedelta64 from pandas._libs.tslibs.timezones cimport get_timezone, tz_compare @@ -525,9 +524,18 @@ def array_equivalent_object(left: object[:], right: object[:]) -> bool: # we are either not equal or both nan # I think None == None will be true here - if not (PyObject_RichCompareBool(x, y, Py_EQ) or - (x is None or is_nan(x)) and (y is None or is_nan(y))): - return False + try: + if not (PyObject_RichCompareBool(x, y, Py_EQ) or + (x is None or is_nan(x)) and (y is None or is_nan(y))): + return False + except TypeError as err: + # Avoid raising TypeError on tzawareness mismatch + # TODO: This try/except can be removed if/when Timestamp + # comparisons are change dto match datetime, see GH#28507 + if "tz-naive and tz-aware" in str(err): + return False + raise + return True diff --git a/pandas/core/dtypes/missing.py b/pandas/core/dtypes/missing.py index 6dd032b9248ed..cd87fbef02e4f 100644 --- a/pandas/core/dtypes/missing.py +++ b/pandas/core/dtypes/missing.py @@ -445,8 +445,14 @@ def array_equivalent(left, right, strict_nan=False): if not isinstance(right_value, float) or not np.isnan(right_value): return False else: - if np.any(left_value != right_value): - return False + try: + if np.any(left_value != right_value): + return False + except TypeError as err: + if "Cannot compare tz-naive" in str(err): + # tzawareness compat failure, see GH#28507 + return False + raise return True # NaNs can occur in float and complex arrays. diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 6ef9d78ff9e97..c7e9dd5f0ea6d 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -4324,12 +4324,9 @@ def equals(self, other): # if other is not object, use other's logic for coercion return other.equals(self) - try: - return array_equivalent( - com.values_from_object(self), com.values_from_object(other) - ) - except Exception: - return False + return array_equivalent( + com.values_from_object(self), com.values_from_object(other) + ) def identical(self, other): """ diff --git a/pandas/tests/dtypes/test_missing.py b/pandas/tests/dtypes/test_missing.py index 1a292d5bfcbb6..25b447e1df7d4 100644 --- a/pandas/tests/dtypes/test_missing.py +++ b/pandas/tests/dtypes/test_missing.py @@ -25,6 +25,9 @@ from pandas import DatetimeIndex, Float64Index, NaT, Series, TimedeltaIndex, date_range from pandas.util import testing as tm +now = pd.Timestamp.now() +utcnow = pd.Timestamp.now("UTC") + @pytest.mark.parametrize("notna_f", [notna, notnull]) def test_notna_notnull(notna_f): @@ -332,6 +335,29 @@ def test_array_equivalent(): assert not array_equivalent(DatetimeIndex([0, np.nan]), TimedeltaIndex([0, np.nan])) +@pytest.mark.parametrize( + "lvalue, rvalue", + [ + # There are 3 variants for each of lvalue and rvalue. We include all + # three for the tz-naive `now` and exclude the datetim64 variant + # for utcnow because it drops tzinfo. + (now, utcnow), + (now.to_datetime64(), utcnow), + (now.to_pydatetime(), utcnow), + (now, utcnow), + (now.to_datetime64(), utcnow.to_pydatetime()), + (now.to_pydatetime(), utcnow.to_pydatetime()), + ], +) +def test_array_equivalent_tzawareness(lvalue, rvalue): + # we shouldn't raise if comparing tzaware and tznaive datetimes + left = np.array([lvalue], dtype=object) + right = np.array([rvalue], dtype=object) + + assert not array_equivalent(left, right, strict_nan=True) + assert not array_equivalent(left, right, strict_nan=False) + + def test_array_equivalent_compat(): # see gh-13388 m = np.array([(1, 2), (3, 4)], dtype=[("a", int), ("b", float)])