From a61c1473de5d8038d80d3ddc94025d45de620237 Mon Sep 17 00:00:00 2001 From: John Hendricks Date: Mon, 7 Apr 2025 20:45:02 -0400 Subject: [PATCH 1/4] raise TypeError in CustomBusinessDay constructor --- pandas/_libs/tslibs/offsets.pyx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index a16964435ef50..da4342cde2c3c 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4666,6 +4666,11 @@ cdef class CustomBusinessDay(BusinessDay): offset=timedelta(0), ): BusinessDay.__init__(self, n, normalize, offset) + if not isinstance(calendar, (None, np.busdaycalendar)): + raise TypeError( + f"Only np.busdaycalendar is supported for calendar, " + f"got {type(calendar).__name__} instead" + ) self._init_custom(weekmask, holidays, calendar) cpdef __setstate__(self, state): @@ -5108,8 +5113,8 @@ def _warn_about_deprecated_aliases(name: str, is_period: bool) -> str: warnings.warn( f"\'{name}\' is deprecated and will be removed " f"in a future version, please use " - f"\'{c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name)}\'" - f" instead.", + f"\'{c_PERIOD_AND_OFFSET_DEPR_FREQSTR.get(name)}\' " + f"instead.", FutureWarning, stacklevel=find_stack_level(), ) @@ -5122,8 +5127,8 @@ def _warn_about_deprecated_aliases(name: str, is_period: bool) -> str: warnings.warn( f"\'{name}\' is deprecated and will be removed " f"in a future version, please use " - f"\'{_name}\'" - f" instead.", + f"\'{_name}\' " + f"instead.", FutureWarning, stacklevel=find_stack_level(), ) From 32cbbc28cee1c61e0a685316b9acbb67a46bf785 Mon Sep 17 00:00:00 2001 From: John Hendricks Date: Mon, 7 Apr 2025 21:31:19 -0400 Subject: [PATCH 2/4] Added test and whatsnew --- doc/source/whatsnew/v3.0.0.rst | 1 + .../tseries/offsets/test_custom_business_day.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 29be9a7341f00..797e8e95f7ee4 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -476,6 +476,7 @@ Other Removals - Disallow indexing an :class:`Index` with a boolean indexer of length zero, it now raises ``ValueError`` (:issue:`55820`) - Disallow non-standard (``np.ndarray``, :class:`Index`, :class:`ExtensionArray`, or :class:`Series`) to :func:`isin`, :func:`unique`, :func:`factorize` (:issue:`52986`) - Disallow passing a pandas type to :meth:`Index.view` (:issue:`55709`) +- Disallow passing objects other than ``np.busdaycalendar`` to ``calendar`` parameter in :class:`CustomBusinessDay` (:issue:`60647`) - Disallow units other than "s", "ms", "us", "ns" for datetime64 and timedelta64 dtypes in :func:`array` (:issue:`53817`) - Removed "freq" keyword from :class:`PeriodArray` constructor, use "dtype" instead (:issue:`52462`) - Removed 'fastpath' keyword in :class:`Categorical` constructor (:issue:`20110`) diff --git a/pandas/tests/tseries/offsets/test_custom_business_day.py b/pandas/tests/tseries/offsets/test_custom_business_day.py index d2f309dd3f33c..b91e3386a5256 100644 --- a/pandas/tests/tseries/offsets/test_custom_business_day.py +++ b/pandas/tests/tseries/offsets/test_custom_business_day.py @@ -10,7 +10,11 @@ import numpy as np import pytest -from pandas._libs.tslibs.offsets import CDay +from pandas._libs.tslibs.offsets import ( + BDay, + CDay, + CustomBusinessDay, +) from pandas import ( _testing as tm, @@ -97,3 +101,9 @@ def test_pickle_compat_0_14_1(self, datapath): cday0_14_1 = read_pickle(pth) cday = CDay(holidays=hdays) assert cday == cday0_14_1 + + def test_type_error_calendar(self): + bd = BDay(1) + msg = "Only np.busdaycalendar is supported for calendar" + with pytest.raises(TypeError, match=msg): + CustomBusinessDay(calendar=bd) From e911f602cb5c1eed4a3255bd15602f0e52ab683e Mon Sep 17 00:00:00 2001 From: John Hendricks Date: Tue, 8 Apr 2025 01:46:20 -0400 Subject: [PATCH 3/4] fixed TypeError in isinstance --- pandas/_libs/tslibs/offsets.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index da4342cde2c3c..a061a3d508c5c 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4666,7 +4666,7 @@ cdef class CustomBusinessDay(BusinessDay): offset=timedelta(0), ): BusinessDay.__init__(self, n, normalize, offset) - if not isinstance(calendar, (None, np.busdaycalendar)): + if calendar is not None and not isinstance(calendar, np.busdaycalendar): raise TypeError( f"Only np.busdaycalendar is supported for calendar, " f"got {type(calendar).__name__} instead" From 0707478eca566eded9ae50c47f219bf302d93d30 Mon Sep 17 00:00:00 2001 From: John Hendricks Date: Tue, 8 Apr 2025 08:35:20 -0400 Subject: [PATCH 4/4] fixed type errors in tests --- doc/source/user_guide/timeseries.rst | 11 ++++++----- pandas/_libs/tslibs/offsets.pyx | 10 ++++++++++ pandas/tests/tseries/holiday/test_calendar.py | 7 ++----- .../tests/tseries/offsets/test_custom_business_day.py | 4 +--- .../tseries/offsets/test_custom_business_hour.py | 5 ++--- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/doc/source/user_guide/timeseries.rst b/doc/source/user_guide/timeseries.rst index 10260cb011d90..43948bbdcb678 100644 --- a/doc/source/user_guide/timeseries.rst +++ b/doc/source/user_guide/timeseries.rst @@ -1065,9 +1065,9 @@ Holiday calendars can be used to provide the list of holidays. See the .. ipython:: python - from pandas.tseries.holiday import USFederalHolidayCalendar + calendar = np.busdaycalendar(holidays=['2014-01-01', '2014-01-20']) - bday_us = pd.offsets.CustomBusinessDay(calendar=USFederalHolidayCalendar()) + bday_us = pd.offsets.CustomBusinessDay(calendar=calendar) # Friday before MLK Day dt = datetime.datetime(2014, 1, 17) @@ -1080,7 +1080,8 @@ in the usual way. .. ipython:: python - bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=USFederalHolidayCalendar()) + bdd = np.busdaycalendar(holidays=['2011-07-01', '2011-07-04', '2011-07-17']) + bmth_us = pd.offsets.CustomBusinessMonthBegin(calendar=bdd) # Skip new years dt = datetime.datetime(2013, 12, 17) @@ -1210,9 +1211,9 @@ as ``BusinessHour`` except that it skips specified custom holidays. .. ipython:: python - from pandas.tseries.holiday import USFederalHolidayCalendar + calendar = np.busdaycalendar(holidays=['2014-01-01', '2014-01-20']) - bhour_us = pd.offsets.CustomBusinessHour(calendar=USFederalHolidayCalendar()) + bhour_us = pd.offsets.CustomBusinessHour(calendar=calendar) # Friday before MLK Day dt = datetime.datetime(2014, 1, 17, 15) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index a061a3d508c5c..4f87a76e5e487 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -4827,6 +4827,11 @@ cdef class CustomBusinessHour(BusinessHour): offset=timedelta(0), ): BusinessHour.__init__(self, n, normalize, start=start, end=end, offset=offset) + if calendar is not None and not isinstance(calendar, np.busdaycalendar): + raise TypeError( + f"Only np.busdaycalendar is supported for calendar, " + f"got {type(calendar).__name__} instead" + ) self._init_custom(weekmask, holidays, calendar) @@ -4845,6 +4850,11 @@ cdef class _CustomBusinessMonth(BusinessMixin): offset=timedelta(0), ): BusinessMixin.__init__(self, n, normalize, offset) + if calendar is not None and not isinstance(calendar, np.busdaycalendar): + raise TypeError( + f"Only np.busdaycalendar is supported for calendar, " + f"got {type(calendar).__name__} instead" + ) self._init_custom(weekmask, holidays, calendar) @cache_readonly diff --git a/pandas/tests/tseries/holiday/test_calendar.py b/pandas/tests/tseries/holiday/test_calendar.py index 90e2e117852a2..864bbe7ef3feb 100644 --- a/pandas/tests/tseries/holiday/test_calendar.py +++ b/pandas/tests/tseries/holiday/test_calendar.py @@ -1,5 +1,6 @@ from datetime import datetime +import numpy as np import pytest from pandas import ( @@ -14,7 +15,6 @@ Holiday, Timestamp, USFederalHolidayCalendar, - USLaborDay, USThanksgivingDay, get_calendar, ) @@ -97,10 +97,7 @@ def test_calendar_2031(): # Labor Day 2031 is on September 1. Saturday before is August 30. # Next working day after August 30 ought to be Tuesday, September 2. - class testCalendar(AbstractHolidayCalendar): - rules = [USLaborDay] - - cal = testCalendar() + cal = np.busdaycalendar(holidays=["2031-09-01"]) workDay = offsets.CustomBusinessDay(calendar=cal) Sat_before_Labor_Day_2031 = to_datetime("2031-08-30") next_working_day = Sat_before_Labor_Day_2031 + 0 * workDay diff --git a/pandas/tests/tseries/offsets/test_custom_business_day.py b/pandas/tests/tseries/offsets/test_custom_business_day.py index b91e3386a5256..4b16d6ba3d87a 100644 --- a/pandas/tests/tseries/offsets/test_custom_business_day.py +++ b/pandas/tests/tseries/offsets/test_custom_business_day.py @@ -22,8 +22,6 @@ ) from pandas.tests.tseries.offsets.common import assert_offset_equal -from pandas.tseries.holiday import USFederalHolidayCalendar - @pytest.fixture def offset(): @@ -82,7 +80,7 @@ def test_weekmask_and_holidays(self): @pytest.mark.filterwarnings("ignore:Non:pandas.errors.PerformanceWarning") def test_calendar(self): - calendar = USFederalHolidayCalendar() + calendar = np.busdaycalendar(holidays=["2014-01-01", "2014-01-20"]) dt = datetime(2014, 1, 17) assert_offset_equal(CDay(calendar=calendar), dt, datetime(2014, 1, 21)) diff --git a/pandas/tests/tseries/offsets/test_custom_business_hour.py b/pandas/tests/tseries/offsets/test_custom_business_hour.py index 360ed70fa5b9e..236abe63dc11a 100644 --- a/pandas/tests/tseries/offsets/test_custom_business_hour.py +++ b/pandas/tests/tseries/offsets/test_custom_business_hour.py @@ -21,8 +21,6 @@ from pandas.tests.tseries.offsets.common import assert_offset_equal -from pandas.tseries.holiday import USFederalHolidayCalendar - holidays = ["2014-06-27", datetime(2014, 6, 30), np.datetime64("2014-07-02")] @@ -299,7 +297,8 @@ def test_apply_nanoseconds(self, nano_case): def test_us_federal_holiday_with_datetime(self): # GH 16867 - bhour_us = CustomBusinessHour(calendar=USFederalHolidayCalendar()) + calendar = np.busdaycalendar(holidays=["2014-01-01", "2014-01-20"]) + bhour_us = CustomBusinessHour(calendar=calendar) t0 = datetime(2014, 1, 17, 15) result = t0 + bhour_us * 8 expected = Timestamp("2014-01-21 15:00:00")