Skip to content

Commit 78c6415

Browse files
jbrockmendelproost
authored andcommitted
BUG: DataFrame[int] +/- datetime64 (pandas-dev#28362)
1 parent 2fd29dc commit 78c6415

File tree

4 files changed

+57
-12
lines changed

4 files changed

+57
-12
lines changed

doc/source/whatsnew/v1.0.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ Datetimelike
119119
- Bug in :meth:`pandas.core.groupby.SeriesGroupBy.nunique` where ``NaT`` values were interfering with the count of unique values (:issue:`27951`)
120120
- Bug in :class:`Timestamp` subtraction when subtracting a :class:`Timestamp` from a ``np.datetime64`` object incorrectly raising ``TypeError`` (:issue:`28286`)
121121
- Addition and subtraction of integer or integer-dtype arrays with :class:`Timestamp` will now raise ``NullFrequencyError`` instead of ``ValueError`` (:issue:`28268`)
122+
- Bug in :class:`Series` and :class:`DataFrame` with integer dtype failing to raise ``TypeError`` when adding or subtracting a ``np.datetime64`` object (:issue:`28080`)
123+
-
122124

123125

124126
Timedelta

pandas/_libs/tslibs/nattype.pyx

+6
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ cdef class _NaT(datetime):
150150
result = np.empty(other.shape, dtype="datetime64[ns]")
151151
result.fill("NaT")
152152
return result
153+
raise TypeError("Cannot add NaT to ndarray with dtype {dtype}"
154+
.format(dtype=other.dtype))
153155

154156
return NotImplemented
155157

@@ -201,6 +203,10 @@ cdef class _NaT(datetime):
201203
result.fill("NaT")
202204
return result
203205

206+
raise TypeError(
207+
"Cannot subtract NaT from ndarray with dtype {dtype}"
208+
.format(dtype=other.dtype))
209+
204210
return NotImplemented
205211

206212
def __pos__(self):

pandas/core/ops/__init__.py

+23-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import numpy as np
1111

12-
from pandas._libs import Timedelta, lib, ops as libops
12+
from pandas._libs import Timedelta, Timestamp, lib, ops as libops
1313
from pandas.errors import NullFrequencyError
1414
from pandas.util._decorators import Appender
1515

@@ -148,13 +148,24 @@ def maybe_upcast_for_op(obj, shape: Tuple[int, ...]):
148148
Be careful to call this *after* determining the `name` attribute to be
149149
attached to the result of the arithmetic operation.
150150
"""
151-
from pandas.core.arrays import TimedeltaArray
151+
from pandas.core.arrays import DatetimeArray, TimedeltaArray
152152

153153
if type(obj) is datetime.timedelta:
154154
# GH#22390 cast up to Timedelta to rely on Timedelta
155155
# implementation; otherwise operation against numeric-dtype
156156
# raises TypeError
157157
return Timedelta(obj)
158+
elif isinstance(obj, np.datetime64):
159+
# GH#28080 numpy casts integer-dtype to datetime64 when doing
160+
# array[int] + datetime64, which we do not allow
161+
if isna(obj):
162+
# Avoid possible ambiguities with pd.NaT
163+
obj = obj.astype("datetime64[ns]")
164+
right = np.broadcast_to(obj, shape)
165+
return DatetimeArray(right)
166+
167+
return Timestamp(obj)
168+
158169
elif isinstance(obj, np.timedelta64):
159170
if isna(obj):
160171
# wrapping timedelta64("NaT") in Timedelta returns NaT,
@@ -624,7 +635,13 @@ def wrapper(left, right):
624635

625636
keep_null_freq = isinstance(
626637
right,
627-
(ABCDatetimeIndex, ABCDatetimeArray, ABCTimedeltaIndex, ABCTimedeltaArray),
638+
(
639+
ABCDatetimeIndex,
640+
ABCDatetimeArray,
641+
ABCTimedeltaIndex,
642+
ABCTimedeltaArray,
643+
Timestamp,
644+
),
628645
)
629646

630647
left, right = _align_method_SERIES(left, right)
@@ -635,13 +652,9 @@ def wrapper(left, right):
635652

636653
rvalues = maybe_upcast_for_op(rvalues, lvalues.shape)
637654

638-
if should_extension_dispatch(lvalues, rvalues):
639-
result = dispatch_to_extension_op(op, lvalues, rvalues, keep_null_freq)
640-
641-
elif is_timedelta64_dtype(rvalues) or isinstance(rvalues, ABCDatetimeArray):
642-
# We should only get here with td64 rvalues with non-scalar values
643-
# for rvalues upcast by maybe_upcast_for_op
644-
assert not isinstance(rvalues, (np.timedelta64, np.ndarray))
655+
if should_extension_dispatch(left, rvalues) or isinstance(
656+
rvalues, (ABCTimedeltaArray, ABCDatetimeArray, Timestamp)
657+
):
645658
result = dispatch_to_extension_op(op, lvalues, rvalues, keep_null_freq)
646659

647660
else:

pandas/tests/arithmetic/test_numeric.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ def test_compare_invalid(self):
7373

7474

7575
# ------------------------------------------------------------------
76-
# Numeric dtypes Arithmetic with Timedelta Scalar
76+
# Numeric dtypes Arithmetic with Datetime/Timedelta Scalar
7777

7878

79-
class TestNumericArraylikeArithmeticWithTimedeltaLike:
79+
class TestNumericArraylikeArithmeticWithDatetimeLike:
8080

8181
# TODO: also check name retentention
8282
@pytest.mark.parametrize("box_cls", [np.array, pd.Index, pd.Series])
@@ -235,6 +235,30 @@ def test_add_sub_timedeltalike_invalid(self, numeric_idx, other, box):
235235
with pytest.raises(TypeError):
236236
other - left
237237

238+
@pytest.mark.parametrize(
239+
"other",
240+
[
241+
pd.Timestamp.now().to_pydatetime(),
242+
pd.Timestamp.now(tz="UTC").to_pydatetime(),
243+
pd.Timestamp.now().to_datetime64(),
244+
pd.NaT,
245+
],
246+
)
247+
@pytest.mark.filterwarnings("ignore:elementwise comp:DeprecationWarning")
248+
def test_add_sub_datetimelike_invalid(self, numeric_idx, other, box):
249+
# GH#28080 numeric+datetime64 should raise; Timestamp raises
250+
# NullFrequencyError instead of TypeError so is excluded.
251+
left = tm.box_expected(numeric_idx, box)
252+
253+
with pytest.raises(TypeError):
254+
left + other
255+
with pytest.raises(TypeError):
256+
other + left
257+
with pytest.raises(TypeError):
258+
left - other
259+
with pytest.raises(TypeError):
260+
other - left
261+
238262

239263
# ------------------------------------------------------------------
240264
# Arithmetic

0 commit comments

Comments
 (0)