Skip to content

Commit b652550

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 fe934d8 commit b652550

File tree

2 files changed

+112
-1
lines changed

2 files changed

+112
-1
lines changed

pandas/_libs/tslibs/offsets.pyx

+7-1
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_month.py

+105
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from datetime import (
55
date,
66
datetime,
7+
timedelta,
78
)
89

910
import numpy as np
@@ -204,6 +205,57 @@ def test_datetimeindex(self):
204205
0
205206
] == datetime(2012, 1, 31)
206207

208+
@pytest.mark.parametrize(
209+
"case",
210+
[
211+
(
212+
CBMonthEnd(n=1, offset=timedelta(days=5)),
213+
{
214+
datetime(2021, 3, 1): datetime(2021, 3, 31) + timedelta(days=5),
215+
datetime(2021, 4, 17): datetime(2021, 4, 30) + timedelta(days=5),
216+
},
217+
),
218+
(
219+
CBMonthEnd(n=2, offset=timedelta(days=40)),
220+
{
221+
datetime(2021, 3, 10): datetime(2021, 4, 30) + timedelta(days=40),
222+
datetime(2021, 4, 30): datetime(2021, 6, 30) + timedelta(days=40),
223+
},
224+
),
225+
(
226+
CBMonthEnd(n=1, offset=timedelta(days=-5)),
227+
{
228+
datetime(2021, 3, 1): datetime(2021, 3, 31) - timedelta(days=5),
229+
datetime(2021, 4, 11): datetime(2021, 4, 30) - timedelta(days=5),
230+
},
231+
),
232+
(
233+
-2 * CBMonthEnd(n=1, offset=timedelta(days=10)),
234+
{
235+
datetime(2021, 3, 1): datetime(2021, 1, 29) + timedelta(days=10),
236+
datetime(2021, 4, 3): datetime(2021, 2, 26) + timedelta(days=10),
237+
},
238+
),
239+
(
240+
CBMonthEnd(n=0, offset=timedelta(days=1)),
241+
{
242+
datetime(2021, 3, 2): datetime(2021, 3, 31) + timedelta(days=1),
243+
datetime(2021, 4, 1): datetime(2021, 4, 30) + timedelta(days=1),
244+
},
245+
),
246+
(
247+
CBMonthEnd(n=1, holidays=["2021-03-31"], offset=timedelta(days=1)),
248+
{
249+
datetime(2021, 3, 2): datetime(2021, 3, 30) + timedelta(days=1),
250+
},
251+
),
252+
],
253+
)
254+
def test_apply_with_extra_offset(self, case):
255+
offset, cases = case
256+
for base, expected in cases.items():
257+
assert_offset_equal(offset, base, expected)
258+
207259

208260
class TestCustomBusinessMonthBegin(CustomBusinessMonthBase, Base):
209261
_offset = CBMonthBegin
@@ -341,6 +393,59 @@ def test_datetimeindex(self):
341393
0
342394
] == datetime(2012, 1, 3)
343395

396+
@pytest.mark.parametrize(
397+
"case",
398+
[
399+
(
400+
CBMonthBegin(n=1, offset=timedelta(days=5)),
401+
{
402+
datetime(2021, 3, 1): datetime(2021, 4, 1) + timedelta(days=5),
403+
datetime(2021, 4, 17): datetime(2021, 5, 3) + timedelta(days=5),
404+
},
405+
),
406+
(
407+
CBMonthBegin(n=2, offset=timedelta(days=40)),
408+
{
409+
datetime(2021, 3, 10): datetime(2021, 5, 3) + timedelta(days=40),
410+
datetime(2021, 4, 30): datetime(2021, 6, 1) + timedelta(days=40),
411+
},
412+
),
413+
(
414+
CBMonthBegin(n=1, offset=timedelta(days=-5)),
415+
{
416+
datetime(2021, 3, 1): datetime(2021, 4, 1) - timedelta(days=5),
417+
datetime(2021, 4, 11): datetime(2021, 5, 3) - timedelta(days=5),
418+
},
419+
),
420+
(
421+
-2 * CBMonthBegin(n=1, offset=timedelta(days=10)),
422+
{
423+
datetime(2021, 3, 1): datetime(2021, 1, 1) + timedelta(days=10),
424+
datetime(2021, 4, 3): datetime(2021, 3, 1) + timedelta(days=10),
425+
},
426+
),
427+
(
428+
CBMonthBegin(n=0, offset=timedelta(days=1)),
429+
{
430+
datetime(2021, 3, 2): datetime(2021, 4, 1) + timedelta(days=1),
431+
datetime(2021, 4, 1): datetime(2021, 4, 1) + timedelta(days=1),
432+
},
433+
),
434+
(
435+
CBMonthBegin(
436+
n=1, holidays=["2021-04-01", "2021-04-02"], offset=timedelta(days=1)
437+
),
438+
{
439+
datetime(2021, 3, 2): datetime(2021, 4, 5) + timedelta(days=1),
440+
},
441+
),
442+
],
443+
)
444+
def test_apply_with_extra_offset(self, case):
445+
offset, cases = case
446+
for base, expected in cases.items():
447+
assert_offset_equal(offset, base, expected)
448+
344449

345450
class TestSemiMonthEnd(Base):
346451
_offset = SemiMonthEnd

0 commit comments

Comments
 (0)