Skip to content

Commit d59b061

Browse files
committed
BUG: CustomBusinessMonthBegin(End) sometimes ignores extra offset (GH41356)
CustomBusinessMonthBegin(End) does not apply extra offset when the initially rolled month begin (end) is already a business day.
1 parent 1b60699 commit d59b061

File tree

2 files changed

+112
-5
lines changed

2 files changed

+112
-5
lines changed

pandas/_libs/tslibs/offsets.pyx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3370,7 +3370,10 @@ cdef class _CustomBusinessMonth(BusinessMixin):
33703370
"""
33713371
Define default roll function to be called in apply method.
33723372
"""
3373-
cbday = CustomBusinessDay(n=self.n, normalize=False, **self.kwds)
3373+
cbday_kwds = self.kwds.copy()
3374+
cbday_kwds['offset'] = timedelta(0)
3375+
3376+
cbday = CustomBusinessDay(n=1, normalize=False, **cbday_kwds)
33743377

33753378
if self._prefix.endswith("S"):
33763379
# MonthBegin
@@ -3414,6 +3417,9 @@ cdef class _CustomBusinessMonth(BusinessMixin):
34143417

34153418
new = cur_month_offset_date + n * self.m_offset
34163419
result = self.cbday_roll(new)
3420+
3421+
if self.offset:
3422+
result = result + self.offset
34173423
return result
34183424

34193425

pandas/tests/tseries/offsets/test_custom_business_month.py

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
- CustomBusinessMonthBegin
55
- CustomBusinessMonthEnd
66
"""
7-
from datetime import (
8-
date,
9-
datetime,
10-
)
7+
from datetime import date, datetime, timedelta
118

129
import numpy as np
1310
import pytest
@@ -200,6 +197,59 @@ def test_datetimeindex(self):
200197
0
201198
] == datetime(2012, 1, 3)
202199

200+
@pytest.mark.parametrize(
201+
"case",
202+
[
203+
(
204+
CBMonthBegin(n=1, offset=timedelta(days=5)),
205+
{
206+
datetime(2021, 3, 1): datetime(2021, 4, 1) + timedelta(days=5),
207+
datetime(2021, 4, 17): datetime(2021, 5, 3) + timedelta(days=5),
208+
},
209+
),
210+
(
211+
CBMonthBegin(n=2, offset=timedelta(days=40)),
212+
{
213+
datetime(2021, 3, 10): datetime(2021, 5, 3) + timedelta(days=40),
214+
datetime(2021, 4, 30): datetime(2021, 6, 1) + timedelta(days=40),
215+
},
216+
),
217+
(
218+
CBMonthBegin(n=1, offset=timedelta(days=-5)),
219+
{
220+
datetime(2021, 3, 1): datetime(2021, 4, 1) - timedelta(days=5),
221+
datetime(2021, 4, 11): datetime(2021, 5, 3) - timedelta(days=5),
222+
},
223+
),
224+
(
225+
-2 * CBMonthBegin(n=1, offset=timedelta(days=10)),
226+
{
227+
datetime(2021, 3, 1): datetime(2021, 1, 1) + timedelta(days=10),
228+
datetime(2021, 4, 3): datetime(2021, 3, 1) + timedelta(days=10),
229+
},
230+
),
231+
(
232+
CBMonthBegin(n=0, offset=timedelta(days=1)),
233+
{
234+
datetime(2021, 3, 2): datetime(2021, 4, 1) + timedelta(days=1),
235+
datetime(2021, 4, 1): datetime(2021, 4, 1) + timedelta(days=1),
236+
},
237+
),
238+
(
239+
CBMonthBegin(
240+
n=1, holidays=["2021-04-01", "2021-04-02"], offset=timedelta(days=1)
241+
),
242+
{
243+
datetime(2021, 3, 2): datetime(2021, 4, 5) + timedelta(days=1),
244+
},
245+
),
246+
],
247+
)
248+
def test_apply_with_extra_offset(self, case):
249+
offset, cases = case
250+
for base, expected in cases.items():
251+
assert_offset_equal(offset, base, expected)
252+
203253

204254
class TestCustomBusinessMonthEnd(CustomBusinessMonthBase, Base):
205255
_offset = CBMonthEnd
@@ -337,3 +387,54 @@ def test_datetimeindex(self):
337387
assert date_range(start="20120101", end="20130101", freq=freq).tolist()[
338388
0
339389
] == datetime(2012, 1, 31)
390+
391+
@pytest.mark.parametrize(
392+
"case",
393+
[
394+
(
395+
CBMonthEnd(n=1, offset=timedelta(days=5)),
396+
{
397+
datetime(2021, 3, 1): datetime(2021, 3, 31) + timedelta(days=5),
398+
datetime(2021, 4, 17): datetime(2021, 4, 30) + timedelta(days=5),
399+
},
400+
),
401+
(
402+
CBMonthEnd(n=2, offset=timedelta(days=40)),
403+
{
404+
datetime(2021, 3, 10): datetime(2021, 4, 30) + timedelta(days=40),
405+
datetime(2021, 4, 30): datetime(2021, 6, 30) + timedelta(days=40),
406+
},
407+
),
408+
(
409+
CBMonthEnd(n=1, offset=timedelta(days=-5)),
410+
{
411+
datetime(2021, 3, 1): datetime(2021, 3, 31) - timedelta(days=5),
412+
datetime(2021, 4, 11): datetime(2021, 4, 30) - timedelta(days=5),
413+
},
414+
),
415+
(
416+
-2 * CBMonthEnd(n=1, offset=timedelta(days=10)),
417+
{
418+
datetime(2021, 3, 1): datetime(2021, 1, 29) + timedelta(days=10),
419+
datetime(2021, 4, 3): datetime(2021, 2, 26) + timedelta(days=10),
420+
},
421+
),
422+
(
423+
CBMonthEnd(n=0, offset=timedelta(days=1)),
424+
{
425+
datetime(2021, 3, 2): datetime(2021, 3, 31) + timedelta(days=1),
426+
datetime(2021, 4, 1): datetime(2021, 4, 30) + timedelta(days=1),
427+
},
428+
),
429+
(
430+
CBMonthEnd(n=1, holidays=["2021-03-31"], offset=timedelta(days=1)),
431+
{
432+
datetime(2021, 3, 2): datetime(2021, 3, 30) + timedelta(days=1),
433+
},
434+
),
435+
],
436+
)
437+
def test_apply_with_extra_offset(self, case):
438+
offset, cases = case
439+
for base, expected in cases.items():
440+
assert_offset_equal(offset, base, expected)

0 commit comments

Comments
 (0)