From 2d824c8a9972bc6cca72fb50886864e53b1b79d1 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 27 Nov 2017 09:43:28 -0800 Subject: [PATCH 1/2] check for datetime+period addition, closed#17983 --- pandas/_libs/period.pyx | 14 ++++++++++++++ pandas/tests/scalar/test_period.py | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/pandas/_libs/period.pyx b/pandas/_libs/period.pyx index 2b09e9376bd3d..b95632b5b0eff 100644 --- a/pandas/_libs/period.pyx +++ b/pandas/_libs/period.pyx @@ -17,6 +17,10 @@ from pandas.compat import PY2 cimport cython +from cpython.datetime cimport PyDateTime_Check, PyDateTime_IMPORT +# import datetime C API +PyDateTime_IMPORT + from tslibs.np_datetime cimport (pandas_datetimestruct, dtstruct_to_dt64, dt64_to_dtstruct, is_leapyear) @@ -647,9 +651,19 @@ cdef class _Period(object): elif util.is_integer_object(other): ordinal = self.ordinal + other * self.freq.n return Period(ordinal=ordinal, freq=self.freq) + elif (PyDateTime_Check(other) or + is_period_object(other) or util.is_datetime64_object(other)): + # can't add datetime-like + # GH#17983 + sname = type(self).__name__ + oname = type(other).__name__ + raise TypeError("unsupported operand type(s) for +: '{self}' " + "and '{other}'".format(self=sname, + other=oname)) else: # pragma: no cover return NotImplemented elif is_period_object(other): + # this can be reached via __radd__ because of cython rules return other + self else: return NotImplemented diff --git a/pandas/tests/scalar/test_period.py b/pandas/tests/scalar/test_period.py index 8cfdf7a461879..da36729cb734b 100644 --- a/pandas/tests/scalar/test_period.py +++ b/pandas/tests/scalar/test_period.py @@ -1038,6 +1038,24 @@ def test_add_raises(self): with tm.assert_raises_regex(TypeError, msg): dt1 + dt2 + boxes = [lambda x: x, lambda x: pd.Series([x]), lambda x: pd.Index([x])] + + @pytest.mark.parametrize('lbox', boxes) + @pytest.mark.parametrize('rbox', boxes) + def test_add_timestamp_raises(self, rbox, lbox): + # GH # 17983 + ts = pd.Timestamp('2017') + per = pd.Period('2017', freq='M') + + with pytest.raises(TypeError): + lbox(ts) + rbox(per) + + with pytest.raises(TypeError): + lbox(per) + rbox(ts) + + with pytest.raises(TypeError): + lbox(per) + rbox(per) + def test_sub(self): dt1 = Period('2011-01-01', freq='D') dt2 = Period('2011-01-15', freq='D') From c79ba873986f7419f4d3181011461e1112d96380 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 27 Nov 2017 15:11:39 -0800 Subject: [PATCH 2/2] check error message, add whatsnew note --- doc/source/whatsnew/v0.22.0.txt | 1 + pandas/tests/scalar/test_period.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 52ca05d9a76a9..beac39cd7c9a0 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -207,4 +207,5 @@ Other - Improved error message when attempting to use a Python keyword as an identifier in a numexpr query (:issue:`18221`) - Fixed a bug where creating a Series from an array that contains both tz-naive and tz-aware values will result in a Series whose dtype is tz-aware instead of object (:issue:`16406`) +- Adding a ``Period`` object to a ``datetime`` or ``Timestamp`` object will now correctly raise a ``TypeError`` (:issue:`17983`) - diff --git a/pandas/tests/scalar/test_period.py b/pandas/tests/scalar/test_period.py index da36729cb734b..3bd4a28b7767d 100644 --- a/pandas/tests/scalar/test_period.py +++ b/pandas/tests/scalar/test_period.py @@ -1047,13 +1047,18 @@ def test_add_timestamp_raises(self, rbox, lbox): ts = pd.Timestamp('2017') per = pd.Period('2017', freq='M') - with pytest.raises(TypeError): + # We may get a different message depending on which class raises + # the error. + msg = (r"cannot add|unsupported operand|" + r"can only operate on a|incompatible type|" + r"ufunc add cannot use operands") + with tm.assert_raises_regex(TypeError, msg): lbox(ts) + rbox(per) - with pytest.raises(TypeError): + with tm.assert_raises_regex(TypeError, msg): lbox(per) + rbox(ts) - with pytest.raises(TypeError): + with tm.assert_raises_regex(TypeError, msg): lbox(per) + rbox(per) def test_sub(self):