Repository: pandas-dev/pandas

Issue:
DatetimeIndex/TimedeltaIndex add/sub zero-dim arrays incorrect
Opening this mainly to get the appropriate reference for the upcoming PR.


Setup:
```
dti = pd.date_range('2016-01-01', periods=3, freq='H')
one = np.array(1)
```

0.21.1:
```
>>> dti + one
DatetimeIndex(['2016-01-01 00:00:00.000000001',
               '2016-01-01 01:00:00.000000001',
               '2016-01-01 02:00:00.000000001'],
              dtype='datetime64[ns]', freq='H')
>>> dti.freq = None
>>> dti + one
DatetimeIndex(['2016-01-01 00:00:00.000000001',
               '2016-01-01 01:00:00.000000001',
               '2016-01-01 02:00:00.000000001'],
              dtype='datetime64[ns]', freq=None)
```

Master (see #19011)
```
>>> dti + one
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pandas/core/indexes/datetimelike.py", line 685, in __add__
    elif is_offsetlike(other):
  File "pandas/core/dtypes/common.py", line 294, in is_offsetlike
    elif (is_list_like(arr_or_obj) and len(arr_or_obj) and
TypeError: len() of unsized object
```


Discussion:

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
@@ -669,6 +669,8 @@ def __add__(self, other):
             from pandas.core.index import Index
             from pandas.core.indexes.timedeltas import TimedeltaIndex
             from pandas.tseries.offsets import DateOffset
+
+            other = lib.item_from_zerodim(other)
             if is_timedelta64_dtype(other):
                 return self._add_delta(other)
             elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
@@ -689,6 +691,7 @@ def __add__(self, other):
                 return self._add_datelike(other)
             else:  # pragma: no cover
                 return NotImplemented
+
         cls.__add__ = __add__
         cls.__radd__ = __add__
 
@@ -697,6 +700,8 @@ def __sub__(self, other):
             from pandas.core.indexes.datetimes import DatetimeIndex
             from pandas.core.indexes.timedeltas import TimedeltaIndex
             from pandas.tseries.offsets import DateOffset
+
+            other = lib.item_from_zerodim(other)
             if is_timedelta64_dtype(other):
                 return self._add_delta(-other)
             elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
@@ -724,6 +729,7 @@ def __sub__(self, other):
 
             else:  # pragma: no cover
                 return NotImplemented
+
         cls.__sub__ = __sub__
 
         def __rsub__(self, other):
@@ -737,8 +743,10 @@ def _add_delta(self, other):
         return NotImplemented
 
     def _add_delta_td(self, other):
-        # add a delta of a timedeltalike
-        # return the i8 result view
+        """
+        Add a delta of a timedeltalike
+        return the i8 result view
+        """
 
         inc = delta_to_nanoseconds(other)
         new_values = checked_add_with_arr(self.asi8, inc,
@@ -748,8 +756,10 @@ def _add_delta_td(self, other):
         return new_values.view('i8')
 
     def _add_delta_tdi(self, other):
-        # add a delta of a TimedeltaIndex
-        # return the i8 result view
+        """
+        Add a delta of a TimedeltaIndex
+        return the i8 result view
+        """
 
         # delta operation
         if not len(self) == len(other):


Test patch:
diff --git a/pandas/tests/indexes/conftest.py b/pandas/tests/indexes/conftest.py
--- a/pandas/tests/indexes/conftest.py
+++ b/pandas/tests/indexes/conftest.py
@@ -1,4 +1,5 @@
 import pytest
+import numpy as np
 
 import pandas.util.testing as tm
 from pandas.core.indexes.api import Index, MultiIndex
@@ -22,3 +23,9 @@
                 ids=lambda x: type(x).__name__)
 def indices(request):
     return request.param
+
+
+@pytest.fixture(params=[1, np.array(1, dtype=np.int64)])
+def one(request):
+    # zero-dim integer array behaves like an integer
+    return request.param
diff --git a/pandas/tests/indexes/datetimes/test_arithmetic.py b/pandas/tests/indexes/datetimes/test_arithmetic.py
--- a/pandas/tests/indexes/datetimes/test_arithmetic.py
+++ b/pandas/tests/indexes/datetimes/test_arithmetic.py
@@ -58,36 +58,37 @@ def test_dti_radd_timestamp_raises(self):
     # -------------------------------------------------------------
     # Binary operations DatetimeIndex and int
 
-    def test_dti_add_int(self, tz):
+    def test_dti_add_int(self, tz, one):
+        # Variants of `one` for #19012
         rng = pd.date_range('2000-01-01 09:00', freq='H',
                             periods=10, tz=tz)
-        result = rng + 1
+        result = rng + one
         expected = pd.date_range('2000-01-01 10:00', freq='H',
                                  periods=10, tz=tz)
         tm.assert_index_equal(result, expected)
 
-    def test_dti_iadd_int(self, tz):
+    def test_dti_iadd_int(self, tz, one):
         rng = pd.date_range('2000-01-01 09:00', freq='H',
                             periods=10, tz=tz)
         expected = pd.date_range('2000-01-01 10:00', freq='H',
                                  periods=10, tz=tz)
-        rng += 1
+        rng += one
         tm.assert_index_equal(rng, expected)
 
-    def test_dti_sub_int(self, tz):
+    def test_dti_sub_int(self, tz, one):
         rng = pd.date_range('2000-01-01 09:00', freq='H',
                             periods=10, tz=tz)
-        result = rng - 1
+        result = rng - one
         expected = pd.date_range('2000-01-01 08:00', freq='H',
                                  periods=10, tz=tz)
         tm.assert_index_equal(result, expected)
 
-    def test_dti_isub_int(self, tz):
+    def test_dti_isub_int(self, tz, one):
         rng = pd.date_range('2000-01-01 09:00', freq='H',
                             periods=10, tz=tz)
         expected = pd.date_range('2000-01-01 08:00', freq='H',
                                  periods=10, tz=tz)
-        rng -= 1
+        rng -= one
         tm.assert_index_equal(rng, expected)
 
     # -------------------------------------------------------------
diff --git a/pandas/tests/indexes/period/test_arithmetic.py b/pandas/tests/indexes/period/test_arithmetic.py
--- a/pandas/tests/indexes/period/test_arithmetic.py
+++ b/pandas/tests/indexes/period/test_arithmetic.py
@@ -131,19 +131,21 @@ def test_add_iadd(self):
                     period.IncompatibleFrequency, msg):
                 rng += delta
 
-        # int
+    def test_pi_add_int(self, one):
+        # Variants of `one` for #19012
         rng = pd.period_range('2000-01-01 09:00', freq='H', periods=10)
-        result = rng + 1
+        result = rng + one
         expected = pd.period_range('2000-01-01 10:00', freq='H', periods=10)
         tm.assert_index_equal(result, expected)
-        rng += 1
+        rng += one
         tm.assert_index_equal(rng, expected)
 
-    def test_sub(self):
+    @pytest.mark.parametrize('five', [5, np.array(5, dtype=np.int64)])
+    def test_sub(self, five):
         rng = period_range('2007-01', periods=50)
 
-        result = rng - 5
-        exp = rng + (-5)
+        result = rng - five
+        exp = rng + (-five)
         tm.assert_index_equal(result, exp)
 
     def test_sub_isub(self):
diff --git a/pandas/tests/indexes/timedeltas/test_arithmetic.py b/pandas/tests/indexes/timedeltas/test_arithmetic.py
--- a/pandas/tests/indexes/timedeltas/test_arithmetic.py
+++ b/pandas/tests/indexes/timedeltas/test_arithmetic.py
@@ -121,28 +121,29 @@ def test_ufunc_coercions(self):
     # -------------------------------------------------------------
     # Binary operations TimedeltaIndex and integer
 
-    def test_tdi_add_int(self):
+    def test_tdi_add_int(self, one):
+        # Variants of `one` for #19012
         rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
-        result = rng + 1
+        result = rng + one
         expected = timedelta_range('1 days 10:00:00', freq='H', periods=10)
         tm.assert_index_equal(result, expected)
 
-    def test_tdi_iadd_int(self):
+    def test_tdi_iadd_int(self, one):
         rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
         expected = timedelta_range('1 days 10:00:00', freq='H', periods=10)
-        rng += 1
+        rng += one
         tm.assert_index_equal(rng, expected)
 
-    def test_tdi_sub_int(self):
+    def test_tdi_sub_int(self, one):
         rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
-        result = rng - 1
+        result = rng - one
         expected = timedelta_range('1 days 08:00:00', freq='H', periods=10)
         tm.assert_index_equal(result, expected)
 
-    def test_tdi_isub_int(self):
+    def test_tdi_isub_int(self, one):
         rng = timedelta_range('1 days 09:00:00', freq='H', periods=10)
         expected = timedelta_range('1 days 08:00:00', freq='H', periods=10)
-        rng -= 1
+        rng -= one
         tm.assert_index_equal(rng, expected)
 
     # -------------------------------------------------------------