Skip to content

Commit 03cecf3

Browse files
jackzyliufeefladder
authored andcommitted
BUG: CustomBusinessMonthBegin(End) sometimes ignores extra offset (GH41356) (pandas-dev#41488)
1 parent 41da3c9 commit 03cecf3

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

doc/source/whatsnew/v1.4.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ Styler
286286

287287
Other
288288
^^^^^
289+
- Bug in :meth:`CustomBusinessMonthBegin.__add__` (:meth:`CustomBusinessMonthEnd.__add__`) not applying the extra ``offset`` parameter when beginning (end) of the target month is already a business day (:issue:`41356`)
289290

290291
.. ***DO NOT USE THIS SECTION***
291292

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_custom_business_month.py

+105
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from datetime import (
88
date,
99
datetime,
10+
timedelta,
1011
)
1112

1213
import numpy as np
@@ -200,6 +201,59 @@ def test_datetimeindex(self):
200201
0
201202
] == datetime(2012, 1, 3)
202203

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

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

0 commit comments

Comments
 (0)