diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 14b4df286d989..dfea3d450fa8a 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -353,7 +353,7 @@ Datetimelike - Bug in :func:`to_datetime` was raising on invalid offsets with ``errors='coerce'`` and ``infer_datetime_format=True`` (:issue:`48633`) - Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`) - Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`48818`) -- +- Bug in ``pandas.tseries.holiday.Holiday`` where a half-open date interval causes inconsistent return types from :meth:`USFederalHolidayCalendar.holidays` (:issue:`49075`) Timedelta ^^^^^^^^^ diff --git a/pandas/tests/tseries/holiday/test_federal.py b/pandas/tests/tseries/holiday/test_federal.py index 64c60d4e365e6..2565877f8a2a4 100644 --- a/pandas/tests/tseries/holiday/test_federal.py +++ b/pandas/tests/tseries/holiday/test_federal.py @@ -1,7 +1,11 @@ from datetime import datetime +from pandas import DatetimeIndex +import pandas._testing as tm + from pandas.tseries.holiday import ( AbstractHolidayCalendar, + USFederalHolidayCalendar, USMartinLutherKingJr, USMemorialDay, ) @@ -36,3 +40,19 @@ class MemorialDay(AbstractHolidayCalendar): datetime(1978, 5, 29, 0, 0), datetime(1979, 5, 28, 0, 0), ] + + +def test_federal_holiday_inconsistent_returntype(): + # GH 49075 test case + # Instantiate two calendars to rule out _cache + cal1 = USFederalHolidayCalendar() + cal2 = USFederalHolidayCalendar() + + results_2018 = cal1.holidays(start=datetime(2018, 8, 1), end=datetime(2018, 8, 31)) + results_2019 = cal2.holidays(start=datetime(2019, 8, 1), end=datetime(2019, 8, 31)) + expected_results = DatetimeIndex([], dtype="datetime64[ns]", freq=None) + + # Check against expected results to ensure both date + # ranges generate expected results as per GH49075 submission + tm.assert_index_equal(results_2018, expected_results) + tm.assert_index_equal(results_2019, expected_results) diff --git a/pandas/tests/tseries/holiday/test_holiday.py b/pandas/tests/tseries/holiday/test_holiday.py index cefb2f86703b2..ee83ca144d38a 100644 --- a/pandas/tests/tseries/holiday/test_holiday.py +++ b/pandas/tests/tseries/holiday/test_holiday.py @@ -3,6 +3,7 @@ import pytest from pytz import utc +from pandas import DatetimeIndex import pandas._testing as tm from pandas.tseries.holiday import ( @@ -264,3 +265,49 @@ def test_both_offset_observance_raises(): offset=[DateOffset(weekday=SA(4))], observance=next_monday, ) + + +def test_half_open_interval_with_observance(): + # Prompted by GH 49075 + # Check for holidays that have a half-open date interval where + # they have either a start_date or end_date defined along + # with a defined observance pattern to make sure that the return type + # for Holiday.dates() remains consistent before & after the year that + # marks the 'edge' of the half-open date interval. + + holiday_1 = Holiday( + "Arbitrary Holiday - start 2022-03-14", + start_date=datetime(2022, 3, 14), + month=3, + day=14, + observance=next_monday, + ) + holiday_2 = Holiday( + "Arbitrary Holiday 2 - end 2022-03-20", + end_date=datetime(2022, 3, 20), + month=3, + day=20, + observance=next_monday, + ) + + class TestHolidayCalendar(AbstractHolidayCalendar): + rules = [ + USMartinLutherKingJr, + holiday_1, + holiday_2, + USLaborDay, + ] + + start = Timestamp("2022-08-01") + end = Timestamp("2022-08-31") + year_offset = DateOffset(years=5) + expected_results = DatetimeIndex([], dtype="datetime64[ns]", freq=None) + test_cal = TestHolidayCalendar() + + date_interval_low = test_cal.holidays(start - year_offset, end - year_offset) + date_window_edge = test_cal.holidays(start, end) + date_interval_high = test_cal.holidays(start + year_offset, end + year_offset) + + tm.assert_index_equal(date_interval_low, expected_results) + tm.assert_index_equal(date_window_edge, expected_results) + tm.assert_index_equal(date_interval_high, expected_results) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index cb65fc958414f..0583b714ea101 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -335,6 +335,9 @@ def _apply_rule(self, dates): ------- Dates with rules applied """ + if dates.empty: + return DatetimeIndex([]) + if self.observance is not None: return dates.map(lambda d: self.observance(d)) @@ -553,8 +556,7 @@ def merge(self, other, inplace: bool = False): class USFederalHolidayCalendar(AbstractHolidayCalendar): """ US Federal Government Holiday Calendar based on rules specified by: - https://www.opm.gov/policy-data-oversight/ - snow-dismissal-procedures/federal-holidays/ + https://www.opm.gov/policy-data-oversight/pay-leave/federal-holidays/ """ rules = [