diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 39ba72063ef..f6fdcef9306 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -56,6 +56,10 @@ Bug fixes `Spencer Clark `_. - Line plots with the `x` argument set to a non-dimensional coord now plot the correct data for 1D DataArrays. (:issue:`27251). By `Tom Nicholas `_. +- Subtracting a scalar ``cftime.datetime`` object from a + :py:class:`CFTimeIndex` now results in a :py:class:`pandas.TimedeltaIndex` + instead of raising a ``TypeError`` (:issue:`2671`). By `Spencer Clark + `_. .. _whats-new.0.11.3: diff --git a/xarray/coding/cftimeindex.py b/xarray/coding/cftimeindex.py index 1861d49a1d4..f1a05d31a0c 100644 --- a/xarray/coding/cftimeindex.py +++ b/xarray/coding/cftimeindex.py @@ -408,13 +408,17 @@ def __radd__(self, other): return CFTimeIndex(other + np.array(self)) def __sub__(self, other): - if isinstance(other, CFTimeIndex): + import cftime + if isinstance(other, (CFTimeIndex, cftime.datetime)): return pd.TimedeltaIndex(np.array(self) - np.array(other)) elif isinstance(other, pd.TimedeltaIndex): return CFTimeIndex(np.array(self) - other.to_pytimedelta()) else: return CFTimeIndex(np.array(self) - other) + def __rsub__(self, other): + return pd.TimedeltaIndex(other - np.array(self)) + def _add_delta(self, deltas): # To support TimedeltaIndex + CFTimeIndex with older versions of # pandas. No longer used as of pandas 0.23. diff --git a/xarray/tests/test_cftimeindex.py b/xarray/tests/test_cftimeindex.py index 0d6ba6b47c9..97be993d842 100644 --- a/xarray/tests/test_cftimeindex.py +++ b/xarray/tests/test_cftimeindex.py @@ -700,6 +700,26 @@ def test_cftimeindex_sub_cftimeindex(calendar): assert isinstance(result, pd.TimedeltaIndex) +@pytest.mark.skipif(not has_cftime, reason='cftime not installed') +@pytest.mark.parametrize('calendar', _CFTIME_CALENDARS) +def test_cftimeindex_sub_cftime_datetime(calendar): + a = xr.cftime_range('2000', periods=5, calendar=calendar) + result = a - a[0] + expected = pd.TimedeltaIndex([timedelta(days=i) for i in range(5)]) + assert result.equals(expected) + assert isinstance(result, pd.TimedeltaIndex) + + +@pytest.mark.skipif(not has_cftime, reason='cftime not installed') +@pytest.mark.parametrize('calendar', _CFTIME_CALENDARS) +def test_cftime_datetime_sub_cftimeindex(calendar): + a = xr.cftime_range('2000', periods=5, calendar=calendar) + result = a[0] - a + expected = pd.TimedeltaIndex([timedelta(days=-i) for i in range(5)]) + assert result.equals(expected) + assert isinstance(result, pd.TimedeltaIndex) + + @pytest.mark.skipif(not has_cftime, reason='cftime not installed') @pytest.mark.parametrize('calendar', _CFTIME_CALENDARS) def test_cftimeindex_sub_timedeltaindex(calendar):