Repository: NCAS-CMS/cf-python

Issue:
subtraction of cf.TimeDuration fails
Hi Both!

Having a bit of trouble with cf.TimeDuration()  - looking at the code `def __sub__` appears to be missing[ this section](https://github.com/NCAS-CMS/cf-python/blob/master/cf/timeduration.py#L526) that appears in `def __add__` 
Is that why it fails? (I should test this!)

Cheers!
Dan

```
t=cf.Data(array=0.0,units='days since 2018-12-01')
t
<CF Data(): 2018-12-01 00:00:00>

 t+cf.TimeDuration(30,'day')
<CF Data(): 2018-12-31 00:00:00 gregorian>

 t-cf.TimeDuration(30,'day')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'Data' and 'TimeDuration'

```

  



```
>> cf.environment(paths=False)
Platform: Linux-3.10.0-1160.42.2.el7.x86_64-x86_64-with-glibc2.10
HDF5 library: 1.10.6
netcdf library: 4.7.4
udunits2 library: /apps/jasmin/jaspy/miniconda_envs/jaspy3.8/m3-4.9.2/envs/jaspy3.8-m3-4.9.2-r20211105/lib/libudunits2.so.0
Python: 3.8.12
netCDF4: 1.5.6
cftime: 1.5.1
numpy: 1.21.3
psutil: 5.8.0
scipy: 1.7.1
matplotlib: 3.4.3
ESMF: 8.1.0
cfdm: 1.9.0.1
cfunits: 3.3.4
cfplot: 3.1.16
cf: 3.11.0

```

Discussion:

Patch:
diff --git a/Changelog.rst b/Changelog.rst
index 0ef1437a7..f223372b3 100644
--- a/Changelog.rst
+++ b/Changelog.rst
@@ -5,6 +5,8 @@ version NEXT
 
 * Fix `cf.aggregate` failures when using the ``dimension`` keyword
   parameter (https://github.com/NCAS-CMS/cf-python/issues/283)
+* Fix bug that raised error with subtraction of a `cf.TimeDuration`
+  (https://github.com/NCAS-CMS/cf-python/issues/287)
 
 ----
 
diff --git a/cf/timeduration.py b/cf/timeduration.py
index 359f0e556..2c8f64195 100644
--- a/cf/timeduration.py
+++ b/cf/timeduration.py
@@ -546,6 +546,13 @@ class TimeDuration:
         if isinstance(other, (self.__class__, int, float)):
             return self._binary_operation(other, "__sub__")
 
+        if hasattr(other, "timetuple"):
+            # other is a date-time object
+            try:
+                return self._datetime_arithmetic(other, __sub__)
+            except TypeError:
+                return NotImplemented
+
         if isinstance(other, Data):
             return self._data_arithmetic(other, "__sub__")
 
@@ -717,17 +724,7 @@ class TimeDuration:
         .. versionadded:: 1.4
 
         """
-        if isinstance(other, (self.__class__, int, float)):
-            return self._binary_operation(other, "__rsub__")
-
-        if hasattr(other, "timetuple"):
-            # other is a date-time object
-            try:
-                return self._datetime_arithmetic(other, __sub__)
-            except TypeError:
-                return NotImplemented
-
-        return NotImplemented
+        return -self + other
 
     def __mod__(self, other):
         """The binary arithmetic operation ``%``


Test patch:
diff --git a/cf/test/test_TimeDuration.py b/cf/test/test_TimeDuration.py
index a4949d2b7..88f9b3e39 100644
--- a/cf/test/test_TimeDuration.py
+++ b/cf/test/test_TimeDuration.py
@@ -474,6 +474,24 @@ class TimeDurationTest(unittest.TestCase):
         self.assertEqual(cf.M(8) / 3, cf.M(8 / 3))
         self.assertEqual(cf.M(8) // 3, cf.M(2))
 
+        # Test arithmetic involving Data as well as datetimes:
+        da = cf.Data([2], units="days since 2000-01-01")
+        dt = cf.TimeDuration(14, "day")
+        t0 = da + dt
+        t1 = dt + da
+        self.assertEqual(
+            t0,
+            cf.dt(2000, 1, 17, calendar="gregorian"),
+        )
+        self.assertEqual(t0, t1)
+        t2 = dt - da
+        t3 = da - dt
+        self.assertEqual(
+            t2,
+            cf.dt(1999, 12, 20, calendar="gregorian"),
+        )
+        self.assertEqual(t2, t3)
+
     def test_Timeduration__days_in_month(self):
         self.assertEqual(cf.TimeDuration.days_in_month(1900, 2), 28)
         self.assertEqual(cf.TimeDuration.days_in_month(1999, 2), 28)