1
2
3
4
5
6
7
8
9 import re
10 from time import time, mktime
11 from datetime import datetime
12 from dateutil.relativedelta import relativedelta
13
14 search_re = re.compile(r'^([^-]+)-([^-/]+)(/(.*))?$')
15 only_int_re = re.compile(r'^\d+$')
16 any_int_re = re.compile(r'^\d+')
17 star_or_int_re = re.compile(r'^(\d+|\*)$')
18
19 __all__ = ('croniter',)
20
21
23 RANGES = (
24 (0, 59),
25 (0, 23),
26 (1, 31),
27 (1, 12),
28 (0, 6),
29 (0, 59)
30 )
31 DAYS = (
32 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
33 )
34
35 ALPHACONV = (
36 { },
37 { },
38 { },
39 { 'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6,
40 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 },
41 { 'sun':0, 'mon':1, 'tue':2, 'wed':3, 'thu':4, 'fri':5, 'sat':0 },
42 { }
43 )
44
45 LOWMAP = (
46 {},
47 {},
48 {0: 1},
49 {0: 1},
50 {7: 0},
51 {},
52 )
53
54 bad_length = 'Exactly 5 or 6 columns has to be specified for iterator' \
55 'expression.'
56
57 - def __init__(self, expr_format, start_time=time()):
58 if isinstance(start_time, datetime):
59 start_time = mktime(start_time.timetuple())
60
61 self.cur = start_time
62 self.exprs = expr_format.split()
63
64 if len(self.exprs) != 5 and len(self.exprs) != 6:
65 raise ValueError(self.bad_length)
66
67 expanded = []
68
69 for i, expr in enumerate(self.exprs):
70 e_list = expr.split(',')
71 res = []
72
73 while len(e_list) > 0:
74 e = e_list.pop()
75 t = re.sub(r'^\*(/.+)$', r'%d-%d\1' % (self.RANGES[i][0],
76 self.RANGES[i][1]),
77 str(e))
78 m = search_re.search(t)
79
80 if m:
81 (low, high, step) = m.group(1), m.group(2), m.group(4) or 1
82
83 if not any_int_re.search(low):
84 low = self.ALPHACONV[i][low.lower()]
85
86 if not any_int_re.search(high):
87 high = self.ALPHACONV[i][high.lower()]
88
89 if (not low or not high or int(low) > int(high)
90 or not only_int_re.search(str(step))):
91 raise ValueError("[%s] is not acceptable" %expr_format)
92
93 for j in xrange(int(low), int(high)+1):
94 if j % int(step) == 0:
95 e_list.append(j)
96 else:
97 if not star_or_int_re.search(t):
98 t = self.ALPHACONV[i][t.lower()]
99
100 try:
101 t = int(t)
102 except:
103 pass
104
105 if t in self.LOWMAP[i]:
106 t = self.LOWMAP[i][t]
107
108 if t != '*' and (int(t) < self.RANGES[i][0] or
109 int(t) > self.RANGES[i][1]):
110 raise ValueError("[%s] is not acceptable, out of range" % expr_format)
111
112 res.append(t)
113
114 res.sort()
115 expanded.append(['*'] if (len(res) == 1 and res[0] == '*') else res)
116 self.expanded = expanded
117
119 return self._get_next(ret_type, is_prev=False)
120
122 return self._get_next(ret_type, is_prev=True)
123
124 - def _get_next(self, ret_type=float, is_prev=False):
125 expanded = self.expanded[:]
126
127 if ret_type not in (float, datetime):
128 raise TypeError("Invalid ret_type, only 'float' or 'datetime' " \
129 "is acceptable.")
130
131 if expanded[2][0] != '*' and expanded[4][0] != '*':
132 bak = expanded[4]
133 expanded[4] = ['*']
134 t1 = self._calc(self.cur, expanded, is_prev)
135 expanded[4] = bak
136 expanded[2] = ['*']
137
138 t2 = self._calc(self.cur, expanded, is_prev)
139 if not is_prev:
140 result = t1 if t1 < t2 else t2
141 else:
142 result = t1 if t1 > t2 else t2
143 else:
144 result = self._calc(self.cur, expanded, is_prev)
145 self.cur = result
146
147 if ret_type == datetime:
148 result = datetime.fromtimestamp(result)
149 return result
150
151 - def _calc(self, now, expanded, is_prev):
152 if is_prev:
153 nearest_diff_method = self._get_prev_nearest_diff
154 sign = -1
155 else:
156 nearest_diff_method = self._get_next_nearest_diff
157 sign = 1
158
159 offset = len(expanded) == 6 and 1 or 60
160 dst = now = datetime.fromtimestamp(now + sign * offset)
161
162 day, month, year = dst.day, dst.month, dst.year
163 current_year = now.year
164 DAYS = self.DAYS
165
166 def proc_month(d):
167 if expanded[3][0] != '*':
168 diff_month = nearest_diff_method(d.month, expanded[3], 12)
169 days = DAYS[month - 1]
170 if month == 2 and self.is_leap(year) == True:
171 days += 1
172
173 reset_day = days if is_prev else 1
174
175 if diff_month != None and diff_month != 0:
176 if is_prev:
177 d += relativedelta(months=diff_month)
178 else:
179 d += relativedelta(months=diff_month, day=reset_day,
180 hour=0, minute=0, second=0)
181 return True, d
182 return False, d
183
184 def proc_day_of_month(d):
185 if expanded[2][0] != '*':
186 days = DAYS[month - 1]
187 if month == 2 and self.is_leap(year) == True:
188 days += 1
189
190 diff_day = nearest_diff_method(d.day, expanded[2], days)
191
192 if diff_day != None and diff_day != 0:
193 if is_prev:
194 d += relativedelta(days=diff_day)
195 else:
196 d += relativedelta(days=diff_day, hour=0, minute=0, second=0)
197 return True, d
198 return False, d
199
200 def proc_day_of_week(d):
201 if expanded[4][0] != '*':
202 diff_day_of_week = nearest_diff_method(d.isoweekday() % 7, expanded[4], 7)
203 if diff_day_of_week != None and diff_day_of_week != 0:
204 if is_prev:
205 d += relativedelta(days=diff_day_of_week)
206 else:
207 d += relativedelta(days=diff_day_of_week, hour=0, minute=0, second=0)
208 return True, d
209 return False, d
210
211 def proc_hour(d):
212 if expanded[1][0] != '*':
213 diff_hour = nearest_diff_method(d.hour, expanded[1], 24)
214 if diff_hour != None and diff_hour != 0:
215 if is_prev:
216 d += relativedelta(hours = diff_hour)
217 else:
218 d += relativedelta(hours = diff_hour, minute=0, second=0)
219 return True, d
220 return False, d
221
222 def proc_minute(d):
223 if expanded[0][0] != '*':
224 diff_min = nearest_diff_method(d.minute, expanded[0], 60)
225 if diff_min != None and diff_min != 0:
226 if is_prev:
227 d += relativedelta(minutes = diff_min)
228 else:
229 d += relativedelta(minutes = diff_min, second=0)
230 return True, d
231 return False, d
232
233 def proc_second(d):
234 if len(expanded) == 6:
235 if expanded[5][0] != '*':
236 diff_sec = nearest_diff_method(d.second, expanded[5], 60)
237 if diff_sec != None and diff_sec != 0:
238 d += relativedelta(seconds = diff_sec)
239 return True, d
240 else:
241 d += relativedelta(second = 0)
242 return False, d
243
244 if is_prev:
245 procs = [proc_second,
246 proc_minute,
247 proc_hour,
248 proc_day_of_week,
249 proc_day_of_month,
250 proc_month]
251 else:
252 procs = [proc_month,
253 proc_day_of_month,
254 proc_day_of_week,
255 proc_hour,
256 proc_minute,
257 proc_second]
258
259 while abs(year - current_year) <= 1:
260 next = False
261 for proc in procs:
262 (changed, dst) = proc(dst)
263 if changed:
264 next = True
265 break
266 if next:
267 continue
268 return mktime(dst.timetuple())
269
270 raise "failed to find prev date"
271
273 small = [item for item in to_check if item < x]
274 large = [item for item in to_check if item >= x]
275 large.extend(small)
276 return large[0]
277
279 small = [item for item in to_check if item <= x]
280 large = [item for item in to_check if item > x]
281 small.reverse()
282 large.reverse()
283 small.extend(large)
284 return small[0]
285
287 for i, d in enumerate(to_check):
288 if d >= x:
289 return d - x
290 return to_check[0] - x + range_val
291
293 candidates = to_check[:]
294 candidates.reverse()
295 for d in candidates:
296 if d <= x:
297 return d - x
298 return (candidates[0]) - x - range_val
299
301 if year % 400 == 0 or (year % 4 == 0 and year % 100 != 0):
302 return True
303 else:
304 return False
305
306 if __name__ == '__main__':
307
308 base = datetime(2010, 1, 25)
309 itr = croniter('0 0 1 * *', base)
310 n1 = itr.get_next(datetime)
311 print n1
312