bug fix for 7987 and add day of week functionality to Holiday
State: closed

This should fix issue 7987 where there is no offset or observance.

closes https://github.com/pydata/pandas/issues/7987


Labels:
name: Bug
name: Frequency
description: DateOffsets

Comments:
this seems fine

cc @rockg ?

Bit late to the party but I was extending Holiday last week  to handle this issue and related.

My requirement is a flexible calendar that include some of the official holidays plus other days where the  business process is significantly different.  Examples are:
-- Fixed days e.g. Halloween
-- Events that do not happen every year e.g. Federal Election Day 
-- Related dates  e.g Easter determines Good Friday and Ash Wednesday ( I found the current implementation does not allow a simple 
    offset = Easter()  - Day(2)

My simple approach  was to subclass Holiday and to add new observance function of skip_odd_years.

```
class MyHoliday(Holiday):
    def __init__(self, name, year=None, month=None, day=None, offset=None, observance=None, start_date=None, end_date=None, subhols =  None):
        super(MyHoliday, self).__init__( name, year=year, month=month, day=day, offset=offset, observance=observance, start_date=start_date, end_date=end_date,)
        self.subhols = subhols
        return 

    def _apply_rule(self, dates):
        """
        Apply the given offset/observance to an
    iterable of dates.

        Parameters
        ----------
        dates : array-like
            Dates to apply the given offset/observance rule

        Returns
        -------
        Dates with rules applied
        """
        if self.observance is not None:
            return filter(lambda x: x is not None, map(lambda d: self.observance(d), dates))

        # added for fixed holiday
        if not self.offset:
            return dates
        if not isinstance(self.offset, list):
            offsets =   [self.offset]
        else:
            offsets =   self.offset

        for offset in offsets:
            dates = map(lambda d: d + offset, dates)

        return dates

    def dates(self, start_date, end_date, return_name =  False):

        # subhols are other dates, a fixed offset to the main holiday

        res = Holiday.dates(self, start_date, end_date, return_name)
        if not self.subhols:
            return res
        if return_name:
            dts = res.index.copy()
        else:
            dts =  res.copy()

        # build the list of other holidays
        extras = [( dt + Day(offset), name)  for name, offset in self.subhols for dt in dts]

        if return_name:

            return res.append( pd.Series(data = [e[1] for e in extras], index = [e[0] for e in extras])).sort_index()
        else:
            return res.append(pd.DatetimeIndex(data = [e[0] for e in extras])).sort()

# new function for observance -- Election Day ?
def skip_odd_years(dt):
    if dt.year % 2 ==1:
        return None
    else:
        return dt 
```

This works but I would prefer a more robust fix for complex offsets in Holiday.

you can just supply a  function for how to compute observance and have it return None on 

jon, this was fixed in the pr.

GoodFriday = Holiday("Good Friday", month=1, day=1, offset=[Easter(), Day(-2)])

You can support multiple offsets in a rule (need to pass it as an array of offsets).  There was a bug in python 3 that prevented this from working, but it was recently fixed [see here](https://github.com/pydata/pandas/pull/7913).  @MichaelWS added tests for Good Friday.