diff --git a/doc/source/v0.15.0.txt b/doc/source/v0.15.0.txt index 0223a11d8a011..5c85e9e684bf3 100644 --- a/doc/source/v0.15.0.txt +++ b/doc/source/v0.15.0.txt @@ -367,7 +367,7 @@ Enhancements - ``PeriodIndex`` supports ``resolution`` as the same as ``DatetimeIndex`` (:issue:`7708`) - ``pandas.tseries.holiday`` has added support for additional holidays and ways to observe holidays (:issue:`7070`) - ``pandas.tseries.holiday.Holiday`` now supports a list of offsets in Python3 (:issue:`7070`) - +- ``pandas.tseries.holiday.Holiday`` now supports a days_of_week parameter (:issue:`7070`) @@ -529,6 +529,7 @@ Bug Fixes - ``Period`` and ``PeriodIndex`` addition/subtraction with ``np.timedelta64`` results in incorrect internal representations (:issue:`7740`) +- ``Holiday`` bug in Holiday with no offset or observance (:issue:`7987`) diff --git a/pandas/tseries/holiday.py b/pandas/tseries/holiday.py index f42ad174b8f0f..ea85f35cd4ca2 100644 --- a/pandas/tseries/holiday.py +++ b/pandas/tseries/holiday.py @@ -4,6 +4,7 @@ from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU from pandas.tseries.offsets import Easter, Day + def next_monday(dt): """ If holiday falls on Saturday, use following Monday instead; @@ -116,7 +117,8 @@ class Holiday(object): for observance. """ def __init__(self, name, year=None, month=None, day=None, offset=None, - observance=None, start_date=None, end_date=None): + observance=None, start_date=None, end_date=None, + days_of_week=None): """ Parameters ---------- @@ -127,6 +129,24 @@ class from pandas.tseries.offsets computes offset from date observance: function computes when holiday is given a pandas Timestamp + days_of_week: + provide a tuple of days e.g (0,1,2,3,) for Monday Through Thursday + Monday=0,..,Sunday=6 + + Examples + -------- + >>> from pandas.tseries.holiday import Holiday, nearest_workday + >>> from pandas import DateOffset + >>> from dateutil.relativedelta import MO + >>> USMemorialDay = Holiday('MemorialDay', month=5, day=24, + offset=DateOffset(weekday=MO(1))) + >>> USLaborDay = Holiday('Labor Day', month=9, day=1, + offset=DateOffset(weekday=MO(1))) + >>> July3rd = Holiday('July 3rd', month=7, day=3,) + >>> NewYears = Holiday('New Years Day', month=1, day=1, + observance=nearest_workday), + >>> July3rd = Holiday('July 3rd', month=7, day=3, + days_of_week=(0, 1, 2, 3)) """ self.name = name self.year = year @@ -136,6 +156,8 @@ class from pandas.tseries.offsets self.start_date = start_date self.end_date = end_date self.observance = observance + assert (days_of_week is None or type(days_of_week) == tuple) + self.days_of_week = days_of_week def __repr__(self): info = '' @@ -183,11 +205,15 @@ def dates(self, start_date, end_date, return_name=False): year_offset = DateOffset(years=1) base_date = Timestamp(datetime(start_date.year, self.month, self.day)) dates = DatetimeIndex(start=base_date, end=end_date, freq=year_offset) - holiday_dates = list(self._apply_rule(dates)) - + holiday_dates = self._apply_rule(dates) + if self.days_of_week is not None: + holiday_dates = list(filter(lambda x: x is not None and + x.dayofweek in self.days_of_week, + holiday_dates)) + else: + holiday_dates = list(filter(lambda x: x is not None, holiday_dates)) if return_name: return Series(self.name, index=holiday_dates) - return holiday_dates def _apply_rule(self, dates): @@ -207,14 +233,13 @@ def _apply_rule(self, dates): if self.observance is not None: return map(lambda d: self.observance(d), dates) - if not isinstance(self.offset, list): - offsets = [self.offset] - else: - offsets = self.offset - - for offset in offsets: - dates = list(map(lambda d: d + offset, dates)) - + if self.offset is not None: + if not isinstance(self.offset, list): + offsets = [self.offset] + else: + offsets = self.offset + for offset in offsets: + dates = list(map(lambda d: d + offset, dates)) return dates holiday_calendars = {} diff --git a/pandas/tseries/tests/test_holiday.py b/pandas/tseries/tests/test_holiday.py index adc2c0d237265..c2300481eca43 100644 --- a/pandas/tseries/tests/test_holiday.py +++ b/pandas/tseries/tests/test_holiday.py @@ -72,6 +72,22 @@ def test_usmemorialday(self): ] self.assertEqual(list(holidays), holidayList) + def test_non_observed_holiday(self): + july_3rd = Holiday('July 4th Eve', month=7, day=3) + result = july_3rd.dates("2001-01-01", "2003-03-03") + expected = [Timestamp('2001-07-03 00:00:00'), + Timestamp('2002-07-03 00:00:00')] + self.assertEqual(list(result), expected) + july_3rd = Holiday('July 4th Eve', month=7, day=3, + days_of_week=(0, 1, 2, 3)) + result = july_3rd.dates("2001-01-01", "2008-03-03") + expected = [Timestamp('2001-07-03 00:00:00'), + Timestamp('2002-07-03 00:00:00'), + Timestamp('2003-07-03 00:00:00'), + Timestamp('2006-07-03 00:00:00'), + Timestamp('2007-07-03 00:00:00')] + self.assertEqual(list(result), expected) + def test_easter(self): holidays = EasterMonday.dates(self.start_date, self.end_date)