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,32 @@ 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
+ def _determine_offset (kwds ):
144
+ # timedelta is used for sub-daily plural offsets and all singular
145
+ # offsets relativedelta is used for plural offsets of daily length or
146
+ # more nanosecond(s) are handled by apply_wraps
147
+ kwds_no_nanos = dict (
148
+ (k , v ) for k , v in kwds .items ()
149
+ if k not in ('nanosecond' , 'nanoseconds' )
150
+ )
151
+
152
+ if len (kwds_no_nanos ) > 0 :
153
+ if any (k in _kwds_use_relativedelta for k in kwds_no_nanos ):
154
+ offset = relativedelta (** kwds_no_nanos )
155
+ else :
156
+ # sub-daily offset - use timedelta (tz-aware)
157
+ offset = timedelta (** kwds_no_nanos )
158
+ else :
159
+ offset = timedelta (1 )
160
+ return offset
161
+
162
+
135
163
class DateOffset (object ):
136
164
"""
137
165
Standard kind of date increment used for a date range.
@@ -177,43 +205,37 @@ def __add__(date):
177
205
"""
178
206
_cacheable = False
179
207
_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
208
_adjust_dst = False
209
+ _typ = "dateoffset"
187
210
188
- # default for prior pickles
189
- normalize = False
211
+ def __setattr__ (self , name , value ):
212
+ # DateOffset needs to be effectively immutable in order for the
213
+ # caching in _cached_params to be correct.
214
+ frozen = ['n' , '_offset' , 'normalize' , '_inc' ]
215
+ if name in frozen and hasattr (self , name ):
216
+ msg = '%s cannot change attribute %s' % (self .__class__ , name )
217
+ raise TypeError (msg )
218
+ object .__setattr__ (self , name , value )
219
+
220
+ def __setstate__ (self , state ):
221
+ """Reconstruct an instance from a pickled state"""
222
+ self .__dict__ = state
223
+ if 'normalize' not in state and not hasattr (self , 'normalize' ):
224
+ # default for prior pickles
225
+ self .normalize = False
190
226
191
227
def __init__ (self , n = 1 , normalize = False , ** kwds ):
192
228
self .n = int (n )
193
229
self .normalize = normalize
194
230
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
231
+ self ._offset = _determine_offset (kwds )
232
+
233
+ @property
234
+ def _use_relativedelta (self ):
235
+ # We need to check for _offset existence because it may not exist
236
+ # if we are in the process of unpickling.
237
+ return (hasattr (self , '_offset' ) and
238
+ isinstance (self ._offset , relativedelta ))
217
239
218
240
@apply_wraps
219
241
def apply (self , other ):
@@ -308,7 +330,24 @@ def copy(self):
308
330
def _should_cache (self ):
309
331
return self .isAnchored () and self ._cacheable
310
332
333
+ @cache_readonly
334
+ def _cached_params (self ):
335
+ assert len (self .kwds ) == 0
336
+ all_paras = dict (list (vars (self ).items ()))
337
+ # equiv: self.__dict__.copy()
338
+ if 'holidays' in all_paras and not all_paras ['holidays' ]:
339
+ all_paras .pop ('holidays' )
340
+ exclude = ['kwds' , 'name' , 'normalize' , 'calendar' ]
341
+ attrs = [(k , v ) for k , v in all_paras .items ()
342
+ if (k not in exclude ) and (k [0 ] != '_' )]
343
+ attrs = sorted (set (attrs ))
344
+ params = tuple ([str (self .__class__ )] + attrs )
345
+ return params
346
+
311
347
def _params (self ):
348
+ if len (self .kwds ) == 0 :
349
+ return self ._cached_params
350
+
312
351
all_paras = dict (list (vars (self ).items ()) + list (self .kwds .items ()))
313
352
if 'holidays' in all_paras and not all_paras ['holidays' ]:
314
353
all_paras .pop ('holidays' )
@@ -578,6 +617,9 @@ def __setstate__(self, state):
578
617
self .kwds ['holidays' ] = self .holidays = holidays
579
618
self .kwds ['weekmask' ] = state ['weekmask' ]
580
619
620
+ if 'normalize' not in state :
621
+ # default for prior pickles
622
+ self .normalize = False
581
623
582
624
class BusinessDay (BusinessMixin , SingleConstructorOffset ):
583
625
"""
@@ -591,6 +633,7 @@ def __init__(self, n=1, normalize=False, **kwds):
591
633
self .normalize = normalize
592
634
self .kwds = kwds
593
635
self .offset = kwds .get ('offset' , timedelta (0 ))
636
+ self ._offset = None
594
637
595
638
@property
596
639
def freqstr (self ):
@@ -709,6 +752,7 @@ def __init__(self, **kwds):
709
752
self .offset = kwds .get ('offset' , timedelta (0 ))
710
753
self .start = kwds .get ('start' , '09:00' )
711
754
self .end = kwds .get ('end' , '17:00' )
755
+ self ._offset = None
712
756
713
757
def _validate_time (self , t_input ):
714
758
from datetime import time as dt_time
@@ -986,6 +1030,7 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
986
1030
self .kwds ['weekmask' ] = self .weekmask = weekmask
987
1031
self .kwds ['holidays' ] = self .holidays = holidays
988
1032
self .kwds ['calendar' ] = self .calendar = calendar
1033
+ self ._offset = None
989
1034
990
1035
def get_calendar (self , weekmask , holidays , calendar ):
991
1036
"""Generate busdaycalendar"""
@@ -1182,6 +1227,7 @@ def __init__(self, n=1, day_of_month=None, normalize=False, **kwds):
1182
1227
self .normalize = normalize
1183
1228
self .kwds = kwds
1184
1229
self .kwds ['day_of_month' ] = self .day_of_month
1230
+ self ._offset = None
1185
1231
1186
1232
@classmethod
1187
1233
def _from_name (cls , suffix = None ):
@@ -1580,6 +1626,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1580
1626
1581
1627
self ._inc = timedelta (weeks = 1 )
1582
1628
self .kwds = kwds
1629
+ self ._offset = None
1583
1630
1584
1631
def isAnchored (self ):
1585
1632
return (self .n == 1 and self .weekday is not None )
@@ -1702,6 +1749,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1702
1749
self .week )
1703
1750
1704
1751
self .kwds = kwds
1752
+ self ._offset = None
1705
1753
1706
1754
@apply_wraps
1707
1755
def apply (self , other ):
@@ -1792,6 +1840,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1792
1840
self .weekday )
1793
1841
1794
1842
self .kwds = kwds
1843
+ self ._offset = None
1795
1844
1796
1845
@apply_wraps
1797
1846
def apply (self , other ):
@@ -1857,8 +1906,8 @@ def __init__(self, n=1, normalize=False, **kwds):
1857
1906
self .normalize = normalize
1858
1907
self .startingMonth = kwds .get ('startingMonth' ,
1859
1908
self ._default_startingMonth )
1860
-
1861
1909
self .kwds = kwds
1910
+ self ._offset = None
1862
1911
1863
1912
def isAnchored (self ):
1864
1913
return (self .n == 1 and self .startingMonth is not None )
@@ -1981,6 +2030,7 @@ def __init__(self, n=1, normalize=False, **kwds):
1981
2030
self .startingMonth = kwds .get ('startingMonth' , 3 )
1982
2031
1983
2032
self .kwds = kwds
2033
+ self ._offset = None
1984
2034
1985
2035
def isAnchored (self ):
1986
2036
return (self .n == 1 and self .startingMonth is not None )
@@ -2306,6 +2356,7 @@ def __init__(self, n=1, normalize=False, **kwds):
2306
2356
self .variation = kwds ["variation" ]
2307
2357
2308
2358
self .kwds = kwds
2359
+ self ._offset = None
2309
2360
2310
2361
if self .n == 0 :
2311
2362
raise ValueError ('N cannot be 0' )
@@ -2690,6 +2741,7 @@ def f(self, other):
2690
2741
2691
2742
class Tick (SingleConstructorOffset ):
2692
2743
_inc = Timedelta (microseconds = 1000 )
2744
+ _typ = "tick"
2693
2745
2694
2746
__gt__ = _tick_comp (operator .gt )
2695
2747
__ge__ = _tick_comp (operator .ge )
@@ -2741,7 +2793,7 @@ def __ne__(self, other):
2741
2793
else :
2742
2794
return DateOffset .__ne__ (self , other )
2743
2795
2744
- @property
2796
+ @cache_readonly
2745
2797
def delta (self ):
2746
2798
return self .n * self ._inc
2747
2799
0 commit comments