From e93ab50f9ac3db4034b2f76d5aa4e4a355483a9b Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 4 Dec 2017 15:15:38 -0800 Subject: [PATCH 1/8] remove unused BeginMixin --- pandas/_libs/tslibs/offsets.pyx | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 251af50ab12ce..29e14103dfe20 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -307,27 +307,6 @@ class CacheableOffset(object): _cacheable = True -class BeginMixin(object): - # helper for vectorized offsets - - def _beg_apply_index(self, i, freq): - """Offsets index to beginning of Period frequency""" - - off = i.to_perioddelta('D') - - base, mult = get_freq_code(freq) - base_period = i.to_period(base) - if self.n <= 0: - # when subtracting, dates on start roll to prior - roll = np.where(base_period.to_timestamp() == i - off, - self.n, self.n + 1) - else: - roll = self.n - - base = (base_period + roll).to_timestamp() - return base + off - - class EndMixin(object): # helper for vectorized offsets From ce8c7b5a1c2c627143631e98206bb60e49947729 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 4 Dec 2017 15:17:42 -0800 Subject: [PATCH 2/8] dont use DateOffset.__init__ for YearOffsets --- pandas/tseries/offsets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 857ec9e9881d9..ac19789b53714 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -1707,13 +1707,16 @@ def onOffset(self, dt): return dt.month == self.month and dt.day == self._get_offset_day(dt) def __init__(self, n=1, normalize=False, month=None): + self.n = self._validate_n(n) + self.normalize = normalize + month = month if month is not None else self._default_month self.month = month if self.month < 1 or self.month > 12: raise ValueError('Month must go from 1 to 12') - DateOffset.__init__(self, n=n, normalize=normalize, month=month) + self.kwds = {'month': month} @classmethod def _from_name(cls, suffix=None): From 1205f44bf1ac96e9b2da1a1893c577fc4776fbc3 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 4 Dec 2017 15:22:23 -0800 Subject: [PATCH 3/8] implement _CustomMixin --- pandas/tseries/offsets.py | 74 ++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index ac19789b53714..9882f986fa88f 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -413,6 +413,25 @@ def _from_name(cls, suffix=None): return cls() +class _CustomMixin(object): + """ + mixin for classes that define and validate calendar, holidays, + and weekdays attributes + """ + def __init__(self, weekmask, holidays, calendar): + calendar, holidays = _get_calendar(weekmask=weekmask, + holidays=holidays, + calendar=calendar) + # Custom offset instances are identified by the + # following two attributes. See DateOffset._params() + # holidays, weekmask + + # assumes self.kwds already exists + self.kwds['weekmask'] = self.weekmask = weekmask + self.kwds['holidays'] = self.holidays = holidays + self.kwds['calendar'] = self.calendar = calendar + + class BusinessMixin(object): """ mixin to business types to provide related functions """ @@ -575,6 +594,7 @@ def __init__(self, start='09:00', end='17:00', offset=timedelta(0)): self.kwds = kwds self._offset = offset + # TODO: Cache this once offsets are immutable def _get_daytime_flag(self): if self.start == self.end: raise ValueError('start and end must not be the same') @@ -616,6 +636,7 @@ def _prev_opening_time(self, other): return datetime(other.year, other.month, other.day, self.start.hour, self.start.minute) + # TODO: cache this once offsets are immutable def _get_business_hours_by_sec(self): """ Return business hours in a day by seconds. @@ -796,7 +817,7 @@ def next_bday(self): return BusinessDay(n=nb_offset) -class CustomBusinessDay(BusinessDay): +class CustomBusinessDay(_CustomMixin, BusinessDay): """ DateOffset subclass representing possibly n custom business days, excluding holidays @@ -822,19 +843,9 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', self.n = self._validate_n(n) self.normalize = normalize self._offset = offset - self.kwds = {} - - calendar, holidays = _get_calendar(weekmask=weekmask, - holidays=holidays, - calendar=calendar) - # CustomBusinessDay instances are identified by the - # following two attributes. See DateOffset._params() - # holidays, weekmask + self.kwds = {'offset': offset} - self.kwds['weekmask'] = self.weekmask = weekmask - self.kwds['holidays'] = self.holidays = holidays - self.kwds['calendar'] = self.calendar = calendar - self.kwds['offset'] = offset + _CustomMixin.__init__(self, weekmask, holidays, calendar) @apply_wraps def apply(self, other): @@ -874,7 +885,8 @@ def onOffset(self, dt): return np.is_busday(day64, busdaycal=self.calendar) -class CustomBusinessHour(BusinessHourMixin, SingleConstructorOffset): +class CustomBusinessHour(_CustomMixin, BusinessHourMixin, + SingleConstructorOffset): """ DateOffset subclass representing possibly n custom business days @@ -892,12 +904,8 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', super(CustomBusinessHour, self).__init__(start=start, end=end, offset=offset) - calendar, holidays = _get_calendar(weekmask=weekmask, - holidays=holidays, - calendar=calendar) - self.kwds['weekmask'] = self.weekmask = weekmask - self.kwds['holidays'] = self.holidays = holidays - self.kwds['calendar'] = self.calendar = calendar + # Note: `self.kwds` gets defined in BusinessHourMixin.__init__ + _CustomMixin.__init__(self, weekmask, holidays, calendar) @cache_readonly def next_bday(self): @@ -981,7 +989,7 @@ class BusinessMonthBegin(MonthOffset): _day_opt = 'business_start' -class CustomBusinessMonthEnd(BusinessMixin, MonthOffset): +class CustomBusinessMonthEnd(_CustomMixin, BusinessMixin, MonthOffset): """ DateOffset subclass representing one custom business month, incrementing between end of month dates @@ -1011,15 +1019,9 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', self.n = self._validate_n(n) self.normalize = normalize self._offset = offset - self.kwds = {} + self.kwds = {'offset': offset} - calendar, holidays = _get_calendar(weekmask=weekmask, - holidays=holidays, - calendar=calendar) - self.kwds['weekmask'] = self.weekmask = weekmask - self.kwds['holidays'] = self.holidays = holidays - self.kwds['calendar'] = self.calendar = calendar - self.kwds['offset'] = offset + _CustomMixin.__init__(self, weekmask, holidays, calendar) @cache_readonly def cbday(self): @@ -1054,7 +1056,7 @@ def apply(self, other): return result -class CustomBusinessMonthBegin(BusinessMixin, MonthOffset): +class CustomBusinessMonthBegin(_CustomMixin, BusinessMixin, MonthOffset): """ DateOffset subclass representing one custom business month, incrementing between beginning of month dates @@ -1084,17 +1086,9 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', self.n = self._validate_n(n) self.normalize = normalize self._offset = offset - self.kwds = {} + self.kwds = {'offset': offset} - # _get_calendar does validation and possible transformation - # of calendar and holidays. - calendar, holidays = _get_calendar(weekmask=weekmask, - holidays=holidays, - calendar=calendar) - self.kwds['calendar'] = self.calendar = calendar - self.kwds['weekmask'] = self.weekmask = weekmask - self.kwds['holidays'] = self.holidays = holidays - self.kwds['offset'] = offset + _CustomMixin.__init__(self, weekmask, holidays, calendar) @cache_readonly def cbday(self): From c37bde039127e04f842e4217679b2fcfd4de9dac Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 4 Dec 2017 15:25:52 -0800 Subject: [PATCH 4/8] implement _CustomBusinessMonth --- pandas/tseries/offsets.py | 63 ++++++++++----------------------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 9882f986fa88f..21e704e41070b 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -989,10 +989,10 @@ class BusinessMonthBegin(MonthOffset): _day_opt = 'business_start' -class CustomBusinessMonthEnd(_CustomMixin, BusinessMixin, MonthOffset): +class _CustomBusinessMonth(_CustomMixin, BusinessMixin, MonthOffset): """ DateOffset subclass representing one custom business month, incrementing - between end of month dates + between [BEGIN/END] of month dates Parameters ---------- @@ -1007,11 +1007,9 @@ class CustomBusinessMonthEnd(_CustomMixin, BusinessMixin, MonthOffset): passed to ``numpy.busdaycalendar`` calendar : pd.HolidayCalendar or np.busdaycalendar """ - _cacheable = False - _prefix = 'CBM' - onOffset = DateOffset.onOffset # override MonthOffset method + onOffset = DateOffset.onOffset # override MonthOffset method apply_index = DateOffset.apply_index # override MonthOffset method def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', @@ -1030,7 +1028,17 @@ def cbday(self): @cache_readonly def m_offset(self): - return MonthEnd(n=1, normalize=self.normalize) + if self._prefix.endwith('S'): + # MonthBegin: + return MonthBegin(n=1, normalize=self.normalize) + else: + # MonthEnd + return MonthEnd(n=1, normalize=self.normalize) + + +class CustomBusinessMonthEnd(_CustomBusinessMonth): + __doc__ = _CustomBusinessMonth.__doc__.replace('[BEGIN/END]', 'end') + _prefix = 'CBM' @apply_wraps def apply(self, other): @@ -1056,49 +1064,10 @@ def apply(self, other): return result -class CustomBusinessMonthBegin(_CustomMixin, BusinessMixin, MonthOffset): - """ - DateOffset subclass representing one custom business month, incrementing - between beginning of month dates - - Parameters - ---------- - n : int, default 1 - offset : timedelta, default timedelta(0) - normalize : bool, default False - Normalize start/end dates to midnight before generating date range - weekmask : str, Default 'Mon Tue Wed Thu Fri' - weekmask of valid business days, passed to ``numpy.busdaycalendar`` - holidays : list - list/array of dates to exclude from the set of valid business days, - passed to ``numpy.busdaycalendar`` - calendar : pd.HolidayCalendar or np.busdaycalendar - """ - - _cacheable = False +class CustomBusinessMonthBegin(_CustomBusinessMonth): + __doc__ = _CustomBusinessMonth.__doc__.replace('[BEGIN/END]', 'beginning') _prefix = 'CBMS' - onOffset = DateOffset.onOffset # override MonthOffset method - apply_index = DateOffset.apply_index # override MonthOffset method - - def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', - holidays=None, calendar=None, offset=timedelta(0)): - self.n = self._validate_n(n) - self.normalize = normalize - self._offset = offset - self.kwds = {'offset': offset} - - _CustomMixin.__init__(self, weekmask, holidays, calendar) - - @cache_readonly - def cbday(self): - kwds = self.kwds - return CustomBusinessDay(n=self.n, normalize=self.normalize, **kwds) - - @cache_readonly - def m_offset(self): - return MonthBegin(n=1, normalize=self.normalize) - @apply_wraps def apply(self, other): n = self.n From c20ea5b1ba8d39dbc019b9de377f60a76cdaf9c3 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 4 Dec 2017 15:28:33 -0800 Subject: [PATCH 5/8] implement next_bday in BusinessHourMixin --- pandas/tseries/offsets.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 21e704e41070b..fcee06fe1c304 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -594,6 +594,22 @@ def __init__(self, start='09:00', end='17:00', offset=timedelta(0)): self.kwds = kwds self._offset = offset + @cache_readonly + def next_bday(self): + # used for moving to next businessday + if self.n >= 0: + nb_offset = 1 + else: + nb_offset = -1 + if self._prefix.startswith('C'): + # CustomBusinessHour + return return CustomBusinessDay(n=nb_offset, + weekmask=self.weekmask, + holidays=self.holidays, + calendar=self.calendar) + else: + return BusinessDay(n=nb_offset) + # TODO: Cache this once offsets are immutable def _get_daytime_flag(self): if self.start == self.end: @@ -807,15 +823,6 @@ def __init__(self, n=1, normalize=False, start='09:00', self.normalize = normalize super(BusinessHour, self).__init__(start=start, end=end, offset=offset) - @cache_readonly - def next_bday(self): - # used for moving to next businessday - if self.n >= 0: - nb_offset = 1 - else: - nb_offset = -1 - return BusinessDay(n=nb_offset) - class CustomBusinessDay(_CustomMixin, BusinessDay): """ @@ -907,18 +914,6 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', # Note: `self.kwds` gets defined in BusinessHourMixin.__init__ _CustomMixin.__init__(self, weekmask, holidays, calendar) - @cache_readonly - def next_bday(self): - # used for moving to next businessday - if self.n >= 0: - nb_offset = 1 - else: - nb_offset = -1 - return CustomBusinessDay(n=nb_offset, - weekmask=self.weekmask, - holidays=self.holidays, - calendar=self.calendar) - # --------------------------------------------------------------------- # Month-Based Offset Classes From 5d14df6a86c8a7249cd4a14bfd82ddc8519974b4 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 4 Dec 2017 15:33:30 -0800 Subject: [PATCH 6/8] typo fixup --- pandas/tseries/offsets.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index fcee06fe1c304..36174a9713af8 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -603,10 +603,10 @@ def next_bday(self): nb_offset = -1 if self._prefix.startswith('C'): # CustomBusinessHour - return return CustomBusinessDay(n=nb_offset, - weekmask=self.weekmask, - holidays=self.holidays, - calendar=self.calendar) + return CustomBusinessDay(n=nb_offset, + weekmask=self.weekmask, + holidays=self.holidays, + calendar=self.calendar) else: return BusinessDay(n=nb_offset) @@ -908,8 +908,7 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', start='09:00', end='17:00', offset=timedelta(0)): self.n = self._validate_n(n) self.normalize = normalize - super(CustomBusinessHour, self).__init__(start=start, - end=end, offset=offset) + BusinessHourMixin.__init__(self, start=start, end=end, offset=offset) # Note: `self.kwds` gets defined in BusinessHourMixin.__init__ _CustomMixin.__init__(self, weekmask, holidays, calendar) @@ -1023,7 +1022,7 @@ def cbday(self): @cache_readonly def m_offset(self): - if self._prefix.endwith('S'): + if self._prefix.endswith('S'): # MonthBegin: return MonthBegin(n=1, normalize=self.normalize) else: From ca9ca9aa670edc0ad97408b372545b53d2e96654 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 4 Dec 2017 15:35:52 -0800 Subject: [PATCH 7/8] define self.kwds outside BusinessHourMixin.__init__ --- pandas/tseries/offsets.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index 36174a9713af8..d18e8ec4bc825 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -591,7 +591,7 @@ def __init__(self, start='09:00', end='17:00', offset=timedelta(0)): kwds = {'offset': offset} self.start = kwds['start'] = _validate_business_time(start) self.end = kwds['end'] = _validate_business_time(end) - self.kwds = kwds + self.kwds.update(kwds) self._offset = offset @cache_readonly @@ -821,6 +821,7 @@ def __init__(self, n=1, normalize=False, start='09:00', end='17:00', offset=timedelta(0)): self.n = self._validate_n(n) self.normalize = normalize + self.kwds = {} super(BusinessHour, self).__init__(start=start, end=end, offset=offset) @@ -908,10 +909,11 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri', start='09:00', end='17:00', offset=timedelta(0)): self.n = self._validate_n(n) self.normalize = normalize - BusinessHourMixin.__init__(self, start=start, end=end, offset=offset) + self._offset = offset + self.kwds = {'offset': offset} - # Note: `self.kwds` gets defined in BusinessHourMixin.__init__ _CustomMixin.__init__(self, weekmask, holidays, calendar) + BusinessHourMixin.__init__(self, start=start, end=end, offset=offset) # --------------------------------------------------------------------- From 2274cc3d2f7fa4134707a6aade54b174607efb7d Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 5 Dec 2017 07:44:47 -0800 Subject: [PATCH 8/8] edits per reviewer requests --- pandas/tseries/offsets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index d18e8ec4bc825..4dae59d11f66f 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -415,7 +415,7 @@ def _from_name(cls, suffix=None): class _CustomMixin(object): """ - mixin for classes that define and validate calendar, holidays, + Mixin for classes that define and validate calendar, holidays, and weekdays attributes """ def __init__(self, weekmask, holidays, calendar): @@ -433,7 +433,7 @@ def __init__(self, weekmask, holidays, calendar): class BusinessMixin(object): - """ mixin to business types to provide related functions """ + """ Mixin to business types to provide related functions """ @property def offset(self): @@ -596,7 +596,7 @@ def __init__(self, start='09:00', end='17:00', offset=timedelta(0)): @cache_readonly def next_bday(self): - # used for moving to next businessday + """used for moving to next businessday""" if self.n >= 0: nb_offset = 1 else: