1010# import after tools, dateutil check
1111from dateutil .relativedelta import relativedelta , weekday
1212from dateutil .easter import easter
13+
1314from pandas ._libs import tslib , Timestamp , OutOfBoundsDatetime , Timedelta
15+ from pandas ._libs .lib import cache_readonly
1416
1517import functools
1618import operator
@@ -132,6 +134,33 @@ class CacheableOffset(object):
132134 _cacheable = True
133135
134136
137+ _kwds_use_relativedelta = (
138+ 'years' , 'months' , 'weeks' , 'days' ,
139+ 'year' , 'month' , 'week' , 'day' , 'weekday' ,
140+ 'hour' , 'minute' , 'second' , 'microsecond'
141+ )
142+
143+
144+ def _determine_offset (kwds ):
145+ # timedelta is used for sub-daily plural offsets and all singular
146+ # offsets relativedelta is used for plural offsets of daily length or
147+ # more nanosecond(s) are handled by apply_wraps
148+ kwds_no_nanos = dict (
149+ (k , v ) for k , v in kwds .items ()
150+ if k not in ('nanosecond' , 'nanoseconds' )
151+ )
152+
153+ if len (kwds_no_nanos ) > 0 :
154+ if any (k in _kwds_use_relativedelta for k in kwds_no_nanos ):
155+ offset = relativedelta (** kwds_no_nanos )
156+ else :
157+ # sub-daily offset - use timedelta (tz-aware)
158+ offset = timedelta (** kwds_no_nanos )
159+ else :
160+ offset = timedelta (1 )
161+ return offset
162+
163+
135164class DateOffset (object ):
136165 """
137166 Standard kind of date increment used for a date range.
@@ -177,43 +206,37 @@ def __add__(date):
177206 """
178207 _cacheable = False
179208 _normalize_cache = True
180- _kwds_use_relativedelta = (
181- 'years' , 'months' , 'weeks' , 'days' ,
182- 'year' , 'month' , 'week' , 'day' , 'weekday' ,
183- 'hour' , 'minute' , 'second' , 'microsecond'
184- )
185- _use_relativedelta = False
186209 _adjust_dst = False
210+ _typ = "dateoffset"
187211
188- # default for prior pickles
189- normalize = False
212+ def __setattr__ (self , name , value ):
213+ # DateOffset needs to be effectively immutable in order for the
214+ # caching in _cached_params to be correct.
215+ frozen = ['n' , '_offset' , 'normalize' , '_inc' ]
216+ if name in frozen and hasattr (self , name ):
217+ msg = '%s cannot change attribute %s' % (self .__class__ , name )
218+ raise TypeError (msg )
219+ object .__setattr__ (self , name , value )
220+
221+ def __setstate__ (self , state ):
222+ """Reconstruct an instance from a pickled state"""
223+ self .__dict__ = state
224+ if 'normalize' not in state and not hasattr (self , 'normalize' ):
225+ # default for prior pickles
226+ self .normalize = False
190227
191228 def __init__ (self , n = 1 , normalize = False , ** kwds ):
192229 self .n = int (n )
193230 self .normalize = normalize
194231 self .kwds = kwds
195- self ._offset , self ._use_relativedelta = self ._determine_offset ()
196-
197- def _determine_offset (self ):
198- # timedelta is used for sub-daily plural offsets and all singular
199- # offsets relativedelta is used for plural offsets of daily length or
200- # more nanosecond(s) are handled by apply_wraps
201- kwds_no_nanos = dict (
202- (k , v ) for k , v in self .kwds .items ()
203- if k not in ('nanosecond' , 'nanoseconds' )
204- )
205- use_relativedelta = False
206-
207- if len (kwds_no_nanos ) > 0 :
208- if any (k in self ._kwds_use_relativedelta for k in kwds_no_nanos ):
209- use_relativedelta = True
210- offset = relativedelta (** kwds_no_nanos )
211- else :
212- # sub-daily offset - use timedelta (tz-aware)
213- offset = timedelta (** kwds_no_nanos )
214- else :
215- offset = timedelta (1 )
216- return offset , use_relativedelta
232+ self ._offset = _determine_offset (kwds )
233+
234+ @property
235+ def _use_relativedelta (self ):
236+ # We need to check for _offset existence because it may not exist
237+ # if we are in the process of unpickling.
238+ return (hasattr (self , '_offset' ) and
239+ isinstance (self ._offset , relativedelta ))
217240
218241 @apply_wraps
219242 def apply (self , other ):
@@ -308,7 +331,24 @@ def copy(self):
308331 def _should_cache (self ):
309332 return self .isAnchored () and self ._cacheable
310333
334+ @cache_readonly
335+ def _cached_params (self ):
336+ assert len (self .kwds ) == 0
337+ all_paras = dict (list (vars (self ).items ()))
338+ # equiv: self.__dict__.copy()
339+ if 'holidays' in all_paras and not all_paras ['holidays' ]:
340+ all_paras .pop ('holidays' )
341+ exclude = ['kwds' , 'name' , 'normalize' , 'calendar' ]
342+ attrs = [(k , v ) for k , v in all_paras .items ()
343+ if (k not in exclude ) and (k [0 ] != '_' )]
344+ attrs = sorted (set (attrs ))
345+ params = tuple ([str (self .__class__ )] + attrs )
346+ return params
347+
311348 def _params (self ):
349+ if len (self .kwds ) == 0 :
350+ return self ._cached_params
351+
312352 all_paras = dict (list (vars (self ).items ()) + list (self .kwds .items ()))
313353 if 'holidays' in all_paras and not all_paras ['holidays' ]:
314354 all_paras .pop ('holidays' )
@@ -578,6 +618,10 @@ def __setstate__(self, state):
578618 self .kwds ['holidays' ] = self .holidays = holidays
579619 self .kwds ['weekmask' ] = state ['weekmask' ]
580620
621+ if 'normalize' not in state :
622+ # default for prior pickles
623+ self .normalize = False
624+
581625
582626class BusinessDay (BusinessMixin , SingleConstructorOffset ):
583627 """
@@ -591,6 +635,7 @@ def __init__(self, n=1, normalize=False, **kwds):
591635 self .normalize = normalize
592636 self .kwds = kwds
593637 self .offset = kwds .get ('offset' , timedelta (0 ))
638+ self ._offset = None
594639
595640 @property
596641 def freqstr (self ):
@@ -709,6 +754,7 @@ def __init__(self, **kwds):
709754 self .offset = kwds .get ('offset' , timedelta (0 ))
710755 self .start = kwds .get ('start' , '09:00' )
711756 self .end = kwds .get ('end' , '17:00' )
757+ self ._offset = None
712758
713759 def _validate_time (self , t_input ):
714760 from datetime import time as dt_time
@@ -986,6 +1032,7 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
9861032 self .kwds ['weekmask' ] = self .weekmask = weekmask
9871033 self .kwds ['holidays' ] = self .holidays = holidays
9881034 self .kwds ['calendar' ] = self .calendar = calendar
1035+ self ._offset = None
9891036
9901037 def get_calendar (self , weekmask , holidays , calendar ):
9911038 """Generate busdaycalendar"""
@@ -1182,6 +1229,7 @@ def __init__(self, n=1, day_of_month=None, normalize=False, **kwds):
11821229 self .normalize = normalize
11831230 self .kwds = kwds
11841231 self .kwds ['day_of_month' ] = self .day_of_month
1232+ self ._offset = None
11851233
11861234 @classmethod
11871235 def _from_name (cls , suffix = None ):
@@ -1580,6 +1628,7 @@ def __init__(self, n=1, normalize=False, **kwds):
15801628
15811629 self ._inc = timedelta (weeks = 1 )
15821630 self .kwds = kwds
1631+ self ._offset = None
15831632
15841633 def isAnchored (self ):
15851634 return (self .n == 1 and self .weekday is not None )
@@ -1702,6 +1751,7 @@ def __init__(self, n=1, normalize=False, **kwds):
17021751 self .week )
17031752
17041753 self .kwds = kwds
1754+ self ._offset = None
17051755
17061756 @apply_wraps
17071757 def apply (self , other ):
@@ -1792,6 +1842,7 @@ def __init__(self, n=1, normalize=False, **kwds):
17921842 self .weekday )
17931843
17941844 self .kwds = kwds
1845+ self ._offset = None
17951846
17961847 @apply_wraps
17971848 def apply (self , other ):
@@ -1857,8 +1908,8 @@ def __init__(self, n=1, normalize=False, **kwds):
18571908 self .normalize = normalize
18581909 self .startingMonth = kwds .get ('startingMonth' ,
18591910 self ._default_startingMonth )
1860-
18611911 self .kwds = kwds
1912+ self ._offset = None
18621913
18631914 def isAnchored (self ):
18641915 return (self .n == 1 and self .startingMonth is not None )
@@ -1981,6 +2032,7 @@ def __init__(self, n=1, normalize=False, **kwds):
19812032 self .startingMonth = kwds .get ('startingMonth' , 3 )
19822033
19832034 self .kwds = kwds
2035+ self ._offset = None
19842036
19852037 def isAnchored (self ):
19862038 return (self .n == 1 and self .startingMonth is not None )
@@ -2306,6 +2358,7 @@ def __init__(self, n=1, normalize=False, **kwds):
23062358 self .variation = kwds ["variation" ]
23072359
23082360 self .kwds = kwds
2361+ self ._offset = None
23092362
23102363 if self .n == 0 :
23112364 raise ValueError ('N cannot be 0' )
@@ -2690,6 +2743,7 @@ def f(self, other):
26902743
26912744class Tick (SingleConstructorOffset ):
26922745 _inc = Timedelta (microseconds = 1000 )
2746+ _typ = "tick"
26932747
26942748 __gt__ = _tick_comp (operator .gt )
26952749 __ge__ = _tick_comp (operator .ge )
@@ -2741,7 +2795,7 @@ def __ne__(self, other):
27412795 else :
27422796 return DateOffset .__ne__ (self , other )
27432797
2744- @property
2798+ @cache_readonly
27452799 def delta (self ):
27462800 return self .n * self ._inc
27472801
0 commit comments