10
10
# import after tools, dateutil check
11
11
from dateutil .relativedelta import relativedelta , weekday
12
12
from dateutil .easter import easter
13
+
13
14
from pandas ._libs import tslib , Timestamp , OutOfBoundsDatetime , Timedelta
15
+ from pandas ._libs .lib import cache_readonly
14
16
15
17
import functools
16
18
import operator
@@ -132,6 +134,33 @@ class CacheableOffset(object):
132
134
_cacheable = True
133
135
134
136
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
+
135
164
class DateOffset (object ):
136
165
"""
137
166
Standard kind of date increment used for a date range.
@@ -177,43 +206,37 @@ def __add__(date):
177
206
"""
178
207
_cacheable = False
179
208
_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
186
209
_adjust_dst = False
210
+ _typ = "dateoffset"
187
211
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
190
227
191
228
def __init__ (self , n = 1 , normalize = False , ** kwds ):
192
229
self .n = int (n )
193
230
self .normalize = normalize
194
231
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 ))
217
240
218
241
@apply_wraps
219
242
def apply (self , other ):
@@ -308,7 +331,24 @@ def copy(self):
308
331
def _should_cache (self ):
309
332
return self .isAnchored () and self ._cacheable
310
333
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
+
311
348
def _params (self ):
349
+ if len (self .kwds ) == 0 :
350
+ return self ._cached_params
351
+
312
352
all_paras = dict (list (vars (self ).items ()) + list (self .kwds .items ()))
313
353
if 'holidays' in all_paras and not all_paras ['holidays' ]:
314
354
all_paras .pop ('holidays' )
@@ -578,6 +618,10 @@ def __setstate__(self, state):
578
618
self .kwds ['holidays' ] = self .holidays = holidays
579
619
self .kwds ['weekmask' ] = state ['weekmask' ]
580
620
621
+ if 'normalize' not in state :
622
+ # default for prior pickles
623
+ self .normalize = False
624
+
581
625
582
626
class BusinessDay (BusinessMixin , SingleConstructorOffset ):
583
627
"""
@@ -591,6 +635,7 @@ def __init__(self, n=1, normalize=False, **kwds):
591
635
self .normalize = normalize
592
636
self .kwds = kwds
593
637
self .offset = kwds .get ('offset' , timedelta (0 ))
638
+ self ._offset = None
594
639
595
640
@property
596
641
def freqstr (self ):
@@ -709,6 +754,7 @@ def __init__(self, **kwds):
709
754
self .offset = kwds .get ('offset' , timedelta (0 ))
710
755
self .start = kwds .get ('start' , '09:00' )
711
756
self .end = kwds .get ('end' , '17:00' )
757
+ self ._offset = None
712
758
713
759
def _validate_time (self , t_input ):
714
760
from datetime import time as dt_time
@@ -986,6 +1032,7 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
986
1032
self .kwds ['weekmask' ] = self .weekmask = weekmask
987
1033
self .kwds ['holidays' ] = self .holidays = holidays
988
1034
self .kwds ['calendar' ] = self .calendar = calendar
1035
+ self ._offset = None
989
1036
990
1037
def get_calendar (self , weekmask , holidays , calendar ):
991
1038
"""Generate busdaycalendar"""
@@ -1182,6 +1229,7 @@ def __init__(self, n=1, day_of_month=None, normalize=False, **kwds):
1182
1229
self .normalize = normalize
1183
1230
self .kwds = kwds
1184
1231
self .kwds ['day_of_month' ] = self .day_of_month
1232
+ self ._offset = None
1185
1233
1186
1234
@classmethod
1187
1235
def _from_name (cls , suffix = None ):
@@ -1580,6 +1628,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1580
1628
1581
1629
self ._inc = timedelta (weeks = 1 )
1582
1630
self .kwds = kwds
1631
+ self ._offset = None
1583
1632
1584
1633
def isAnchored (self ):
1585
1634
return (self .n == 1 and self .weekday is not None )
@@ -1702,6 +1751,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1702
1751
self .week )
1703
1752
1704
1753
self .kwds = kwds
1754
+ self ._offset = None
1705
1755
1706
1756
@apply_wraps
1707
1757
def apply (self , other ):
@@ -1792,6 +1842,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1792
1842
self .weekday )
1793
1843
1794
1844
self .kwds = kwds
1845
+ self ._offset = None
1795
1846
1796
1847
@apply_wraps
1797
1848
def apply (self , other ):
@@ -1857,8 +1908,8 @@ def __init__(self, n=1, normalize=False, **kwds):
1857
1908
self .normalize = normalize
1858
1909
self .startingMonth = kwds .get ('startingMonth' ,
1859
1910
self ._default_startingMonth )
1860
-
1861
1911
self .kwds = kwds
1912
+ self ._offset = None
1862
1913
1863
1914
def isAnchored (self ):
1864
1915
return (self .n == 1 and self .startingMonth is not None )
@@ -1981,6 +2032,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1981
2032
self .startingMonth = kwds .get ('startingMonth' , 3 )
1982
2033
1983
2034
self .kwds = kwds
2035
+ self ._offset = None
1984
2036
1985
2037
def isAnchored (self ):
1986
2038
return (self .n == 1 and self .startingMonth is not None )
@@ -2306,6 +2358,7 @@ def __init__(self, n=1, normalize=False, **kwds):
2306
2358
self .variation = kwds ["variation" ]
2307
2359
2308
2360
self .kwds = kwds
2361
+ self ._offset = None
2309
2362
2310
2363
if self .n == 0 :
2311
2364
raise ValueError ('N cannot be 0' )
@@ -2690,6 +2743,7 @@ def f(self, other):
2690
2743
2691
2744
class Tick (SingleConstructorOffset ):
2692
2745
_inc = Timedelta (microseconds = 1000 )
2746
+ _typ = "tick"
2693
2747
2694
2748
__gt__ = _tick_comp (operator .gt )
2695
2749
__ge__ = _tick_comp (operator .ge )
@@ -2741,7 +2795,7 @@ def __ne__(self, other):
2741
2795
else :
2742
2796
return DateOffset .__ne__ (self , other )
2743
2797
2744
- @property
2798
+ @cache_readonly
2745
2799
def delta (self ):
2746
2800
return self .n * self ._inc
2747
2801
0 commit comments