Path: blob/master/ invest-robot-contest_TinkoffBotTwitch-main/venv/lib/python3.8/site-packages/pandas/tseries/holiday.py
7813 views
from __future__ import annotations12from datetime import (3datetime,4timedelta,5)6import warnings78from dateutil.relativedelta import ( # noqa:F4019FR,10MO,11SA,12SU,13TH,14TU,15WE,16)17import numpy as np1819from pandas.errors import PerformanceWarning2021from pandas import (22DateOffset,23DatetimeIndex,24Series,25Timestamp,26concat,27date_range,28)2930from pandas.tseries.offsets import (31Day,32Easter,33)343536def next_monday(dt: datetime) -> datetime:37"""38If holiday falls on Saturday, use following Monday instead;39if holiday falls on Sunday, use Monday instead40"""41if dt.weekday() == 5:42return dt + timedelta(2)43elif dt.weekday() == 6:44return dt + timedelta(1)45return dt464748def next_monday_or_tuesday(dt: datetime) -> datetime:49"""50For second holiday of two adjacent ones!51If holiday falls on Saturday, use following Monday instead;52if holiday falls on Sunday or Monday, use following Tuesday instead53(because Monday is already taken by adjacent holiday on the day before)54"""55dow = dt.weekday()56if dow == 5 or dow == 6:57return dt + timedelta(2)58elif dow == 0:59return dt + timedelta(1)60return dt616263def previous_friday(dt: datetime) -> datetime:64"""65If holiday falls on Saturday or Sunday, use previous Friday instead.66"""67if dt.weekday() == 5:68return dt - timedelta(1)69elif dt.weekday() == 6:70return dt - timedelta(2)71return dt727374def sunday_to_monday(dt: datetime) -> datetime:75"""76If holiday falls on Sunday, use day thereafter (Monday) instead.77"""78if dt.weekday() == 6:79return dt + timedelta(1)80return dt818283def weekend_to_monday(dt: datetime) -> datetime:84"""85If holiday falls on Sunday or Saturday,86use day thereafter (Monday) instead.87Needed for holidays such as Christmas observation in Europe88"""89if dt.weekday() == 6:90return dt + timedelta(1)91elif dt.weekday() == 5:92return dt + timedelta(2)93return dt949596def nearest_workday(dt: datetime) -> datetime:97"""98If holiday falls on Saturday, use day before (Friday) instead;99if holiday falls on Sunday, use day thereafter (Monday) instead.100"""101if dt.weekday() == 5:102return dt - timedelta(1)103elif dt.weekday() == 6:104return dt + timedelta(1)105return dt106107108def next_workday(dt: datetime) -> datetime:109"""110returns next weekday used for observances111"""112dt += timedelta(days=1)113while dt.weekday() > 4:114# Mon-Fri are 0-4115dt += timedelta(days=1)116return dt117118119def previous_workday(dt: datetime) -> datetime:120"""121returns previous weekday used for observances122"""123dt -= timedelta(days=1)124while dt.weekday() > 4:125# Mon-Fri are 0-4126dt -= timedelta(days=1)127return dt128129130def before_nearest_workday(dt: datetime) -> datetime:131"""132returns previous workday after nearest workday133"""134return previous_workday(nearest_workday(dt))135136137def after_nearest_workday(dt: datetime) -> datetime:138"""139returns next workday after nearest workday140needed for Boxing day or multiple holidays in a series141"""142return next_workday(nearest_workday(dt))143144145class Holiday:146"""147Class that defines a holiday with start/end dates and rules148for observance.149"""150151def __init__(152self,153name,154year=None,155month=None,156day=None,157offset=None,158observance=None,159start_date=None,160end_date=None,161days_of_week=None,162):163"""164Parameters165----------166name : str167Name of the holiday , defaults to class name168offset : array of pandas.tseries.offsets or169class from pandas.tseries.offsets170computes offset from date171observance: function172computes when holiday is given a pandas Timestamp173days_of_week:174provide a tuple of days e.g (0,1,2,3,) for Monday Through Thursday175Monday=0,..,Sunday=6176177Examples178--------179>>> from pandas.tseries.holiday import Holiday, nearest_workday180>>> from dateutil.relativedelta import MO181182>>> USMemorialDay = Holiday(183... "Memorial Day", month=5, day=31, offset=pd.DateOffset(weekday=MO(-1))184... )185>>> USMemorialDay186Holiday: Memorial Day (month=5, day=31, offset=<DateOffset: weekday=MO(-1)>)187188>>> USLaborDay = Holiday(189... "Labor Day", month=9, day=1, offset=pd.DateOffset(weekday=MO(1))190... )191>>> USLaborDay192Holiday: Labor Day (month=9, day=1, offset=<DateOffset: weekday=MO(+1)>)193194>>> July3rd = Holiday("July 3rd", month=7, day=3)195>>> July3rd196Holiday: July 3rd (month=7, day=3, )197198>>> NewYears = Holiday(199... "New Years Day", month=1, day=1,200... observance=nearest_workday201... )202>>> NewYears # doctest: +SKIP203Holiday: New Years Day (204month=1, day=1, observance=<function nearest_workday at 0x66545e9bc440>205)206207>>> July3rd = Holiday("July 3rd", month=7, day=3, days_of_week=(0, 1, 2, 3))208>>> July3rd209Holiday: July 3rd (month=7, day=3, )210"""211if offset is not None and observance is not None:212raise NotImplementedError("Cannot use both offset and observance.")213214self.name = name215self.year = year216self.month = month217self.day = day218self.offset = offset219self.start_date = (220Timestamp(start_date) if start_date is not None else start_date221)222self.end_date = Timestamp(end_date) if end_date is not None else end_date223self.observance = observance224assert days_of_week is None or type(days_of_week) == tuple225self.days_of_week = days_of_week226227def __repr__(self) -> str:228info = ""229if self.year is not None:230info += f"year={self.year}, "231info += f"month={self.month}, day={self.day}, "232233if self.offset is not None:234info += f"offset={self.offset}"235236if self.observance is not None:237info += f"observance={self.observance}"238239repr = f"Holiday: {self.name} ({info})"240return repr241242def dates(self, start_date, end_date, return_name=False):243"""244Calculate holidays observed between start date and end date245246Parameters247----------248start_date : starting date, datetime-like, optional249end_date : ending date, datetime-like, optional250return_name : bool, optional, default=False251If True, return a series that has dates and holiday names.252False will only return dates.253"""254start_date = Timestamp(start_date)255end_date = Timestamp(end_date)256257filter_start_date = start_date258filter_end_date = end_date259260if self.year is not None:261dt = Timestamp(datetime(self.year, self.month, self.day))262if return_name:263return Series(self.name, index=[dt])264else:265return [dt]266267dates = self._reference_dates(start_date, end_date)268holiday_dates = self._apply_rule(dates)269if self.days_of_week is not None:270holiday_dates = holiday_dates[271np.in1d(holiday_dates.dayofweek, self.days_of_week)272]273274if self.start_date is not None:275filter_start_date = max(276self.start_date.tz_localize(filter_start_date.tz), filter_start_date277)278if self.end_date is not None:279filter_end_date = min(280self.end_date.tz_localize(filter_end_date.tz), filter_end_date281)282holiday_dates = holiday_dates[283(holiday_dates >= filter_start_date) & (holiday_dates <= filter_end_date)284]285if return_name:286return Series(self.name, index=holiday_dates)287return holiday_dates288289def _reference_dates(self, start_date, end_date):290"""291Get reference dates for the holiday.292293Return reference dates for the holiday also returning the year294prior to the start_date and year following the end_date. This ensures295that any offsets to be applied will yield the holidays within296the passed in dates.297"""298if self.start_date is not None:299start_date = self.start_date.tz_localize(start_date.tz)300301if self.end_date is not None:302end_date = self.end_date.tz_localize(start_date.tz)303304year_offset = DateOffset(years=1)305reference_start_date = Timestamp(306datetime(start_date.year - 1, self.month, self.day)307)308309reference_end_date = Timestamp(310datetime(end_date.year + 1, self.month, self.day)311)312# Don't process unnecessary holidays313dates = date_range(314start=reference_start_date,315end=reference_end_date,316freq=year_offset,317tz=start_date.tz,318)319320return dates321322def _apply_rule(self, dates):323"""324Apply the given offset/observance to a DatetimeIndex of dates.325326Parameters327----------328dates : DatetimeIndex329Dates to apply the given offset/observance rule330331Returns332-------333Dates with rules applied334"""335if self.observance is not None:336return dates.map(lambda d: self.observance(d))337338if self.offset is not None:339if not isinstance(self.offset, list):340offsets = [self.offset]341else:342offsets = self.offset343for offset in offsets:344345# if we are adding a non-vectorized value346# ignore the PerformanceWarnings:347with warnings.catch_warnings():348warnings.simplefilter("ignore", PerformanceWarning)349dates += offset350return dates351352353holiday_calendars = {}354355356def register(cls):357try:358name = cls.name359except AttributeError:360name = cls.__name__361holiday_calendars[name] = cls362363364def get_calendar(name):365"""366Return an instance of a calendar based on its name.367368Parameters369----------370name : str371Calendar name to return an instance of372"""373return holiday_calendars[name]()374375376class HolidayCalendarMetaClass(type):377def __new__(cls, clsname, bases, attrs):378calendar_class = super().__new__(cls, clsname, bases, attrs)379register(calendar_class)380return calendar_class381382383class AbstractHolidayCalendar(metaclass=HolidayCalendarMetaClass):384"""385Abstract interface to create holidays following certain rules.386"""387388rules: list[Holiday] = []389start_date = Timestamp(datetime(1970, 1, 1))390end_date = Timestamp(datetime(2200, 12, 31))391_cache = None392393def __init__(self, name=None, rules=None):394"""395Initializes holiday object with a given set a rules. Normally396classes just have the rules defined within them.397398Parameters399----------400name : str401Name of the holiday calendar, defaults to class name402rules : array of Holiday objects403A set of rules used to create the holidays.404"""405super().__init__()406if name is None:407name = type(self).__name__408self.name = name409410if rules is not None:411self.rules = rules412413def rule_from_name(self, name):414for rule in self.rules:415if rule.name == name:416return rule417418return None419420def holidays(self, start=None, end=None, return_name=False):421"""422Returns a curve with holidays between start_date and end_date423424Parameters425----------426start : starting date, datetime-like, optional427end : ending date, datetime-like, optional428return_name : bool, optional429If True, return a series that has dates and holiday names.430False will only return a DatetimeIndex of dates.431432Returns433-------434DatetimeIndex of holidays435"""436if self.rules is None:437raise Exception(438f"Holiday Calendar {self.name} does not have any rules specified"439)440441if start is None:442start = AbstractHolidayCalendar.start_date443444if end is None:445end = AbstractHolidayCalendar.end_date446447start = Timestamp(start)448end = Timestamp(end)449450# If we don't have a cache or the dates are outside the prior cache, we451# get them again452if self._cache is None or start < self._cache[0] or end > self._cache[1]:453pre_holidays = [454rule.dates(start, end, return_name=True) for rule in self.rules455]456if pre_holidays:457holidays = concat(pre_holidays)458else:459holidays = Series(index=DatetimeIndex([]), dtype=object)460461self._cache = (start, end, holidays.sort_index())462463holidays = self._cache[2]464holidays = holidays[start:end]465466if return_name:467return holidays468else:469return holidays.index470471@staticmethod472def merge_class(base, other):473"""474Merge holiday calendars together. The base calendar475will take precedence to other. The merge will be done476based on each holiday's name.477478Parameters479----------480base : AbstractHolidayCalendar481instance/subclass or array of Holiday objects482other : AbstractHolidayCalendar483instance/subclass or array of Holiday objects484"""485try:486other = other.rules487except AttributeError:488pass489490if not isinstance(other, list):491other = [other]492other_holidays = {holiday.name: holiday for holiday in other}493494try:495base = base.rules496except AttributeError:497pass498499if not isinstance(base, list):500base = [base]501base_holidays = {holiday.name: holiday for holiday in base}502503other_holidays.update(base_holidays)504return list(other_holidays.values())505506def merge(self, other, inplace=False):507"""508Merge holiday calendars together. The caller's class509rules take precedence. The merge will be done510based on each holiday's name.511512Parameters513----------514other : holiday calendar515inplace : bool (default=False)516If True set rule_table to holidays, else return array of Holidays517"""518holidays = self.merge_class(self, other)519if inplace:520self.rules = holidays521else:522return holidays523524525USMemorialDay = Holiday(526"Memorial Day", month=5, day=31, offset=DateOffset(weekday=MO(-1))527)528USLaborDay = Holiday("Labor Day", month=9, day=1, offset=DateOffset(weekday=MO(1)))529USColumbusDay = Holiday(530"Columbus Day", month=10, day=1, offset=DateOffset(weekday=MO(2))531)532USThanksgivingDay = Holiday(533"Thanksgiving Day", month=11, day=1, offset=DateOffset(weekday=TH(4))534)535USMartinLutherKingJr = Holiday(536"Birthday of Martin Luther King, Jr.",537start_date=datetime(1986, 1, 1),538month=1,539day=1,540offset=DateOffset(weekday=MO(3)),541)542USPresidentsDay = Holiday(543"Washington’s Birthday", month=2, day=1, offset=DateOffset(weekday=MO(3))544)545GoodFriday = Holiday("Good Friday", month=1, day=1, offset=[Easter(), Day(-2)])546547EasterMonday = Holiday("Easter Monday", month=1, day=1, offset=[Easter(), Day(1)])548549550class USFederalHolidayCalendar(AbstractHolidayCalendar):551"""552US Federal Government Holiday Calendar based on rules specified by:553https://www.opm.gov/policy-data-oversight/554snow-dismissal-procedures/federal-holidays/555"""556557rules = [558Holiday("New Year's Day", month=1, day=1, observance=nearest_workday),559USMartinLutherKingJr,560USPresidentsDay,561USMemorialDay,562Holiday(563"Juneteenth National Independence Day",564month=6,565day=19,566start_date="2021-06-18",567observance=nearest_workday,568),569Holiday("Independence Day", month=7, day=4, observance=nearest_workday),570USLaborDay,571USColumbusDay,572Holiday("Veterans Day", month=11, day=11, observance=nearest_workday),573USThanksgivingDay,574Holiday("Christmas Day", month=12, day=25, observance=nearest_workday),575]576577578def HolidayCalendarFactory(name, base, other, base_class=AbstractHolidayCalendar):579rules = AbstractHolidayCalendar.merge_class(base, other)580calendar_class = type(name, (base_class,), {"rules": rules, "name": name})581return calendar_class582583584