diff --git a/doc/source/whatsnew/v0.23.0.txt b/doc/source/whatsnew/v0.23.0.txt index 6407a33c442d0..0cd07de2e456d 100644 --- a/doc/source/whatsnew/v0.23.0.txt +++ b/doc/source/whatsnew/v0.23.0.txt @@ -208,6 +208,7 @@ Other API Changes - In :func:`read_excel`, the ``comment`` argument is now exposed as a named parameter (:issue:`18735`) - Rearranged the order of keyword arguments in :func:`read_excel()` to align with :func:`read_csv()` (:issue:`16672`) - The options ``html.border`` and ``mode.use_inf_as_null`` were deprecated in prior versions, these will now show ``FutureWarning`` rather than a ``DeprecationWarning`` (:issue:`19003`) +- Subtracting ``NaT`` from a :class:`Series` with ``dtype='datetime64[ns]'`` returns a ``Series`` with ``dtype='timedelta64[ns]'`` instead of ``dtype='datetime64[ns]'``(:issue:`18808`) .. _whatsnew_0230.deprecations: diff --git a/pandas/core/ops.py b/pandas/core/ops.py index faac8ab312d6b..18659898ae442 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -407,8 +407,12 @@ def _validate_datetime(self, lvalues, rvalues, name): # if tz's must be equal (same or None) if getattr(lvalues, 'tz', None) != getattr(rvalues, 'tz', None): - raise ValueError("Incompatible tz's on datetime subtraction " - "ops") + if len(rvalues) == 1 and isna(rvalues).all(): + # NaT gets a pass + pass + else: + raise ValueError("Incompatible tz's on datetime " + "subtraction ops", rvalues) else: raise TypeError('cannot operate on a series without a rhs ' @@ -505,11 +509,20 @@ def _convert_to_array(self, values, name=None, other=None): inferred_type = lib.infer_dtype(values) if (inferred_type in ('datetime64', 'datetime', 'date', 'time') or is_datetimetz(inferred_type)): + + if ovalues is pd.NaT and name == '__sub__': + # Note: This can only occur when `values` represents `right` + # i.e. `other`. + if other.dtype == 'timedelta64[ns]': + values = np.array([iNaT], dtype='timedelta64[ns]') + else: + values = np.array([iNaT], dtype='datetime64[ns]') + # if we have a other of timedelta, but use pd.NaT here we # we are in the wrong path - if (supplied_dtype is None and other is not None and - (other.dtype in ('timedelta64[ns]', 'datetime64[ns]')) and - isna(values).all()): + elif (supplied_dtype is None and other is not None and + (other.dtype in ('timedelta64[ns]', 'datetime64[ns]')) and + isna(values).all()): values = np.empty(values.shape, dtype='timedelta64[ns]') values[:] = iNaT diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index ce4e388bc6f39..019476c467166 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -960,6 +960,13 @@ def test_timedelta64_ops_nat(self): assert_series_equal(timedelta_series / nan, nat_series_dtype_timedelta) + def test_td64_sub_NaT(self): + # GH#18808 + ser = Series([NaT, Timedelta('1s')]) + res = ser - NaT + expected = Series([NaT, NaT], dtype='timedelta64[ns]') + tm.assert_series_equal(res, expected) + @pytest.mark.parametrize('scalar_td', [timedelta(minutes=5, seconds=4), Timedelta(minutes=5, seconds=4), Timedelta('5m4s').to_timedelta64()]) @@ -1224,13 +1231,10 @@ def test_datetime64_ops_nat(self): single_nat_dtype_datetime = Series([NaT], dtype='datetime64[ns]') # subtraction - assert_series_equal(datetime_series - NaT, nat_series_dtype_timestamp) assert_series_equal(-NaT + datetime_series, nat_series_dtype_timestamp) with pytest.raises(TypeError): -single_nat_dtype_datetime + datetime_series - assert_series_equal(nat_series_dtype_timestamp - NaT, - nat_series_dtype_timestamp) assert_series_equal(-NaT + nat_series_dtype_timestamp, nat_series_dtype_timestamp) with pytest.raises(TypeError): @@ -1263,6 +1267,20 @@ def test_datetime64_ops_nat(self): with pytest.raises(TypeError): nat_series_dtype_timestamp / 1 + def test_dt64_sub_NaT(self): + # GH#18808 + dti = pd.DatetimeIndex([pd.NaT, pd.Timestamp('19900315')]) + ser = pd.Series(dti) + res = ser - pd.NaT + expected = pd.Series([pd.NaT, pd.NaT], dtype='timedelta64[ns]') + tm.assert_series_equal(res, expected) + + dti_tz = dti.tz_localize('Asia/Tokyo') + ser_tz = pd.Series(dti_tz) + res = ser_tz - pd.NaT + expected = pd.Series([pd.NaT, pd.NaT], dtype='timedelta64[ns]') + tm.assert_series_equal(res, expected) + class TestSeriesOperators(TestData): def test_op_method(self):