diff --git a/doc/source/whatsnew/v0.18.2.txt b/doc/source/whatsnew/v0.18.2.txt index a5734dc1db200..26d77be98b303 100644 --- a/doc/source/whatsnew/v0.18.2.txt +++ b/doc/source/whatsnew/v0.18.2.txt @@ -86,7 +86,6 @@ Bug Fixes - - Bug in ``PeriodIndex`` and ``Period`` subtraction raises ``AttributeError`` (:issue:`13071`) @@ -103,3 +102,4 @@ Bug Fixes - Bug in ``NaT`` - ``Period`` raises ``AttributeError`` (:issue:`13071`) +- Bug in ``Period`` addition raises ``TypeError`` if ``Period`` is on right hand side (:issue:`13069`) diff --git a/pandas/src/period.pyx b/pandas/src/period.pyx index 457ca2b8ec842..0cb0b575b25dc 100644 --- a/pandas/src/period.pyx +++ b/pandas/src/period.pyx @@ -813,16 +813,23 @@ cdef class Period(object): return NotImplemented def __add__(self, other): - if isinstance(other, (timedelta, np.timedelta64, - offsets.Tick, offsets.DateOffset, Timedelta)): - return self._add_delta(other) - elif lib.is_integer(other): - if self.ordinal == tslib.iNaT: - ordinal = self.ordinal - else: - ordinal = self.ordinal + other * self.freq.n - return Period(ordinal=ordinal, freq=self.freq) - else: # pragma: no cover + if isinstance(self, Period): + if isinstance(other, (timedelta, np.timedelta64, + offsets.Tick, offsets.DateOffset, Timedelta)): + return self._add_delta(other) + elif other is tslib.NaT: + return tslib.NaT + elif lib.is_integer(other): + if self.ordinal == tslib.iNaT: + ordinal = self.ordinal + else: + ordinal = self.ordinal + other * self.freq.n + return Period(ordinal=ordinal, freq=self.freq) + else: # pragma: no cover + return NotImplemented + elif isinstance(other, Period): + return other + self + else: return NotImplemented def __sub__(self, other): diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 6e1ab20fa6560..7d3255add4f64 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -4,7 +4,7 @@ import numpy as np from pandas.tseries.tools import to_datetime, normalize_date -from pandas.core.common import ABCSeries, ABCDatetimeIndex +from pandas.core.common import ABCSeries, ABCDatetimeIndex, ABCPeriod # import after tools, dateutil check from dateutil.relativedelta import relativedelta, weekday @@ -381,6 +381,8 @@ def __call__(self, other): def __add__(self, other): if isinstance(other, (ABCDatetimeIndex, ABCSeries)): return other + self + elif isinstance(other, ABCPeriod): + return other + self try: return self.apply(other) except ApplyTypeError: @@ -2489,6 +2491,8 @@ def __add__(self, other): return type(self)(self.n + other.n) else: return _delta_to_tick(self.delta + other.delta) + elif isinstance(other, ABCPeriod): + return other + self try: return self.apply(other) except ApplyTypeError: diff --git a/pandas/tseries/tests/test_period.py b/pandas/tseries/tests/test_period.py index debb3e7956488..d96af0bf78863 100644 --- a/pandas/tseries/tests/test_period.py +++ b/pandas/tseries/tests/test_period.py @@ -87,22 +87,26 @@ def test_period_cons_nat(self): self.assertEqual(p.ordinal, tslib.iNaT) self.assertEqual(p.freq, 'M') self.assertEqual((p + 1).ordinal, tslib.iNaT) + self.assertEqual((1 + p).ordinal, tslib.iNaT) p = Period('nat', freq='W-SUN') self.assertEqual(p.ordinal, tslib.iNaT) self.assertEqual(p.freq, 'W-SUN') self.assertEqual((p + 1).ordinal, tslib.iNaT) + self.assertEqual((1 + p).ordinal, tslib.iNaT) p = Period(tslib.iNaT, freq='D') self.assertEqual(p.ordinal, tslib.iNaT) self.assertEqual(p.freq, 'D') self.assertEqual((p + 1).ordinal, tslib.iNaT) + self.assertEqual((1 + p).ordinal, tslib.iNaT) p = Period(tslib.iNaT, freq='3D') self.assertEqual(p.ordinal, tslib.iNaT) self.assertEqual(p.freq, offsets.Day(3)) self.assertEqual(p.freqstr, '3D') self.assertEqual((p + 1).ordinal, tslib.iNaT) + self.assertEqual((1 + p).ordinal, tslib.iNaT) self.assertRaises(ValueError, Period, 'NaT') @@ -2796,7 +2800,9 @@ def test_multiples(self): self.assertEqual(result2.freq, offsets.YearEnd()) self.assertEqual((result1 + 1).ordinal, result1.ordinal + 2) + self.assertEqual((1 + result1).ordinal, result1.ordinal + 2) self.assertEqual((result1 - 1).ordinal, result2.ordinal - 2) + self.assertEqual((-1 + result1).ordinal, result2.ordinal - 2) def test_pindex_multiples(self): pi = PeriodIndex(start='1/1/11', end='12/31/11', freq='2M') @@ -3290,12 +3296,29 @@ def test_add(self): dt1 = Period(freq='D', year=2008, month=1, day=1) dt2 = Period(freq='D', year=2008, month=1, day=2) assert_equal(dt1 + 1, dt2) - # + assert_equal(1 + dt1, dt2) + + def test_add_pdnat(self): + p = pd.Period('2011-01', freq='M') + self.assertIs(p + pd.NaT, pd.NaT) + self.assertIs(pd.NaT + p, pd.NaT) + + p = pd.Period('NaT', freq='M') + self.assertIs(p + pd.NaT, pd.NaT) + self.assertIs(pd.NaT + p, pd.NaT) + + def test_add_raises(self): # GH 4731 + dt1 = Period(freq='D', year=2008, month=1, day=1) + dt2 = Period(freq='D', year=2008, month=1, day=2) msg = "unsupported operand type\(s\)" with tm.assertRaisesRegexp(TypeError, msg): dt1 + "str" + msg = "unsupported operand type\(s\)" + with tm.assertRaisesRegexp(TypeError, msg): + "str" + dt1 + with tm.assertRaisesRegexp(TypeError, msg): dt1 + dt2 @@ -3303,7 +3326,9 @@ def test_add_offset(self): # freq is DateOffset for freq in ['A', '2A', '3A']: p = Period('2011', freq=freq) - self.assertEqual(p + offsets.YearEnd(2), Period('2013', freq=freq)) + exp = Period('2013', freq=freq) + self.assertEqual(p + offsets.YearEnd(2), exp) + self.assertEqual(offsets.YearEnd(2) + p, exp) for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(365, 'D'), @@ -3311,12 +3336,22 @@ def test_add_offset(self): with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p + for freq in ['M', '2M', '3M']: p = Period('2011-03', freq=freq) - self.assertEqual(p + offsets.MonthEnd(2), - Period('2011-05', freq=freq)) - self.assertEqual(p + offsets.MonthEnd(12), - Period('2012-03', freq=freq)) + exp = Period('2011-05', freq=freq) + self.assertEqual(p + offsets.MonthEnd(2), exp) + self.assertEqual(offsets.MonthEnd(2) + p, exp) + + exp = Period('2012-03', freq=freq) + self.assertEqual(p + offsets.MonthEnd(12), exp) + self.assertEqual(offsets.MonthEnd(12) + p, exp) for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(365, 'D'), @@ -3324,21 +3359,42 @@ def test_add_offset(self): with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p + # freq is Tick for freq in ['D', '2D', '3D']: p = Period('2011-04-01', freq=freq) - self.assertEqual(p + offsets.Day(5), - Period('2011-04-06', freq=freq)) - self.assertEqual(p + offsets.Hour(24), - Period('2011-04-02', freq=freq)) - self.assertEqual(p + np.timedelta64(2, 'D'), - Period('2011-04-03', freq=freq)) - self.assertEqual(p + np.timedelta64(3600 * 24, 's'), - Period('2011-04-02', freq=freq)) - self.assertEqual(p + timedelta(-2), - Period('2011-03-30', freq=freq)) - self.assertEqual(p + timedelta(hours=48), - Period('2011-04-03', freq=freq)) + + exp = Period('2011-04-06', freq=freq) + self.assertEqual(p + offsets.Day(5), exp) + self.assertEqual(offsets.Day(5) + p, exp) + + exp = Period('2011-04-02', freq=freq) + self.assertEqual(p + offsets.Hour(24), exp) + self.assertEqual(offsets.Hour(24) + p, exp) + + exp = Period('2011-04-03', freq=freq) + self.assertEqual(p + np.timedelta64(2, 'D'), exp) + with tm.assertRaises(TypeError): + np.timedelta64(2, 'D') + p + + exp = Period('2011-04-02', freq=freq) + self.assertEqual(p + np.timedelta64(3600 * 24, 's'), exp) + with tm.assertRaises(TypeError): + np.timedelta64(3600 * 24, 's') + p + + exp = Period('2011-03-30', freq=freq) + self.assertEqual(p + timedelta(-2), exp) + self.assertEqual(timedelta(-2) + p, exp) + + exp = Period('2011-04-03', freq=freq) + self.assertEqual(p + timedelta(hours=48), exp) + self.assertEqual(timedelta(hours=48) + p, exp) for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(4, 'h'), @@ -3346,20 +3402,41 @@ def test_add_offset(self): with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p + for freq in ['H', '2H', '3H']: p = Period('2011-04-01 09:00', freq=freq) - self.assertEqual(p + offsets.Day(2), - Period('2011-04-03 09:00', freq=freq)) - self.assertEqual(p + offsets.Hour(3), - Period('2011-04-01 12:00', freq=freq)) - self.assertEqual(p + np.timedelta64(3, 'h'), - Period('2011-04-01 12:00', freq=freq)) - self.assertEqual(p + np.timedelta64(3600, 's'), - Period('2011-04-01 10:00', freq=freq)) - self.assertEqual(p + timedelta(minutes=120), - Period('2011-04-01 11:00', freq=freq)) - self.assertEqual(p + timedelta(days=4, minutes=180), - Period('2011-04-05 12:00', freq=freq)) + + exp = Period('2011-04-03 09:00', freq=freq) + self.assertEqual(p + offsets.Day(2), exp) + self.assertEqual(offsets.Day(2) + p, exp) + + exp = Period('2011-04-01 12:00', freq=freq) + self.assertEqual(p + offsets.Hour(3), exp) + self.assertEqual(offsets.Hour(3) + p, exp) + + exp = Period('2011-04-01 12:00', freq=freq) + self.assertEqual(p + np.timedelta64(3, 'h'), exp) + with tm.assertRaises(TypeError): + np.timedelta64(3, 'h') + p + + exp = Period('2011-04-01 10:00', freq=freq) + self.assertEqual(p + np.timedelta64(3600, 's'), exp) + with tm.assertRaises(TypeError): + np.timedelta64(3600, 's') + p + + exp = Period('2011-04-01 11:00', freq=freq) + self.assertEqual(p + timedelta(minutes=120), exp) + self.assertEqual(timedelta(minutes=120) + p, exp) + + exp = Period('2011-04-05 12:00', freq=freq) + self.assertEqual(p + timedelta(days=4, minutes=180), exp) + self.assertEqual(timedelta(days=4, minutes=180) + p, exp) for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(3200, 's'), @@ -3367,12 +3444,20 @@ def test_add_offset(self): with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p + def test_add_offset_nat(self): # freq is DateOffset for freq in ['A', '2A', '3A']: p = Period('NaT', freq=freq) for o in [offsets.YearEnd(2)]: self.assertEqual((p + o).ordinal, tslib.iNaT) + self.assertEqual((o + p).ordinal, tslib.iNaT) for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(365, 'D'), @@ -3380,17 +3465,36 @@ def test_add_offset_nat(self): with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p + for freq in ['M', '2M', '3M']: p = Period('NaT', freq=freq) for o in [offsets.MonthEnd(2), offsets.MonthEnd(12)]: self.assertEqual((p + o).ordinal, tslib.iNaT) + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + self.assertEqual((o + p).ordinal, tslib.iNaT) + for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(365, 'D'), timedelta(365)]: with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p # freq is Tick for freq in ['D', '2D', '3D']: p = Period('NaT', freq=freq) @@ -3399,12 +3503,26 @@ def test_add_offset_nat(self): timedelta(hours=48)]: self.assertEqual((p + o).ordinal, tslib.iNaT) + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + self.assertEqual((o + p).ordinal, tslib.iNaT) + for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(4, 'h'), timedelta(hours=23)]: + with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p + for freq in ['H', '2H', '3H']: p = Period('NaT', freq=freq) for o in [offsets.Day(2), offsets.Hour(3), np.timedelta64(3, 'h'), @@ -3412,12 +3530,22 @@ def test_add_offset_nat(self): timedelta(days=4, minutes=180)]: self.assertEqual((p + o).ordinal, tslib.iNaT) + if not isinstance(o, np.timedelta64): + self.assertEqual((o + p).ordinal, tslib.iNaT) + for o in [offsets.YearBegin(2), offsets.MonthBegin(1), offsets.Minute(), np.timedelta64(3200, 's'), timedelta(hours=23, minutes=30)]: with tm.assertRaises(ValueError): p + o + if isinstance(o, np.timedelta64): + with tm.assertRaises(TypeError): + o + p + else: + with tm.assertRaises(ValueError): + o + p + def test_sub_pdnat(self): # GH 13071 p = pd.Period('2011-01', freq='M') @@ -3551,6 +3679,7 @@ def test_nat_ops(self): for freq in ['M', '2M', '3M']: p = Period('NaT', freq=freq) self.assertEqual((p + 1).ordinal, tslib.iNaT) + self.assertEqual((1 + p).ordinal, tslib.iNaT) self.assertEqual((p - 1).ordinal, tslib.iNaT) self.assertEqual( (p - Period('2011-01', freq=freq)).ordinal, tslib.iNaT)