diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 448ceffdaa1eb..13b4a05912462 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -972,6 +972,7 @@ Datetimelike - Bug in constructing arrays with :class:`ArrowDtype` with ``timestamp`` type incorrectly allowing ``Decimal("NaN")`` (:issue:`61773`) - Bug in constructing arrays with a timezone-aware :class:`ArrowDtype` from timezone-naive datetime objects incorrectly treating those as UTC times instead of wall times like :class:`DatetimeTZDtype` (:issue:`61775`) - Bug in setting scalar values with mismatched resolution into arrays with non-nanosecond ``datetime64``, ``timedelta64`` or :class:`DatetimeTZDtype` incorrectly truncating those scalars (:issue:`56410`) +- Bug in :class:`CustomBusinessDay` where calendar parameter validation was not enforced, now requires ``numpy.busdaycalendar`` objects (:issue:`60647`) Timedelta diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index a44d819c7899a..507c56d7cacee 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -216,6 +216,16 @@ cdef _get_calendar(weekmask, holidays, calendar): pass return calendar, holidays + # GH#60647 + # Enforce that when a calendar is provided, it must be a numpy.busdaycalendar + if calendar is not None and 'pandas_market_calendars' in str(type(calendar)): + raise TypeError( + "CustomBusinessDay.calendar must be a numpy.busdaycalendar; " + f"got {type(calendar).__name__}. " + "For pandas_market_calendars please pass the result of " + ".holidays() (a DatetimeIndex) instead." + ) + if holidays is None: holidays = [] try: diff --git a/pandas/tests/tseries/offsets/test_custom_business_day.py b/pandas/tests/tseries/offsets/test_custom_business_day.py index d2f309dd3f33c..b6d71e643851d 100644 --- a/pandas/tests/tseries/offsets/test_custom_business_day.py +++ b/pandas/tests/tseries/offsets/test_custom_business_day.py @@ -82,6 +82,22 @@ def test_calendar(self): dt = datetime(2014, 1, 17) assert_offset_equal(CDay(calendar=calendar), dt, datetime(2014, 1, 21)) + def test_calendar_validation(self): + """Test CustomBusinessDay calendar parameter validation (GH#60647)""" + # Valid numpy.busdaycalendar should work + calendar = np.busdaycalendar(weekmask="1111100") # Monday-Friday + cbd = CDay(calendar=calendar) + assert cbd.calendar is calendar + + # Test error message for pandas_market_calendars scenario + class pandas_market_calendars: + def __init__(self): + self.name = "pandas_market_calendars" + + mock_calendar = pandas_market_calendars() + with pytest.raises(TypeError): + CDay(calendar=mock_calendar) + def test_roundtrip_pickle(self, offset, offset2): def _check_roundtrip(obj): unpickled = tm.round_trip_pickle(obj)