1111from dateutil .relativedelta import relativedelta , weekday
1212from dateutil .easter import easter
1313from pandas ._libs import tslib , Timestamp , OutOfBoundsDatetime , Timedelta
14+ from pandas .util ._decorators import cache_readonly
1415
1516import functools
1617import operator
@@ -573,9 +574,9 @@ def __setstate__(self, state):
573574 """Reconstruct an instance from a pickled state"""
574575 self .__dict__ = state
575576 if 'weekmask' in state and 'holidays' in state :
576- calendar , holidays = self . get_calendar (weekmask = self .weekmask ,
577- holidays = self .holidays ,
578- calendar = None )
577+ calendar , holidays = _get_calendar (weekmask = self .weekmask ,
578+ holidays = self .holidays ,
579+ calendar = None )
579580 self .kwds ['calendar' ] = self .calendar = calendar
580581 self .kwds ['holidays' ] = self .holidays = holidays
581582 self .kwds ['weekmask' ] = state ['weekmask' ]
@@ -978,9 +979,9 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
978979 self .normalize = normalize
979980 self .kwds = kwds
980981 self .offset = kwds .get ('offset' , timedelta (0 ))
981- calendar , holidays = self . get_calendar (weekmask = weekmask ,
982- holidays = holidays ,
983- calendar = calendar )
982+ calendar , holidays = _get_calendar (weekmask = weekmask ,
983+ holidays = holidays ,
984+ calendar = calendar )
984985 # CustomBusinessDay instances are identified by the
985986 # following two attributes. See DateOffset._params()
986987 # holidays, weekmask
@@ -989,36 +990,6 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
989990 self .kwds ['holidays' ] = self .holidays = holidays
990991 self .kwds ['calendar' ] = self .calendar = calendar
991992
992- def get_calendar (self , weekmask , holidays , calendar ):
993- """Generate busdaycalendar"""
994- if isinstance (calendar , np .busdaycalendar ):
995- if not holidays :
996- holidays = tuple (calendar .holidays )
997- elif not isinstance (holidays , tuple ):
998- holidays = tuple (holidays )
999- else :
1000- # trust that calendar.holidays and holidays are
1001- # consistent
1002- pass
1003- return calendar , holidays
1004-
1005- if holidays is None :
1006- holidays = []
1007- try :
1008- holidays = holidays + calendar .holidays ().tolist ()
1009- except AttributeError :
1010- pass
1011- holidays = [self ._to_dt64 (dt , dtype = 'datetime64[D]' ) for dt in
1012- holidays ]
1013- holidays = tuple (sorted (holidays ))
1014-
1015- kwargs = {'weekmask' : weekmask }
1016- if holidays :
1017- kwargs ['holidays' ] = holidays
1018-
1019- busdaycalendar = np .busdaycalendar (** kwargs )
1020- return busdaycalendar , holidays
1021-
1022993 @apply_wraps
1023994 def apply (self , other ):
1024995 if self .n <= 0 :
@@ -1050,25 +1021,10 @@ def apply(self, other):
10501021 def apply_index (self , i ):
10511022 raise NotImplementedError
10521023
1053- @staticmethod
1054- def _to_dt64 (dt , dtype = 'datetime64' ):
1055- # Currently
1056- # > np.datetime64(dt.datetime(2013,5,1),dtype='datetime64[D]')
1057- # numpy.datetime64('2013-05-01T02:00:00.000000+0200')
1058- # Thus astype is needed to cast datetime to datetime64[D]
1059- if getattr (dt , 'tzinfo' , None ) is not None :
1060- i8 = tslib .pydt_to_i8 (dt )
1061- dt = tslib .tz_convert_single (i8 , 'UTC' , dt .tzinfo )
1062- dt = Timestamp (dt )
1063- dt = np .datetime64 (dt )
1064- if dt .dtype .name != dtype :
1065- dt = dt .astype (dtype )
1066- return dt
1067-
10681024 def onOffset (self , dt ):
10691025 if self .normalize and not _is_normalized (dt ):
10701026 return False
1071- day64 = self . _to_dt64 (dt , 'datetime64[D]' )
1027+ day64 = _to_dt64 (dt , 'datetime64[D]' )
10721028 return np .is_busday (day64 , busdaycal = self .calendar )
10731029
10741030
@@ -1087,19 +1043,25 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
10871043 self .n = int (n )
10881044 self .normalize = normalize
10891045 super (CustomBusinessHour , self ).__init__ (** kwds )
1046+
1047+ calendar , holidays = _get_calendar (weekmask = weekmask ,
1048+ holidays = holidays ,
1049+ calendar = calendar )
1050+ self .kwds ['weekmask' ] = self .weekmask = weekmask
1051+ self .kwds ['holidays' ] = self .holidays = holidays
1052+ self .kwds ['calendar' ] = self .calendar = calendar
1053+
1054+ @cache_readonly
1055+ def next_bday (self ):
10901056 # used for moving to next businessday
10911057 if self .n >= 0 :
10921058 nb_offset = 1
10931059 else :
10941060 nb_offset = - 1
1095- self .next_bday = CustomBusinessDay (n = nb_offset ,
1096- weekmask = weekmask ,
1097- holidays = holidays ,
1098- calendar = calendar )
1099-
1100- self .kwds ['weekmask' ] = self .next_bday .weekmask
1101- self .kwds ['holidays' ] = self .next_bday .holidays
1102- self .kwds ['calendar' ] = self .next_bday .calendar
1061+ return CustomBusinessDay (n = nb_offset ,
1062+ weekmask = self .weekmask ,
1063+ holidays = self .holidays ,
1064+ calendar = self .calendar )
11031065
11041066
11051067class MonthOffset (SingleConstructorOffset ):
@@ -1471,11 +1433,25 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
14711433 self .normalize = normalize
14721434 self .kwds = kwds
14731435 self .offset = kwds .get ('offset' , timedelta (0 ))
1474- self .cbday = CustomBusinessDay (n = self .n , normalize = normalize ,
1475- weekmask = weekmask , holidays = holidays ,
1476- calendar = calendar , ** kwds )
1477- self .m_offset = MonthEnd (n = 1 , normalize = normalize , ** kwds )
1478- self .kwds ['calendar' ] = self .cbday .calendar # cache numpy calendar
1436+
1437+ calendar , holidays = _get_calendar (weekmask = weekmask ,
1438+ holidays = holidays ,
1439+ calendar = calendar )
1440+ self .kwds ['weekmask' ] = self .weekmask = weekmask
1441+ self .kwds ['holidays' ] = self .holidays = holidays
1442+ self .kwds ['calendar' ] = self .calendar = calendar
1443+
1444+ @cache_readonly
1445+ def cbday (self ):
1446+ kwds = self .kwds
1447+ return CustomBusinessDay (n = self .n , normalize = self .normalize , ** kwds )
1448+
1449+ @cache_readonly
1450+ def m_offset (self ):
1451+ kwds = self .kwds
1452+ kwds = {key : kwds [key ] for key in kwds
1453+ if key not in ['calendar' , 'weekmask' , 'holidays' ]}
1454+ return MonthEnd (n = 1 , normalize = self .normalize , ** kwds )
14791455
14801456 @apply_wraps
14811457 def apply (self , other ):
@@ -1531,11 +1507,27 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
15311507 self .normalize = normalize
15321508 self .kwds = kwds
15331509 self .offset = kwds .get ('offset' , timedelta (0 ))
1534- self .cbday = CustomBusinessDay (n = self .n , normalize = normalize ,
1535- weekmask = weekmask , holidays = holidays ,
1536- calendar = calendar , ** kwds )
1537- self .m_offset = MonthBegin (n = 1 , normalize = normalize , ** kwds )
1538- self .kwds ['calendar' ] = self .cbday .calendar # cache numpy calendar
1510+
1511+ # _get_calendar does validation and possible transformation
1512+ # of calendar and holidays.
1513+ calendar , holidays = _get_calendar (weekmask = weekmask ,
1514+ holidays = holidays ,
1515+ calendar = calendar )
1516+ kwds ['calendar' ] = self .calendar = calendar
1517+ kwds ['weekmask' ] = self .weekmask = weekmask
1518+ kwds ['holidays' ] = self .holidays = holidays
1519+
1520+ @cache_readonly
1521+ def cbday (self ):
1522+ kwds = self .kwds
1523+ return CustomBusinessDay (n = self .n , normalize = self .normalize , ** kwds )
1524+
1525+ @cache_readonly
1526+ def m_offset (self ):
1527+ kwds = self .kwds
1528+ kwds = {key : kwds [key ] for key in kwds
1529+ if key not in ['calendar' , 'weekmask' , 'holidays' ]}
1530+ return MonthBegin (n = 1 , normalize = self .normalize , ** kwds )
15391531
15401532 @apply_wraps
15411533 def apply (self , other ):
@@ -2861,6 +2853,54 @@ class Nano(Tick):
28612853CBMonthBegin = CustomBusinessMonthBegin
28622854CDay = CustomBusinessDay
28632855
2856+ # ---------------------------------------------------------------------
2857+ # Business Calendar helpers
2858+
2859+
2860+ def _get_calendar (weekmask , holidays , calendar ):
2861+ """Generate busdaycalendar"""
2862+ if isinstance (calendar , np .busdaycalendar ):
2863+ if not holidays :
2864+ holidays = tuple (calendar .holidays )
2865+ elif not isinstance (holidays , tuple ):
2866+ holidays = tuple (holidays )
2867+ else :
2868+ # trust that calendar.holidays and holidays are
2869+ # consistent
2870+ pass
2871+ return calendar , holidays
2872+
2873+ if holidays is None :
2874+ holidays = []
2875+ try :
2876+ holidays = holidays + calendar .holidays ().tolist ()
2877+ except AttributeError :
2878+ pass
2879+ holidays = [_to_dt64 (dt , dtype = 'datetime64[D]' ) for dt in holidays ]
2880+ holidays = tuple (sorted (holidays ))
2881+
2882+ kwargs = {'weekmask' : weekmask }
2883+ if holidays :
2884+ kwargs ['holidays' ] = holidays
2885+
2886+ busdaycalendar = np .busdaycalendar (** kwargs )
2887+ return busdaycalendar , holidays
2888+
2889+
2890+ def _to_dt64 (dt , dtype = 'datetime64' ):
2891+ # Currently
2892+ # > np.datetime64(dt.datetime(2013,5,1),dtype='datetime64[D]')
2893+ # numpy.datetime64('2013-05-01T02:00:00.000000+0200')
2894+ # Thus astype is needed to cast datetime to datetime64[D]
2895+ if getattr (dt , 'tzinfo' , None ) is not None :
2896+ i8 = tslib .pydt_to_i8 (dt )
2897+ dt = tslib .tz_convert_single (i8 , 'UTC' , dt .tzinfo )
2898+ dt = Timestamp (dt )
2899+ dt = np .datetime64 (dt )
2900+ if dt .dtype .name != dtype :
2901+ dt = dt .astype (dtype )
2902+ return dt
2903+
28642904
28652905def _get_firstbday (wkday ):
28662906 """
0 commit comments