From 50ddc3feb9825ba6a85b86e56ac83bce96683e99 Mon Sep 17 00:00:00 2001 From: jreback Date: Thu, 9 May 2013 10:28:57 -0400 Subject: [PATCH] BUG: raise on invalid operations for timedelta/datetime e.g. can't add 2 datetimes, nor multiple timedelta * datetime BUG: added correct div operaters and don't fail in py3 --- RELEASE.rst | 2 ++ pandas/core/series.py | 13 +++++++++- pandas/tests/test_series.py | 48 +++++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/RELEASE.rst b/RELEASE.rst index 7c9982961c01f..ece8de259021f 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -94,6 +94,8 @@ pandas 0.11.1 - Fixed bug in selecting month/quarter/year from a series would not select the time element on the last day (GH3546_) - Properly convert np.datetime64 objects in a Series (GH3416_) + - Raise a TypeError on invalid datetime/timedelta operations + e.g. add datetimes, multiple timedelta x datetime .. _GH3164: https://github.com/pydata/pandas/issues/3164 .. _GH2786: https://github.com/pydata/pandas/issues/2786 diff --git a/pandas/core/series.py b/pandas/core/series.py index 7c0c12c11e177..d8e7ea74e0560 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -75,7 +75,7 @@ def na_op(x, y): return result - def wrapper(self, other): + def wrapper(self, other, name=name): from pandas.core.frame import DataFrame dtype = None wrap_results = lambda x: x @@ -123,6 +123,13 @@ def convert_to_array(values): # 2 datetimes or 2 timedeltas if (is_timedelta_lhs and is_timedelta_rhs) or (is_datetime_lhs and is_datetime_rhs): + if is_datetime_lhs and name not in ['__sub__']: + raise TypeError("can only operate on a datetimes for subtraction, " + "but the operator [%s] was passed" % name) + elif is_timedelta_lhs and name not in ['__add__','__sub__']: + raise TypeError("can only operate on a timedeltas for " + "addition and subtraction, but the operator [%s] was passed" % name) + dtype = 'timedelta64[ns]' # we may have to convert to object unfortunately here @@ -135,6 +142,10 @@ def wrap_results(x): # datetime and timedelta elif (is_timedelta_lhs and is_datetime_rhs) or (is_timedelta_rhs and is_datetime_lhs): + + if name not in ['__add__','__sub__']: + raise TypeError("can only operate on a timedelta and a datetime for " + "addition and subtraction, but the operator [%s] was passed" % name) dtype = 'M8[ns]' else: diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 78e2cef230e24..981d74d8ba94b 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -1889,26 +1889,14 @@ def test_operators_timedelta64(self): assert_series_equal(result,expected) self.assert_(result.dtype=='m8[ns]') - result = df['A'] + datetime(2001,1,1) - expected = Series([timedelta(days=26663+i) for i in range(3)]) - assert_series_equal(result,expected) - self.assert_(result.dtype=='m8[ns]') - d = datetime(2001,1,1,3,4) resulta = df['A'] - d self.assert_(resulta.dtype=='m8[ns]') - resultb = df['A'] + d - self.assert_(resultb.dtype=='m8[ns]') - # roundtrip resultb = resulta + d assert_series_equal(df['A'],resultb) - # timedelta on lhs - result = resultb + d - self.assert_(result.dtype=='m8[ns]') - # timedeltas on rhs td = timedelta(days=1) resulta = df['A'] + td @@ -1931,6 +1919,42 @@ def test_operators_timedelta64(self): self.assert_(result.dtype=='m8[ns]') assert_series_equal(result,expected) + def test_operators_datetimelike(self): + + ### timedelta64 ### + td1 = Series([timedelta(minutes=5,seconds=3)]*3) + td2 = timedelta(minutes=5,seconds=4) + for op in ['__mul__','__floordiv__','__truediv__','__div__','__pow__']: + op = getattr(td1,op,None) + if op is not None: + self.assertRaises(TypeError, op, td2) + td1 + td2 + td1 - td2 + + ### datetime64 ### + dt1 = Series([Timestamp('20111230'),Timestamp('20120101'),Timestamp('20120103')]) + dt2 = Series([Timestamp('20111231'),Timestamp('20120102'),Timestamp('20120104')]) + for op in ['__add__','__mul__','__floordiv__','__truediv__','__div__','__pow__']: + op = getattr(dt1,op,None) + if op is not None: + self.assertRaises(TypeError, op, dt2) + dt1 - dt2 + + ### datetime64 with timetimedelta ### + for op in ['__mul__','__floordiv__','__truediv__','__div__','__pow__']: + op = getattr(dt1,op,None) + if op is not None: + self.assertRaises(TypeError, op, td1) + dt1 + td1 + dt1 - td1 + + ### timetimedelta with datetime64 ### + for op in ['__mul__','__floordiv__','__truediv__','__div__','__pow__']: + op = getattr(td1,op,None) + if op is not None: + self.assertRaises(TypeError, op, dt1) + td1 + dt1 + td1 - dt1 def test_timedelta64_functions(self):