diff --git a/pandas/_libs/missing.pyx b/pandas/_libs/missing.pyx index e6516b004a973..8df73398f1e4a 100644 --- a/pandas/_libs/missing.pyx +++ b/pandas/_libs/missing.pyx @@ -338,6 +338,14 @@ def _create_binary_propagating_op(name, is_divmod=False): elif is_cmp and isinstance(other, (date, time, timedelta)): return NA + elif isinstance(other, date): + if name in ["__sub__", "__rsub__"]: + return NA + + elif isinstance(other, timedelta): + if name in ["__sub__", "__rsub__", "__add__", "__radd__"]: + return NA + return NotImplemented method.__name__ = name diff --git a/pandas/tests/extension/test_arrow.py b/pandas/tests/extension/test_arrow.py index bdefc65e1f957..705e9d55c06e7 100644 --- a/pandas/tests/extension/test_arrow.py +++ b/pandas/tests/extension/test_arrow.py @@ -384,7 +384,7 @@ def test_accumulate_series(self, data, all_numeric_accumulations, skipna, reques # renders the exception messages even when not showing them pytest.skip(f"{all_numeric_accumulations} not implemented for pyarrow < 9") - elif all_numeric_accumulations == "cumsum" and (pa.types.is_boolean(pa_type)): + elif all_numeric_accumulations == "cumsum" and pa.types.is_boolean(pa_type): request.node.add_marker( pytest.mark.xfail( reason=f"{all_numeric_accumulations} not implemented for {pa_type}", @@ -859,17 +859,7 @@ def test_factorize(self, data_for_grouping, request): def test_combine_add(self, data_repeated, request): pa_dtype = next(data_repeated(1)).dtype.pyarrow_dtype - if pa.types.is_duration(pa_dtype): - # TODO: this fails on the scalar addition constructing 'expected' - # but not in the actual 'combine' call, so may be salvage-able - mark = pytest.mark.xfail( - raises=TypeError, - reason=f"{pa_dtype} cannot be added to {pa_dtype}", - ) - request.node.add_marker(mark) - super().test_combine_add(data_repeated) - - elif pa.types.is_temporal(pa_dtype): + if pa.types.is_temporal(pa_dtype) and not pa.types.is_duration(pa_dtype): # analogous to datetime64, these cannot be added orig_data1, orig_data2 = data_repeated(2) s1 = pd.Series(orig_data1) @@ -915,14 +905,24 @@ def _patch_combine(self, obj, other, op): pa_expected = pa.array(expected_data._values) if pa.types.is_duration(pa_expected.type): - # pyarrow sees sequence of datetime/timedelta objects and defaults - # to "us" but the non-pointwise op retains unit - unit = original_dtype.pyarrow_dtype.unit - if type(other) in [datetime, timedelta] and unit in ["s", "ms"]: - # pydatetime/pytimedelta objects have microsecond reso, so we - # take the higher reso of the original and microsecond. Note - # this matches what we would do with DatetimeArray/TimedeltaArray - unit = "us" + orig_pa_type = original_dtype.pyarrow_dtype + if pa.types.is_date(orig_pa_type): + if pa.types.is_date64(orig_pa_type): + # TODO: why is this different vs date32? + unit = "ms" + else: + unit = "s" + else: + # pyarrow sees sequence of datetime/timedelta objects and defaults + # to "us" but the non-pointwise op retains unit + # timestamp or duration + unit = orig_pa_type.unit + if type(other) in [datetime, timedelta] and unit in ["s", "ms"]: + # pydatetime/pytimedelta objects have microsecond reso, so we + # take the higher reso of the original and microsecond. Note + # this matches what we would do with DatetimeArray/TimedeltaArray + unit = "us" + pa_expected = pa_expected.cast(f"duration[{unit}]") else: pa_expected = pa_expected.cast(original_dtype.pyarrow_dtype) @@ -979,7 +979,7 @@ def _get_arith_xfail_marker(self, opname, pa_dtype): f"for {pa_dtype}" ) ) - elif arrow_temporal_supported: + elif arrow_temporal_supported and pa.types.is_time(pa_dtype): mark = pytest.mark.xfail( raises=TypeError, reason=( @@ -1024,6 +1024,7 @@ def test_arith_series_with_scalar( ) or pa.types.is_duration(pa_dtype) or pa.types.is_timestamp(pa_dtype) + or pa.types.is_date(pa_dtype) ): # BaseOpsUtil._combine always returns int64, while ArrowExtensionArray does # not upcast @@ -1055,6 +1056,7 @@ def test_arith_frame_with_scalar( ) or pa.types.is_duration(pa_dtype) or pa.types.is_timestamp(pa_dtype) + or pa.types.is_date(pa_dtype) ): # BaseOpsUtil._combine always returns int64, while ArrowExtensionArray does # not upcast @@ -1107,6 +1109,7 @@ def test_arith_series_with_array( ) or pa.types.is_duration(pa_dtype) or pa.types.is_timestamp(pa_dtype) + or pa.types.is_date(pa_dtype) ): monkeypatch.setattr(TestBaseArithmeticOps, "_combine", self._patch_combine) self.check_opname(ser, op_name, other, exc=self.series_array_exc)