Skip to content

Commit 9a1b2b4

Browse files
committed
BUG: Fix DateOffsets to return NotImplemented when types do not match
Enable test cases that involve these DateOffsets with `__r*` ops
1 parent 1ec308d commit 9a1b2b4

File tree

2 files changed

+28
-21
lines changed

2 files changed

+28
-21
lines changed

pandas/tests/test_series.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -2187,23 +2187,22 @@ def test_operators_timedelta64(self):
21872187
def test_timedeltas_with_DateOffset(self):
21882188

21892189
# GH 4532
2190-
# commented out cases are problematic b/c of issues with Offsets
21912190
# operate with pd.offsets
21922191
s = Series([Timestamp('20130101 9:01'), Timestamp('20130101 9:02')])
21932192

21942193
result = s + pd.offsets.Second(5)
2195-
# result2 = pd.offsets.Second(5) + s
2194+
result2 = pd.offsets.Second(5) + s
21962195
expected = Series(
21972196
[Timestamp('20130101 9:01:05'), Timestamp('20130101 9:02:05')])
21982197
assert_series_equal(result, expected)
2199-
# assert_series_equal(result2, expected)
2198+
assert_series_equal(result2, expected)
22002199

22012200
result = s + pd.offsets.Milli(5)
2202-
# result2 = pd.offsets.Milli(5) + s
2201+
result2 = pd.offsets.Milli(5) + s
22032202
expected = Series(
22042203
[Timestamp('20130101 9:01:00.005'), Timestamp('20130101 9:02:00.005')])
22052204
assert_series_equal(result, expected)
2206-
# assert_series_equal(result2, expected)
2205+
assert_series_equal(result2, expected)
22072206

22082207
result = s + pd.offsets.Minute(5) + pd.offsets.Milli(5)
22092208
expected = Series(
@@ -2232,8 +2231,7 @@ def test_timedeltas_with_DateOffset(self):
22322231
'Milli', 'Nano' ]:
22332232
op = getattr(pd.offsets,do)
22342233
s + op(5)
2235-
# can't do this because DateOffset doesn't do the right thing
2236-
# op(5) + s
2234+
op(5) + s
22372235

22382236
# invalid DateOffsets
22392237
for do in [ 'Week', 'BDay', 'BQuarterEnd', 'BMonthEnd', 'BYearEnd',

pandas/tseries/offsets.py

+23-14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#----------------------------------------------------------------------
2020
# DateOffset
2121

22+
class ApplyTypeError(TypeError):
23+
# sentinel class for catching the apply error to return NotImplemented
24+
pass
25+
2226

2327
class CacheableOffset(object):
2428

@@ -128,15 +132,15 @@ def __repr__(self):
128132
kwds_new[key] = self.kwds[key]
129133
if len(kwds_new) > 0:
130134
attrs.append('='.join((attr, repr(kwds_new))))
131-
else:
135+
else:
132136
if attr not in exclude:
133137
attrs.append('='.join((attr, repr(getattr(self, attr)))))
134138

135139
if abs(self.n) != 1:
136140
plural = 's'
137141
else:
138142
plural = ''
139-
143+
140144
n_str = ""
141145
if self.n != 1:
142146
n_str = "%s * " % self.n
@@ -170,19 +174,21 @@ def __call__(self, other):
170174
return self.apply(other)
171175

172176
def __add__(self, other):
173-
return self.apply(other)
177+
try:
178+
return self.apply(other)
179+
except ApplyTypeError:
180+
return NotImplemented
174181

175182
def __radd__(self, other):
176183
return self.__add__(other)
177184

178185
def __sub__(self, other):
179186
if isinstance(other, datetime):
180-
raise TypeError('Cannot subtract datetime from offset!')
187+
raise TypeError('Cannot subtract datetime from offset.')
181188
elif type(other) == type(self):
182189
return self.__class__(self.n - other.n, **self.kwds)
183190
else: # pragma: no cover
184-
raise TypeError('Cannot subtract %s from %s'
185-
% (type(other), type(self)))
191+
return NotImplemented
186192

187193
def __rsub__(self, other):
188194
return self.__class__(-self.n, **self.kwds) + other
@@ -273,7 +279,7 @@ def __repr__(self): #TODO: Figure out if this should be merged into DateOffset
273279
plural = 's'
274280
else:
275281
plural = ''
276-
282+
277283
n_str = ""
278284
if self.n != 1:
279285
n_str = "%s * " % self.n
@@ -370,8 +376,8 @@ def apply(self, other):
370376
return BDay(self.n, offset=self.offset + other,
371377
normalize=self.normalize)
372378
else:
373-
raise TypeError('Only know how to combine business day with '
374-
'datetime or timedelta!')
379+
raise ApplyTypeError('Only know how to combine business day with '
380+
'datetime or timedelta.')
375381

376382
@classmethod
377383
def onOffset(cls, dt):
@@ -463,8 +469,8 @@ def apply(self, other):
463469
return BDay(self.n, offset=self.offset + other,
464470
normalize=self.normalize)
465471
else:
466-
raise TypeError('Only know how to combine trading day with '
467-
'datetime, datetime64 or timedelta!')
472+
raise ApplyTypeError('Only know how to combine trading day with '
473+
'datetime, datetime64 or timedelta.')
468474
dt64 = self._to_dt64(other)
469475

470476
day64 = dt64.astype('datetime64[D]')
@@ -1177,7 +1183,10 @@ def __add__(self, other):
11771183
return type(self)(self.n + other.n)
11781184
else:
11791185
return _delta_to_tick(self.delta + other.delta)
1180-
return self.apply(other)
1186+
try:
1187+
return self.apply(other)
1188+
except ApplyTypeError:
1189+
return NotImplemented
11811190

11821191
def __eq__(self, other):
11831192
if isinstance(other, compat.string_types):
@@ -1220,8 +1229,8 @@ def apply(self, other):
12201229
return other + self.delta
12211230
elif isinstance(other, type(self)):
12221231
return type(self)(self.n + other.n)
1223-
else: # pragma: no cover
1224-
raise TypeError('Unhandled type: %s' % type(other))
1232+
else:
1233+
raise ApplyTypeError('Unhandled type: %s' % type(other).__name__)
12251234

12261235
_rule_base = 'undefined'
12271236

0 commit comments

Comments
 (0)