diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 795d277268e44..d36f990240c5b 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -358,6 +358,7 @@ Strings Interval ^^^^^^^^ - Bug in :class:`Interval` ``__repr__`` not displaying UTC offsets for :class:`Timestamp` bounds. Additionally the hour, minute and second components will now be shown. (:issue:`55015`) +- Bug in :meth:`IntervalIndex.from_arrays` when passed ``datetime64`` or ``timedelta64`` arrays with mismatched resolutions constructing an invalid ``IntervalArray`` object (:issue:`55714`) - Bug in :meth:`IntervalIndex.get_indexer` with datetime or timedelta intervals incorrectly matching on integer targets (:issue:`47772`) - Bug in :meth:`IntervalIndex.get_indexer` with timezone-aware datetime intervals incorrectly matching on a sequence of timezone-naive targets (:issue:`47772`) - Bug in setting values on a :class:`Series` with an :class:`IntervalIndex` using a slice incorrectly raising (:issue:`54722`) diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index 4bc30d6dbd029..28ee6b2476b0d 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -357,6 +357,11 @@ def _ensure_simple_new_inputs( f"'{left.tz}' and '{right.tz}'" ) raise ValueError(msg) + elif needs_i8_conversion(left.dtype) and left.unit != right.unit: + # e.g. m8[s] vs m8[ms], try to cast to a common dtype GH#55714 + left_arr, right_arr = left._data._ensure_matching_resos(right._data) + left = ensure_index(left_arr) + right = ensure_index(right_arr) # For dt64/td64 we want DatetimeArray/TimedeltaArray instead of ndarray left = ensure_wrapped_if_datetimelike(left) diff --git a/pandas/tests/indexes/interval/test_constructors.py b/pandas/tests/indexes/interval/test_constructors.py index 9524288b33eef..1efe5ff980f6c 100644 --- a/pandas/tests/indexes/interval/test_constructors.py +++ b/pandas/tests/indexes/interval/test_constructors.py @@ -256,6 +256,29 @@ def test_mixed_float_int(self, left_subtype, right_subtype): tm.assert_index_equal(result.right, expected_right) assert result.dtype.subtype == expected_subtype + @pytest.mark.parametrize("interval_cls", [IntervalArray, IntervalIndex]) + def test_from_arrays_mismatched_datetimelike_resos(self, interval_cls): + # GH#55714 + left = date_range("2016-01-01", periods=3, unit="s") + right = date_range("2017-01-01", periods=3, unit="ms") + result = interval_cls.from_arrays(left, right) + expected = interval_cls.from_arrays(left.as_unit("ms"), right) + tm.assert_equal(result, expected) + + # td64 + left2 = left - left[0] + right2 = right - left[0] + result2 = interval_cls.from_arrays(left2, right2) + expected2 = interval_cls.from_arrays(left2.as_unit("ms"), right2) + tm.assert_equal(result2, expected2) + + # dt64tz + left3 = left.tz_localize("UTC") + right3 = right.tz_localize("UTC") + result3 = interval_cls.from_arrays(left3, right3) + expected3 = interval_cls.from_arrays(left3.as_unit("ms"), right3) + tm.assert_equal(result3, expected3) + class TestFromBreaks(ConstructorTests): """Tests specific to IntervalIndex.from_breaks"""