diff --git a/pandas/_libs/index.pyx b/pandas/_libs/index.pyx index ba2838d59f814..7000c07b1f5a6 100644 --- a/pandas/_libs/index.pyx +++ b/pandas/_libs/index.pyx @@ -544,6 +544,10 @@ cpdef convert_scalar(ndarray arr, object value): pass elif isinstance(value, timedelta): return Timedelta(value).value + elif util.is_datetime64_object(value): + # exclude np.datetime64("NaT") which would otherwise be picked up + # by the `value != value check below + pass elif value is None or value != value: return NPY_NAT elif isinstance(value, str): diff --git a/pandas/core/series.py b/pandas/core/series.py index 6a58b1ea6f82d..dc9a42b7071cb 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -46,6 +46,7 @@ ABCSparseSeries, ) from pandas.core.dtypes.missing import ( + is_valid_nat_for_dtype, isna, na_value_for_dtype, notna, @@ -1198,13 +1199,15 @@ def setitem(key, value): pass elif is_timedelta64_dtype(self.dtype): # reassign a null value to iNaT - if isna(value): + if is_valid_nat_for_dtype(value, self.dtype): + # exclude np.datetime64("NaT") value = iNaT try: self.index._engine.set_value(self._values, key, value) return - except TypeError: + except (TypeError, ValueError): + # ValueError appears in only some builds in CI pass self.loc[key] = value diff --git a/pandas/tests/series/indexing/test_indexing.py b/pandas/tests/series/indexing/test_indexing.py index 6ff878f07da84..d73be76795c88 100644 --- a/pandas/tests/series/indexing/test_indexing.py +++ b/pandas/tests/series/indexing/test_indexing.py @@ -654,6 +654,36 @@ def test_timedelta_assignment(): tm.assert_series_equal(s, expected) +@pytest.mark.parametrize( + "nat_val,should_cast", + [ + (pd.NaT, True), + (np.timedelta64("NaT", "ns"), True), + (np.datetime64("NaT", "ns"), False), + ], +) +def test_td64_series_assign_nat(nat_val, should_cast): + # some nat-like values should be cast to timedelta64 when inserting + # into a timedelta64 series. Others should coerce to object + # and retain their dtypes. + base = pd.Series([0, 1, 2], dtype="m8[ns]") + expected = pd.Series([pd.NaT, 1, 2], dtype="m8[ns]") + if not should_cast: + expected = expected.astype(object) + + ser = base.copy(deep=True) + ser[0] = nat_val + tm.assert_series_equal(ser, expected) + + ser = base.copy(deep=True) + ser.loc[0] = nat_val + tm.assert_series_equal(ser, expected) + + ser = base.copy(deep=True) + ser.iloc[0] = nat_val + tm.assert_series_equal(ser, expected) + + @pytest.mark.parametrize( "td", [