Skip to content

Commit 6a09c29

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 dad3e7f commit 6a09c29

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

pandas/_libs/tslibs/offsets.pyx

+10-1
Original file line numberDiff line numberDiff line change
@@ -3369,7 +3369,13 @@ cdef class _CustomBusinessMonth(BusinessMixin):
33693369
"""
33703370
Define default roll function to be called in apply method.
33713371
"""
3372-
cbday = CustomBusinessDay(n=self.n, normalize=False, **self.kwds)
3372+
if self.offset:
3373+
cbday_kwds = self.kwds.copy()
3374+
cbday_kwds['offset'] = timedelta(0)
3375+
else:
3376+
cbday_kwds = self.kwds
3377+
3378+
cbday = CustomBusinessDay(n=1, normalize=False, **cbday_kwds)
33733379

33743380
if self._prefix.endswith("S"):
33753381
# MonthBegin
@@ -3413,6 +3419,9 @@ cdef class _CustomBusinessMonth(BusinessMixin):
34133419

34143420
new = cur_month_offset_date + n * self.m_offset
34153421
result = self.cbday_roll(new)
3422+
3423+
if self.offset:
3424+
result = result + self.offset
34163425
return result
34173426

34183427

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)