Repository: pandas-dev/pandas

Issue:
ERR: clarify exceptions for invalid datetimelike ops
xref #9630

IIRC most of these are `TypeErrors` (see `tseries/tests_base.py`, section on addition/subtraction ops)
but I guess *some* are`NotImplementedError``

```
In [3]: pd.date_range('20130101',periods=5)+Timestamp('20140101')
NotImplementedError: 
```

```
In [4]: pd.date_range('20130101',periods=5)+Timestamp('20140101').to_datetime64()

TypeError: ufunc add cannot use operands with types dtype('<M8[ns]') and dtype('<M8[ns]')
```



Discussion:
@jreback looks like a good first issue. Would like to work on this one over the next weekend. Are there any blockers / anyone already working on this one?
go for it!
To clarify, based on the discussion from https://github.com/pandas-dev/pandas/pull/9630, all `NotImplementedError`s should be caught and replaced with `TypeError`
Is it correct?
actually that example looks fixed:

```
In [1]: pd.date_range('20130101',periods=5)+Timestamp('20140101')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-4fbfb506deba> in <module>()
----> 1 pd.date_range('20130101',periods=5)+Timestamp('20140101')

/Users/jreback/pandas/pandas/core/indexes/datetimelike.py in __add__(self, other)
    658                 return self.shift(other)
    659             elif isinstance(other, (Timestamp, datetime)):
--> 660                 return self._add_datelike(other)
    661             else:  # pragma: no cover
    662                 return NotImplemented

/Users/jreback/pandas/pandas/core/indexes/datetimes.py in _add_datelike(self, other)
    748         if other is libts.NaT:
    749             return self._nat_new(box=True)
--> 750         raise TypeError("cannot add a datelike to a DatetimeIndex")
    751 
    752     def _sub_datelike(self, other):

TypeError: cannot add a datelike to a DatetimeIndex

```

but

```
In [2]: pd.date_range('20130101',periods=5)+Timestamp('20140101').to_datetime64()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-f97a2c790501> in <module>()
----> 1 pd.date_range('20130101',periods=5)+Timestamp('20140101').to_datetime64()

TypeError: ufunc add cannot use operands with types dtype('<M8[ns]') and dtype('<M8[ns]')

```

should be a 'better' error message (the TypeError is ok)
basically the path for [2] should end up similarly to [1]

Resolving patch:
diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py
--- a/pandas/core/indexes/datetimelike.py
+++ b/pandas/core/indexes/datetimelike.py
@@ -621,7 +621,9 @@ def _convert_scalar_indexer(self, key, kind=None):
                 ._convert_scalar_indexer(key, kind=kind))
 
     def _add_datelike(self, other):
-        raise AbstractMethodError(self)
+        raise TypeError("cannot add {0} and {1}"
+                        .format(type(self).__name__,
+                                type(other).__name__))
 
     def _sub_datelike(self, other):
         raise AbstractMethodError(self)
@@ -647,16 +649,13 @@ def __add__(self, other):
                     return other._add_delta(self)
                 raise TypeError("cannot add TimedeltaIndex and {typ}"
                                 .format(typ=type(other)))
-            elif isinstance(other, Index):
-                raise TypeError("cannot add {typ1} and {typ2}"
-                                .format(typ1=type(self).__name__,
-                                        typ2=type(other).__name__))
             elif isinstance(other, (DateOffset, timedelta, np.timedelta64,
                                     Timedelta)):
                 return self._add_delta(other)
             elif is_integer(other):
                 return self.shift(other)
-            elif isinstance(other, (Timestamp, datetime)):
+            elif isinstance(other, (Index, Timestamp, datetime,
+                                    np.datetime64)):
                 return self._add_datelike(other)
             else:  # pragma: no cover
                 return NotImplemented
diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py
--- a/pandas/core/indexes/datetimes.py
+++ b/pandas/core/indexes/datetimes.py
@@ -747,7 +747,9 @@ def _add_datelike(self, other):
         # adding a timedeltaindex to a datetimelike
         if other is libts.NaT:
             return self._nat_new(box=True)
-        raise TypeError("cannot add a datelike to a DatetimeIndex")
+        raise TypeError("cannot add {0} and {1}"
+                        .format(type(self).__name__,
+                                type(other).__name__))
 
     def _sub_datelike(self, other):
         # subtract a datetime from myself, yielding a TimedeltaIndex


Test patch:
diff --git a/pandas/tests/indexes/datetimes/test_ops.py b/pandas/tests/indexes/datetimes/test_ops.py
--- a/pandas/tests/indexes/datetimes/test_ops.py
+++ b/pandas/tests/indexes/datetimes/test_ops.py
@@ -434,31 +434,51 @@ def test_add_iadd(self):
             tm.assert_index_equal(rng, expected)
 
         idx = DatetimeIndex(['2011-01-01', '2011-01-02'])
-        msg = "cannot add a datelike to a DatetimeIndex"
+        msg = "cannot add DatetimeIndex and Timestamp"
         with tm.assert_raises_regex(TypeError, msg):
             idx + Timestamp('2011-01-01')
 
         with tm.assert_raises_regex(TypeError, msg):
             Timestamp('2011-01-01') + idx
 
-    def test_add_dti_dti(self):
-        # previously performed setop (deprecated in 0.16.0), now raises
-        # TypeError (GH14164)
-
-        dti = date_range('20130101', periods=3)
-        dti_tz = date_range('20130101', periods=3).tz_localize('US/Eastern')
-
-        with pytest.raises(TypeError):
-            dti + dti
-
-        with pytest.raises(TypeError):
-            dti_tz + dti_tz
-
-        with pytest.raises(TypeError):
-            dti_tz + dti
-
-        with pytest.raises(TypeError):
-            dti + dti_tz
+    @pytest.mark.parametrize('addend', [
+        datetime(2011, 1, 1),
+        DatetimeIndex(['2011-01-01', '2011-01-02']),
+        DatetimeIndex(['2011-01-01', '2011-01-02'])
+                .tz_localize('US/Eastern'),
+        np.datetime64('2011-01-01'),
+        Timestamp('2011-01-01'),
+    ])
+    def test_add_datetimelike_and_dti(self, addend):
+        # issue #9631
+
+        dti = DatetimeIndex(['2011-01-01', '2011-01-02'])
+        msg = 'cannot add DatetimeIndex and {0}'.format(
+            type(addend).__name__)
+        with tm.assert_raises_regex(TypeError, msg):
+            dti + addend
+        with tm.assert_raises_regex(TypeError, msg):
+            addend + dti
+
+    @pytest.mark.parametrize('addend', [
+        datetime(2011, 1, 1),
+        DatetimeIndex(['2011-01-01', '2011-01-02']),
+        DatetimeIndex(['2011-01-01', '2011-01-02'])
+                .tz_localize('US/Eastern'),
+        np.datetime64('2011-01-01'),
+        Timestamp('2011-01-01'),
+    ])
+    def test_add_datetimelike_and_dti_tz(self, addend):
+        # issue #9631
+
+        dti_tz = DatetimeIndex(['2011-01-01', '2011-01-02']) \
+            .tz_localize('US/Eastern')
+        msg = 'cannot add DatetimeIndex and {0}'.format(
+            type(addend).__name__)
+        with tm.assert_raises_regex(TypeError, msg):
+            dti_tz + addend
+        with tm.assert_raises_regex(TypeError, msg):
+            addend + dti_tz
 
     def test_difference(self):
         for tz in self.tz: