Skip to content

DOC/ENH: Holiday exclusion argument #61600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Other enhancements
- :meth:`Series.str.decode` result now has :class:`StringDtype` when ``future.infer_string`` is True (:issue:`60709`)
- :meth:`~Series.to_hdf` and :meth:`~DataFrame.to_hdf` now round-trip with :class:`StringDtype` (:issue:`60663`)
- Improved ``repr`` of :class:`.NumpyExtensionArray` to account for NEP51 (:issue:`61085`)
- The :class:`Holiday` has gained the constructor argument and field ``exclude_dates`` to exclude specific datetimes from a custom holiday calendar (:issue:`54382`)
- The :meth:`Series.str.decode` has gained the argument ``dtype`` to control the dtype of the result (:issue:`60940`)
- The :meth:`~Series.cumsum`, :meth:`~Series.cummin`, and :meth:`~Series.cummax` reductions are now implemented for :class:`StringDtype` columns (:issue:`60633`)
- The :meth:`~Series.sum` reduction is now implemented for :class:`StringDtype` columns (:issue:`59853`)
Expand Down
54 changes: 54 additions & 0 deletions pandas/tests/tseries/holiday/test_holiday.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,57 @@ def test_holidays_with_timezone_specified_but_no_occurences():
expected_results.index = expected_results.index.as_unit("ns")

tm.assert_equal(test_case, expected_results)


def test_holiday_with_exclusion():
# GH 54382
start = Timestamp("2020-05-01")
end = Timestamp("2025-05-31")
exclude = [Timestamp("2022-05-30")] # Queen's platinum Jubilee
default_uk_spring_bank_holiday: Holiday = Holiday(
"UK Spring Bank Holiday",
month=5,
day=31,
offset=DateOffset(weekday=MO(-1)),
)

queens_jubilee_uk_spring_bank_holiday: Holiday = Holiday(
"Queen's Jubilee UK Spring Bank Holiday",
month=5,
day=31,
offset=DateOffset(weekday=MO(-1)),
exclude_dates=exclude,
)

original_dates = list(default_uk_spring_bank_holiday.dates(start, end))
exclusion_dates = list(queens_jubilee_uk_spring_bank_holiday.dates(start, end))

assert all(ex in original_dates for ex in exclude)
assert all(ex not in exclusion_dates for ex in exclude)
assert set(exclusion_dates).issubset(original_dates)


def test_holiday_with_multiple_exclusions():
start = Timestamp("2000-01-01")
end = Timestamp("2100-05-31")
exclude = [
Timestamp("2025-01-01"),
Timestamp("2042-01-01"),
Timestamp("2061-01-01"),
] # Yakudoshi new year
default_japan_new_year: Holiday = Holiday(
"Japan New Year",
month=1,
day=1,
)

yakudoshi_new_year: Holiday = Holiday(
"Yakudoshi New Year", month=1, day=1, exclude_dates=exclude
)

original_dates = list(default_japan_new_year.dates(start, end))
exclusion_dates = list(yakudoshi_new_year.dates(start, end))

assert all(ex in original_dates for ex in exclude)
assert all(ex not in exclusion_dates for ex in exclude)
assert set(exclusion_dates).issubset(original_dates)
20 changes: 19 additions & 1 deletion pandas/tseries/holiday.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
datetime,
timedelta,
)
from typing import TYPE_CHECKING
from typing import (
TYPE_CHECKING,
Any,
)
import warnings

from dateutil.relativedelta import (
Expand Down Expand Up @@ -169,6 +172,7 @@ def __init__(
start_date=None,
end_date=None,
days_of_week: tuple | None = None,
exclude_dates: list[Any] | None = None,
) -> None:
"""
Parameters
Expand All @@ -193,6 +197,9 @@ class from pandas.tseries.offsets, default None
days_of_week : tuple of int or dateutil.relativedelta weekday strs, default None
Provide a tuple of days e.g (0,1,2,3,) for Monday Through Thursday
Monday=0,..,Sunday=6
exclude_dates : list of datetime-likes or
single datetime-like, default None
Specific dates to exclude e.g. skipping a specific year's holiday

Examples
--------
Expand Down Expand Up @@ -257,6 +264,11 @@ class from pandas.tseries.offsets, default None
self.observance = observance
assert days_of_week is None or type(days_of_week) == tuple
self.days_of_week = days_of_week
self.exclude_dates = (
[Timestamp(ex) for ex in exclude_dates]
if exclude_dates is not None
else exclude_dates
)

def __repr__(self) -> str:
info = ""
Expand Down Expand Up @@ -328,6 +340,12 @@ def dates(
holiday_dates = holiday_dates[
(holiday_dates >= filter_start_date) & (holiday_dates <= filter_end_date)
]

if self.exclude_dates:
holiday_dates = DatetimeIndex(
[hd for hd in holiday_dates if hd not in self.exclude_dates]
)

if return_name:
return Series(self.name, index=holiday_dates)
return holiday_dates
Expand Down
Loading