Skip to content

Commit 7b0cf28

Browse files
committed
BUG: Fix tz-aware DatetimeIndex +/- TimedeltaIndex/timedelta64 array
1 parent 13f6267 commit 7b0cf28

File tree

4 files changed

+66
-10
lines changed

4 files changed

+66
-10
lines changed

doc/source/whatsnew/v0.22.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ Indexing
202202
- Bug in :class:`IntervalIndex` where empty and purely NA data was constructed inconsistently depending on the construction method (:issue:`18421`)
203203
- Bug in ``IntervalIndex.symmetric_difference()`` where the symmetric difference with a non-``IntervalIndex`` did not raise (:issue:`18475`)
204204
- Bug in indexing a datetimelike ``Index`` that raised ``ValueError`` instead of ``IndexError`` (:issue:`18386`).
205-
205+
- Bug in tz-aware :class:`DatetimeIndex` where addition/subtraction with a :class:`TimedeltaIndex` or array with ``dtype='timedelta64[ns]'`` was incorrect (:issue:`17558`)
206206

207207
I/O
208208
^^^

pandas/core/indexes/datetimelike.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
is_integer, is_float,
1515
is_bool_dtype, _ensure_int64,
1616
is_scalar, is_dtype_equal,
17-
is_list_like)
17+
is_list_like, is_timedelta64_dtype)
1818
from pandas.core.dtypes.generic import (
1919
ABCIndex, ABCSeries,
2020
ABCPeriodIndex, ABCIndexClass)
@@ -651,14 +651,14 @@ def __add__(self, other):
651651
from pandas.core.index import Index
652652
from pandas.core.indexes.timedeltas import TimedeltaIndex
653653
from pandas.tseries.offsets import DateOffset
654-
if isinstance(other, TimedeltaIndex):
654+
if is_timedelta64_dtype(other):
655655
return self._add_delta(other)
656656
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
657657
if hasattr(other, '_add_delta'):
658658
return other._add_delta(self)
659659
raise TypeError("cannot add TimedeltaIndex and {typ}"
660660
.format(typ=type(other)))
661-
elif isinstance(other, (DateOffset, timedelta, np.timedelta64)):
661+
elif isinstance(other, (DateOffset, timedelta)):
662662
return self._add_delta(other)
663663
elif is_integer(other):
664664
return self.shift(other)
@@ -674,7 +674,7 @@ def __sub__(self, other):
674674
from pandas.core.indexes.datetimes import DatetimeIndex
675675
from pandas.core.indexes.timedeltas import TimedeltaIndex
676676
from pandas.tseries.offsets import DateOffset
677-
if isinstance(other, TimedeltaIndex):
677+
if is_timedelta64_dtype(other):
678678
return self._add_delta(-other)
679679
elif isinstance(self, TimedeltaIndex) and isinstance(other, Index):
680680
if not isinstance(other, TimedeltaIndex):
@@ -687,7 +687,7 @@ def __sub__(self, other):
687687
raise TypeError("cannot subtract {typ1} and {typ2}"
688688
.format(typ1=type(self).__name__,
689689
typ2=type(other).__name__))
690-
elif isinstance(other, (DateOffset, timedelta, np.timedelta64)):
690+
elif isinstance(other, (DateOffset, timedelta)):
691691
return self._add_delta(-other)
692692
elif is_integer(other):
693693
return self.shift(-other)
@@ -736,7 +736,7 @@ def _add_delta_tdi(self, other):
736736
if self.hasnans or other.hasnans:
737737
mask = (self._isnan) | (other._isnan)
738738
new_values[mask] = iNaT
739-
return new_values.view(self.dtype)
739+
return new_values.view('i8')
740740

741741
def isin(self, values):
742742
"""

pandas/core/indexes/datetimes.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
_NS_DTYPE, _INT64_DTYPE,
1414
is_object_dtype, is_datetime64_dtype,
1515
is_datetimetz, is_dtype_equal,
16+
is_timedelta64_dtype,
1617
is_integer, is_float,
1718
is_integer_dtype,
1819
is_datetime64_ns_dtype,
@@ -859,10 +860,13 @@ def _add_delta(self, delta):
859860

860861
if isinstance(delta, (Tick, timedelta, np.timedelta64)):
861862
new_values = self._add_delta_td(delta)
862-
elif isinstance(delta, TimedeltaIndex):
863+
elif is_timedelta64_dtype(delta):
864+
if not isinstance(delta, TimedeltaIndex):
865+
delta = TimedeltaIndex(delta)
866+
else:
867+
# update name when delta is Index
868+
name = com._maybe_match_name(self, delta)
863869
new_values = self._add_delta_tdi(delta)
864-
# update name when delta is Index
865-
name = com._maybe_match_name(self, delta)
866870
elif isinstance(delta, DateOffset):
867871
new_values = self._add_offset(delta).asi8
868872
else:

pandas/tests/indexes/datetimes/test_arithmetic.py

+52
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,58 @@ def test_dti_isub_timedeltalike(self, tz, delta):
121121
rng -= delta
122122
tm.assert_index_equal(rng, expected)
123123

124+
# -------------------------------------------------------------
125+
# Binary operations DatetimeIndex and TimedeltaIndex/array
126+
def test_dti_add_tdi(self, tz):
127+
# GH 17558
128+
dti = DatetimeIndex([Timestamp('2017-01-01', tz=tz)] * 10)
129+
tdi = pd.timedelta_range('0 days', periods=10)
130+
expected = pd.date_range('2017-01-01', periods=10, tz=tz)
131+
132+
result = dti + tdi
133+
tm.assert_index_equal(result, expected)
134+
135+
result = dti + tdi.values
136+
tm.assert_index_equal(result, expected)
137+
138+
def test_dti_iadd_tdi(self, tz):
139+
# GH 17558
140+
tdi = pd.timedelta_range('0 days', periods=10)
141+
expected = pd.date_range('2017-01-01', periods=10, tz=tz)
142+
143+
result = DatetimeIndex([Timestamp('2017-01-01', tz=tz)] * 10)
144+
result += tdi
145+
tm.assert_index_equal(result, expected)
146+
147+
result = DatetimeIndex([Timestamp('2017-01-01', tz=tz)] * 10)
148+
result += tdi.values
149+
tm.assert_index_equal(result, expected)
150+
151+
def test_dti_sub_tdi(self, tz):
152+
# GH 17558
153+
dti = DatetimeIndex([Timestamp('2017-01-01', tz=tz)] * 10)
154+
tdi = pd.timedelta_range('0 days', periods=10)
155+
expected = pd.date_range('2017-01-01', periods=10, tz=tz, freq='-1D')
156+
157+
result = dti - tdi
158+
tm.assert_index_equal(result, expected)
159+
160+
result = dti - tdi.values
161+
tm.assert_index_equal(result, expected)
162+
163+
def test_dti_isub_tdi(self, tz):
164+
# GH 17558
165+
tdi = pd.timedelta_range('0 days', periods=10)
166+
expected = pd.date_range('2017-01-01', periods=10, tz=tz, freq='-1D')
167+
168+
result = DatetimeIndex([Timestamp('2017-01-01', tz=tz)] * 10)
169+
result -= tdi
170+
tm.assert_index_equal(result, expected)
171+
172+
result = DatetimeIndex([Timestamp('2017-01-01', tz=tz)] * 10)
173+
result -= tdi.values
174+
tm.assert_index_equal(result, expected)
175+
124176
# -------------------------------------------------------------
125177
# Binary Operations DatetimeIndex and datetime-like
126178
# TODO: A couple other tests belong in this section. Move them in

0 commit comments

Comments
 (0)