rrule.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. """
  2. Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
  3. This module offers extensions to the standard python 2.3+
  4. datetime module.
  5. """
  6. __author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
  7. __license__ = "PSF License"
  8. import itertools
  9. import datetime
  10. import calendar
  11. import thread
  12. import sys
  13. __all__ = ["rrule", "rruleset", "rrulestr",
  14. "YEARLY", "MONTHLY", "WEEKLY", "DAILY",
  15. "HOURLY", "MINUTELY", "SECONDLY",
  16. "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
  17. # Every mask is 7 days longer to handle cross-year weekly periods.
  18. M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30+
  19. [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
  20. M365MASK = list(M366MASK)
  21. M29, M30, M31 = range(1,30), range(1,31), range(1,32)
  22. MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
  23. MDAY365MASK = list(MDAY366MASK)
  24. M29, M30, M31 = range(-29,0), range(-30,0), range(-31,0)
  25. NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
  26. NMDAY365MASK = list(NMDAY366MASK)
  27. M366RANGE = (0,31,60,91,121,152,182,213,244,274,305,335,366)
  28. M365RANGE = (0,31,59,90,120,151,181,212,243,273,304,334,365)
  29. WDAYMASK = [0,1,2,3,4,5,6]*55
  30. del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
  31. MDAY365MASK = tuple(MDAY365MASK)
  32. M365MASK = tuple(M365MASK)
  33. (YEARLY,
  34. MONTHLY,
  35. WEEKLY,
  36. DAILY,
  37. HOURLY,
  38. MINUTELY,
  39. SECONDLY) = range(7)
  40. # Imported on demand.
  41. easter = None
  42. parser = None
  43. class weekday(object):
  44. __slots__ = ["weekday", "n"]
  45. def __init__(self, weekday, n=None):
  46. if n == 0:
  47. raise ValueError, "Can't create weekday with n == 0"
  48. self.weekday = weekday
  49. self.n = n
  50. def __call__(self, n):
  51. if n == self.n:
  52. return self
  53. else:
  54. return self.__class__(self.weekday, n)
  55. def __eq__(self, other):
  56. try:
  57. if self.weekday != other.weekday or self.n != other.n:
  58. return False
  59. except AttributeError:
  60. return False
  61. return True
  62. def __repr__(self):
  63. s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
  64. if not self.n:
  65. return s
  66. else:
  67. return "%s(%+d)" % (s, self.n)
  68. MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
  69. class rrulebase:
  70. def __init__(self, cache=False):
  71. if cache:
  72. self._cache = []
  73. self._cache_lock = thread.allocate_lock()
  74. self._cache_gen = self._iter()
  75. self._cache_complete = False
  76. else:
  77. self._cache = None
  78. self._cache_complete = False
  79. self._len = None
  80. def __iter__(self):
  81. if self._cache_complete:
  82. return iter(self._cache)
  83. elif self._cache is None:
  84. return self._iter()
  85. else:
  86. return self._iter_cached()
  87. def _iter_cached(self):
  88. i = 0
  89. gen = self._cache_gen
  90. cache = self._cache
  91. acquire = self._cache_lock.acquire
  92. release = self._cache_lock.release
  93. while gen:
  94. if i == len(cache):
  95. acquire()
  96. if self._cache_complete:
  97. break
  98. try:
  99. for j in range(10):
  100. cache.append(gen.next())
  101. except StopIteration:
  102. self._cache_gen = gen = None
  103. self._cache_complete = True
  104. break
  105. release()
  106. yield cache[i]
  107. i += 1
  108. while i < self._len:
  109. yield cache[i]
  110. i += 1
  111. def __getitem__(self, item):
  112. if self._cache_complete:
  113. return self._cache[item]
  114. elif isinstance(item, slice):
  115. if item.step and item.step < 0:
  116. return list(iter(self))[item]
  117. else:
  118. return list(itertools.islice(self,
  119. item.start or 0,
  120. item.stop or sys.maxint,
  121. item.step or 1))
  122. elif item >= 0:
  123. gen = iter(self)
  124. try:
  125. for i in range(item+1):
  126. res = gen.next()
  127. except StopIteration:
  128. raise IndexError
  129. return res
  130. else:
  131. return list(iter(self))[item]
  132. def __contains__(self, item):
  133. if self._cache_complete:
  134. return item in self._cache
  135. else:
  136. for i in self:
  137. if i == item:
  138. return True
  139. elif i > item:
  140. return False
  141. return False
  142. # __len__() introduces a large performance penality.
  143. def count(self):
  144. if self._len is None:
  145. for x in self: pass
  146. return self._len
  147. def before(self, dt, inc=False):
  148. if self._cache_complete:
  149. gen = self._cache
  150. else:
  151. gen = self
  152. last = None
  153. if inc:
  154. for i in gen:
  155. if i > dt:
  156. break
  157. last = i
  158. else:
  159. for i in gen:
  160. if i >= dt:
  161. break
  162. last = i
  163. return last
  164. def after(self, dt, inc=False):
  165. if self._cache_complete:
  166. gen = self._cache
  167. else:
  168. gen = self
  169. if inc:
  170. for i in gen:
  171. if i >= dt:
  172. return i
  173. else:
  174. for i in gen:
  175. if i > dt:
  176. return i
  177. return None
  178. def between(self, after, before, inc=False):
  179. if self._cache_complete:
  180. gen = self._cache
  181. else:
  182. gen = self
  183. started = False
  184. l = []
  185. if inc:
  186. for i in gen:
  187. if i > before:
  188. break
  189. elif not started:
  190. if i >= after:
  191. started = True
  192. l.append(i)
  193. else:
  194. l.append(i)
  195. else:
  196. for i in gen:
  197. if i >= before:
  198. break
  199. elif not started:
  200. if i > after:
  201. started = True
  202. l.append(i)
  203. else:
  204. l.append(i)
  205. return l
  206. class rrule(rrulebase):
  207. def __init__(self, freq, dtstart=None,
  208. interval=1, wkst=None, count=None, until=None, bysetpos=None,
  209. bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
  210. byweekno=None, byweekday=None,
  211. byhour=None, byminute=None, bysecond=None,
  212. cache=False):
  213. rrulebase.__init__(self, cache)
  214. global easter
  215. if not dtstart:
  216. dtstart = datetime.datetime.now().replace(microsecond=0)
  217. elif not isinstance(dtstart, datetime.datetime):
  218. dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
  219. else:
  220. dtstart = dtstart.replace(microsecond=0)
  221. self._dtstart = dtstart
  222. self._tzinfo = dtstart.tzinfo
  223. self._freq = freq
  224. self._interval = interval
  225. self._count = count
  226. if until and not isinstance(until, datetime.datetime):
  227. until = datetime.datetime.fromordinal(until.toordinal())
  228. self._until = until
  229. if wkst is None:
  230. self._wkst = calendar.firstweekday()
  231. elif type(wkst) is int:
  232. self._wkst = wkst
  233. else:
  234. self._wkst = wkst.weekday
  235. if bysetpos is None:
  236. self._bysetpos = None
  237. elif type(bysetpos) is int:
  238. if bysetpos == 0 or not (-366 <= bysetpos <= 366):
  239. raise ValueError("bysetpos must be between 1 and 366, "
  240. "or between -366 and -1")
  241. self._bysetpos = (bysetpos,)
  242. else:
  243. self._bysetpos = tuple(bysetpos)
  244. for pos in self._bysetpos:
  245. if pos == 0 or not (-366 <= pos <= 366):
  246. raise ValueError("bysetpos must be between 1 and 366, "
  247. "or between -366 and -1")
  248. if not (byweekno or byyearday or bymonthday or
  249. byweekday is not None or byeaster is not None):
  250. if freq == YEARLY:
  251. if not bymonth:
  252. bymonth = dtstart.month
  253. bymonthday = dtstart.day
  254. elif freq == MONTHLY:
  255. bymonthday = dtstart.day
  256. elif freq == WEEKLY:
  257. byweekday = dtstart.weekday()
  258. # bymonth
  259. if not bymonth:
  260. self._bymonth = None
  261. elif type(bymonth) is int:
  262. self._bymonth = (bymonth,)
  263. else:
  264. self._bymonth = tuple(bymonth)
  265. # byyearday
  266. if not byyearday:
  267. self._byyearday = None
  268. elif type(byyearday) is int:
  269. self._byyearday = (byyearday,)
  270. else:
  271. self._byyearday = tuple(byyearday)
  272. # byeaster
  273. if byeaster is not None:
  274. if not easter:
  275. from dateutil import easter
  276. if type(byeaster) is int:
  277. self._byeaster = (byeaster,)
  278. else:
  279. self._byeaster = tuple(byeaster)
  280. else:
  281. self._byeaster = None
  282. # bymonthay
  283. if not bymonthday:
  284. self._bymonthday = ()
  285. self._bynmonthday = ()
  286. elif type(bymonthday) is int:
  287. if bymonthday < 0:
  288. self._bynmonthday = (bymonthday,)
  289. self._bymonthday = ()
  290. else:
  291. self._bymonthday = (bymonthday,)
  292. self._bynmonthday = ()
  293. else:
  294. self._bymonthday = tuple([x for x in bymonthday if x > 0])
  295. self._bynmonthday = tuple([x for x in bymonthday if x < 0])
  296. # byweekno
  297. if byweekno is None:
  298. self._byweekno = None
  299. elif type(byweekno) is int:
  300. self._byweekno = (byweekno,)
  301. else:
  302. self._byweekno = tuple(byweekno)
  303. # byweekday / bynweekday
  304. if byweekday is None:
  305. self._byweekday = None
  306. self._bynweekday = None
  307. elif type(byweekday) is int:
  308. self._byweekday = (byweekday,)
  309. self._bynweekday = None
  310. elif hasattr(byweekday, "n"):
  311. if not byweekday.n or freq > MONTHLY:
  312. self._byweekday = (byweekday.weekday,)
  313. self._bynweekday = None
  314. else:
  315. self._bynweekday = ((byweekday.weekday, byweekday.n),)
  316. self._byweekday = None
  317. else:
  318. self._byweekday = []
  319. self._bynweekday = []
  320. for wday in byweekday:
  321. if type(wday) is int:
  322. self._byweekday.append(wday)
  323. elif not wday.n or freq > MONTHLY:
  324. self._byweekday.append(wday.weekday)
  325. else:
  326. self._bynweekday.append((wday.weekday, wday.n))
  327. self._byweekday = tuple(self._byweekday)
  328. self._bynweekday = tuple(self._bynweekday)
  329. if not self._byweekday:
  330. self._byweekday = None
  331. elif not self._bynweekday:
  332. self._bynweekday = None
  333. # byhour
  334. if byhour is None:
  335. if freq < HOURLY:
  336. self._byhour = (dtstart.hour,)
  337. else:
  338. self._byhour = None
  339. elif type(byhour) is int:
  340. self._byhour = (byhour,)
  341. else:
  342. self._byhour = tuple(byhour)
  343. # byminute
  344. if byminute is None:
  345. if freq < MINUTELY:
  346. self._byminute = (dtstart.minute,)
  347. else:
  348. self._byminute = None
  349. elif type(byminute) is int:
  350. self._byminute = (byminute,)
  351. else:
  352. self._byminute = tuple(byminute)
  353. # bysecond
  354. if bysecond is None:
  355. if freq < SECONDLY:
  356. self._bysecond = (dtstart.second,)
  357. else:
  358. self._bysecond = None
  359. elif type(bysecond) is int:
  360. self._bysecond = (bysecond,)
  361. else:
  362. self._bysecond = tuple(bysecond)
  363. if self._freq >= HOURLY:
  364. self._timeset = None
  365. else:
  366. self._timeset = []
  367. for hour in self._byhour:
  368. for minute in self._byminute:
  369. for second in self._bysecond:
  370. self._timeset.append(
  371. datetime.time(hour, minute, second,
  372. tzinfo=self._tzinfo))
  373. self._timeset.sort()
  374. self._timeset = tuple(self._timeset)
  375. def _iter(self):
  376. year, month, day, hour, minute, second, weekday, yearday, _ = \
  377. self._dtstart.timetuple()
  378. # Some local variables to speed things up a bit
  379. freq = self._freq
  380. interval = self._interval
  381. wkst = self._wkst
  382. until = self._until
  383. bymonth = self._bymonth
  384. byweekno = self._byweekno
  385. byyearday = self._byyearday
  386. byweekday = self._byweekday
  387. byeaster = self._byeaster
  388. bymonthday = self._bymonthday
  389. bynmonthday = self._bynmonthday
  390. bysetpos = self._bysetpos
  391. byhour = self._byhour
  392. byminute = self._byminute
  393. bysecond = self._bysecond
  394. ii = _iterinfo(self)
  395. ii.rebuild(year, month)
  396. getdayset = {YEARLY:ii.ydayset,
  397. MONTHLY:ii.mdayset,
  398. WEEKLY:ii.wdayset,
  399. DAILY:ii.ddayset,
  400. HOURLY:ii.ddayset,
  401. MINUTELY:ii.ddayset,
  402. SECONDLY:ii.ddayset}[freq]
  403. if freq < HOURLY:
  404. timeset = self._timeset
  405. else:
  406. gettimeset = {HOURLY:ii.htimeset,
  407. MINUTELY:ii.mtimeset,
  408. SECONDLY:ii.stimeset}[freq]
  409. if ((freq >= HOURLY and
  410. self._byhour and hour not in self._byhour) or
  411. (freq >= MINUTELY and
  412. self._byminute and minute not in self._byminute) or
  413. (freq >= SECONDLY and
  414. self._bysecond and second not in self._bysecond)):
  415. timeset = ()
  416. else:
  417. timeset = gettimeset(hour, minute, second)
  418. total = 0
  419. count = self._count
  420. while True:
  421. # Get dayset with the right frequency
  422. dayset, start, end = getdayset(year, month, day)
  423. # Do the "hard" work ;-)
  424. filtered = False
  425. for i in dayset[start:end]:
  426. if ((bymonth and ii.mmask[i] not in bymonth) or
  427. (byweekno and not ii.wnomask[i]) or
  428. (byweekday and ii.wdaymask[i] not in byweekday) or
  429. (ii.nwdaymask and not ii.nwdaymask[i]) or
  430. (byeaster and not ii.eastermask[i]) or
  431. ((bymonthday or bynmonthday) and
  432. ii.mdaymask[i] not in bymonthday and
  433. ii.nmdaymask[i] not in bynmonthday) or
  434. (byyearday and
  435. ((i < ii.yearlen and i+1 not in byyearday
  436. and -ii.yearlen+i not in byyearday) or
  437. (i >= ii.yearlen and i+1-ii.yearlen not in byyearday
  438. and -ii.nextyearlen+i-ii.yearlen
  439. not in byyearday)))):
  440. dayset[i] = None
  441. filtered = True
  442. # Output results
  443. if bysetpos and timeset:
  444. poslist = []
  445. for pos in bysetpos:
  446. if pos < 0:
  447. daypos, timepos = divmod(pos, len(timeset))
  448. else:
  449. daypos, timepos = divmod(pos-1, len(timeset))
  450. try:
  451. i = [x for x in dayset[start:end]
  452. if x is not None][daypos]
  453. time = timeset[timepos]
  454. except IndexError:
  455. pass
  456. else:
  457. date = datetime.date.fromordinal(ii.yearordinal+i)
  458. res = datetime.datetime.combine(date, time)
  459. if res not in poslist:
  460. poslist.append(res)
  461. poslist.sort()
  462. for res in poslist:
  463. if until and res > until:
  464. self._len = total
  465. return
  466. elif res >= self._dtstart:
  467. total += 1
  468. yield res
  469. if count:
  470. count -= 1
  471. if not count:
  472. self._len = total
  473. return
  474. else:
  475. for i in dayset[start:end]:
  476. if i is not None:
  477. date = datetime.date.fromordinal(ii.yearordinal+i)
  478. for time in timeset:
  479. res = datetime.datetime.combine(date, time)
  480. if until and res > until:
  481. self._len = total
  482. return
  483. elif res >= self._dtstart:
  484. total += 1
  485. yield res
  486. if count:
  487. count -= 1
  488. if not count:
  489. self._len = total
  490. return
  491. # Handle frequency and interval
  492. fixday = False
  493. if freq == YEARLY:
  494. year += interval
  495. if year > datetime.MAXYEAR:
  496. self._len = total
  497. return
  498. ii.rebuild(year, month)
  499. elif freq == MONTHLY:
  500. month += interval
  501. if month > 12:
  502. div, mod = divmod(month, 12)
  503. month = mod
  504. year += div
  505. if month == 0:
  506. month = 12
  507. year -= 1
  508. if year > datetime.MAXYEAR:
  509. self._len = total
  510. return
  511. ii.rebuild(year, month)
  512. elif freq == WEEKLY:
  513. if wkst > weekday:
  514. day += -(weekday+1+(6-wkst))+self._interval*7
  515. else:
  516. day += -(weekday-wkst)+self._interval*7
  517. weekday = wkst
  518. fixday = True
  519. elif freq == DAILY:
  520. day += interval
  521. fixday = True
  522. elif freq == HOURLY:
  523. if filtered:
  524. # Jump to one iteration before next day
  525. hour += ((23-hour)//interval)*interval
  526. while True:
  527. hour += interval
  528. div, mod = divmod(hour, 24)
  529. if div:
  530. hour = mod
  531. day += div
  532. fixday = True
  533. if not byhour or hour in byhour:
  534. break
  535. timeset = gettimeset(hour, minute, second)
  536. elif freq == MINUTELY:
  537. if filtered:
  538. # Jump to one iteration before next day
  539. minute += ((1439-(hour*60+minute))//interval)*interval
  540. while True:
  541. minute += interval
  542. div, mod = divmod(minute, 60)
  543. if div:
  544. minute = mod
  545. hour += div
  546. div, mod = divmod(hour, 24)
  547. if div:
  548. hour = mod
  549. day += div
  550. fixday = True
  551. filtered = False
  552. if ((not byhour or hour in byhour) and
  553. (not byminute or minute in byminute)):
  554. break
  555. timeset = gettimeset(hour, minute, second)
  556. elif freq == SECONDLY:
  557. if filtered:
  558. # Jump to one iteration before next day
  559. second += (((86399-(hour*3600+minute*60+second))
  560. //interval)*interval)
  561. while True:
  562. second += self._interval
  563. div, mod = divmod(second, 60)
  564. if div:
  565. second = mod
  566. minute += div
  567. div, mod = divmod(minute, 60)
  568. if div:
  569. minute = mod
  570. hour += div
  571. div, mod = divmod(hour, 24)
  572. if div:
  573. hour = mod
  574. day += div
  575. fixday = True
  576. if ((not byhour or hour in byhour) and
  577. (not byminute or minute in byminute) and
  578. (not bysecond or second in bysecond)):
  579. break
  580. timeset = gettimeset(hour, minute, second)
  581. if fixday and day > 28:
  582. daysinmonth = calendar.monthrange(year, month)[1]
  583. if day > daysinmonth:
  584. while day > daysinmonth:
  585. day -= daysinmonth
  586. month += 1
  587. if month == 13:
  588. month = 1
  589. year += 1
  590. if year > datetime.MAXYEAR:
  591. self._len = total
  592. return
  593. daysinmonth = calendar.monthrange(year, month)[1]
  594. ii.rebuild(year, month)
  595. class _iterinfo(object):
  596. __slots__ = ["rrule", "lastyear", "lastmonth",
  597. "yearlen", "nextyearlen", "yearordinal", "yearweekday",
  598. "mmask", "mrange", "mdaymask", "nmdaymask",
  599. "wdaymask", "wnomask", "nwdaymask", "eastermask"]
  600. def __init__(self, rrule):
  601. for attr in self.__slots__:
  602. setattr(self, attr, None)
  603. self.rrule = rrule
  604. def rebuild(self, year, month):
  605. # Every mask is 7 days longer to handle cross-year weekly periods.
  606. rr = self.rrule
  607. if year != self.lastyear:
  608. self.yearlen = 365+calendar.isleap(year)
  609. self.nextyearlen = 365+calendar.isleap(year+1)
  610. firstyday = datetime.date(year, 1, 1)
  611. self.yearordinal = firstyday.toordinal()
  612. self.yearweekday = firstyday.weekday()
  613. wday = datetime.date(year, 1, 1).weekday()
  614. if self.yearlen == 365:
  615. self.mmask = M365MASK
  616. self.mdaymask = MDAY365MASK
  617. self.nmdaymask = NMDAY365MASK
  618. self.wdaymask = WDAYMASK[wday:]
  619. self.mrange = M365RANGE
  620. else:
  621. self.mmask = M366MASK
  622. self.mdaymask = MDAY366MASK
  623. self.nmdaymask = NMDAY366MASK
  624. self.wdaymask = WDAYMASK[wday:]
  625. self.mrange = M366RANGE
  626. if not rr._byweekno:
  627. self.wnomask = None
  628. else:
  629. self.wnomask = [0]*(self.yearlen+7)
  630. #no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
  631. no1wkst = firstwkst = (7-self.yearweekday+rr._wkst)%7
  632. if no1wkst >= 4:
  633. no1wkst = 0
  634. # Number of days in the year, plus the days we got
  635. # from last year.
  636. wyearlen = self.yearlen+(self.yearweekday-rr._wkst)%7
  637. else:
  638. # Number of days in the year, minus the days we
  639. # left in last year.
  640. wyearlen = self.yearlen-no1wkst
  641. div, mod = divmod(wyearlen, 7)
  642. numweeks = div+mod//4
  643. for n in rr._byweekno:
  644. if n < 0:
  645. n += numweeks+1
  646. if not (0 < n <= numweeks):
  647. continue
  648. if n > 1:
  649. i = no1wkst+(n-1)*7
  650. if no1wkst != firstwkst:
  651. i -= 7-firstwkst
  652. else:
  653. i = no1wkst
  654. for j in range(7):
  655. self.wnomask[i] = 1
  656. i += 1
  657. if self.wdaymask[i] == rr._wkst:
  658. break
  659. if 1 in rr._byweekno:
  660. # Check week number 1 of next year as well
  661. # TODO: Check -numweeks for next year.
  662. i = no1wkst+numweeks*7
  663. if no1wkst != firstwkst:
  664. i -= 7-firstwkst
  665. if i < self.yearlen:
  666. # If week starts in next year, we
  667. # don't care about it.
  668. for j in range(7):
  669. self.wnomask[i] = 1
  670. i += 1
  671. if self.wdaymask[i] == rr._wkst:
  672. break
  673. if no1wkst:
  674. # Check last week number of last year as
  675. # well. If no1wkst is 0, either the year
  676. # started on week start, or week number 1
  677. # got days from last year, so there are no
  678. # days from last year's last week number in
  679. # this year.
  680. if -1 not in rr._byweekno:
  681. lyearweekday = datetime.date(year-1,1,1).weekday()
  682. lno1wkst = (7-lyearweekday+rr._wkst)%7
  683. lyearlen = 365+calendar.isleap(year-1)
  684. if lno1wkst >= 4:
  685. lno1wkst = 0
  686. lnumweeks = 52+(lyearlen+
  687. (lyearweekday-rr._wkst)%7)%7//4
  688. else:
  689. lnumweeks = 52+(self.yearlen-no1wkst)%7//4
  690. else:
  691. lnumweeks = -1
  692. if lnumweeks in rr._byweekno:
  693. for i in range(no1wkst):
  694. self.wnomask[i] = 1
  695. if (rr._bynweekday and
  696. (month != self.lastmonth or year != self.lastyear)):
  697. ranges = []
  698. if rr._freq == YEARLY:
  699. if rr._bymonth:
  700. for month in rr._bymonth:
  701. ranges.append(self.mrange[month-1:month+1])
  702. else:
  703. ranges = [(0, self.yearlen)]
  704. elif rr._freq == MONTHLY:
  705. ranges = [self.mrange[month-1:month+1]]
  706. if ranges:
  707. # Weekly frequency won't get here, so we may not
  708. # care about cross-year weekly periods.
  709. self.nwdaymask = [0]*self.yearlen
  710. for first, last in ranges:
  711. last -= 1
  712. for wday, n in rr._bynweekday:
  713. if n < 0:
  714. i = last+(n+1)*7
  715. i -= (self.wdaymask[i]-wday)%7
  716. else:
  717. i = first+(n-1)*7
  718. i += (7-self.wdaymask[i]+wday)%7
  719. if first <= i <= last:
  720. self.nwdaymask[i] = 1
  721. if rr._byeaster:
  722. self.eastermask = [0]*(self.yearlen+7)
  723. eyday = easter.easter(year).toordinal()-self.yearordinal
  724. for offset in rr._byeaster:
  725. self.eastermask[eyday+offset] = 1
  726. self.lastyear = year
  727. self.lastmonth = month
  728. def ydayset(self, year, month, day):
  729. return range(self.yearlen), 0, self.yearlen
  730. def mdayset(self, year, month, day):
  731. set = [None]*self.yearlen
  732. start, end = self.mrange[month-1:month+1]
  733. for i in range(start, end):
  734. set[i] = i
  735. return set, start, end
  736. def wdayset(self, year, month, day):
  737. # We need to handle cross-year weeks here.
  738. set = [None]*(self.yearlen+7)
  739. i = datetime.date(year, month, day).toordinal()-self.yearordinal
  740. start = i
  741. for j in range(7):
  742. set[i] = i
  743. i += 1
  744. #if (not (0 <= i < self.yearlen) or
  745. # self.wdaymask[i] == self.rrule._wkst):
  746. # This will cross the year boundary, if necessary.
  747. if self.wdaymask[i] == self.rrule._wkst:
  748. break
  749. return set, start, i
  750. def ddayset(self, year, month, day):
  751. set = [None]*self.yearlen
  752. i = datetime.date(year, month, day).toordinal()-self.yearordinal
  753. set[i] = i
  754. return set, i, i+1
  755. def htimeset(self, hour, minute, second):
  756. set = []
  757. rr = self.rrule
  758. for minute in rr._byminute:
  759. for second in rr._bysecond:
  760. set.append(datetime.time(hour, minute, second,
  761. tzinfo=rr._tzinfo))
  762. set.sort()
  763. return set
  764. def mtimeset(self, hour, minute, second):
  765. set = []
  766. rr = self.rrule
  767. for second in rr._bysecond:
  768. set.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
  769. set.sort()
  770. return set
  771. def stimeset(self, hour, minute, second):
  772. return (datetime.time(hour, minute, second,
  773. tzinfo=self.rrule._tzinfo),)
  774. class rruleset(rrulebase):
  775. class _genitem:
  776. def __init__(self, genlist, gen):
  777. try:
  778. self.dt = gen()
  779. genlist.append(self)
  780. except StopIteration:
  781. pass
  782. self.genlist = genlist
  783. self.gen = gen
  784. def next(self):
  785. try:
  786. self.dt = self.gen()
  787. except StopIteration:
  788. self.genlist.remove(self)
  789. def __cmp__(self, other):
  790. return cmp(self.dt, other.dt)
  791. def __init__(self, cache=False):
  792. rrulebase.__init__(self, cache)
  793. self._rrule = []
  794. self._rdate = []
  795. self._exrule = []
  796. self._exdate = []
  797. def rrule(self, rrule):
  798. self._rrule.append(rrule)
  799. def rdate(self, rdate):
  800. self._rdate.append(rdate)
  801. def exrule(self, exrule):
  802. self._exrule.append(exrule)
  803. def exdate(self, exdate):
  804. self._exdate.append(exdate)
  805. def _iter(self):
  806. rlist = []
  807. self._rdate.sort()
  808. self._genitem(rlist, iter(self._rdate).next)
  809. for gen in [iter(x).next for x in self._rrule]:
  810. self._genitem(rlist, gen)
  811. rlist.sort()
  812. exlist = []
  813. self._exdate.sort()
  814. self._genitem(exlist, iter(self._exdate).next)
  815. for gen in [iter(x).next for x in self._exrule]:
  816. self._genitem(exlist, gen)
  817. exlist.sort()
  818. lastdt = None
  819. total = 0
  820. while rlist:
  821. ritem = rlist[0]
  822. if not lastdt or lastdt != ritem.dt:
  823. while exlist and exlist[0] < ritem:
  824. exlist[0].next()
  825. exlist.sort()
  826. if not exlist or ritem != exlist[0]:
  827. total += 1
  828. yield ritem.dt
  829. lastdt = ritem.dt
  830. ritem.next()
  831. rlist.sort()
  832. self._len = total
  833. class _rrulestr:
  834. _freq_map = {"YEARLY": YEARLY,
  835. "MONTHLY": MONTHLY,
  836. "WEEKLY": WEEKLY,
  837. "DAILY": DAILY,
  838. "HOURLY": HOURLY,
  839. "MINUTELY": MINUTELY,
  840. "SECONDLY": SECONDLY}
  841. _weekday_map = {"MO":0,"TU":1,"WE":2,"TH":3,"FR":4,"SA":5,"SU":6}
  842. def _handle_int(self, rrkwargs, name, value, **kwargs):
  843. rrkwargs[name.lower()] = int(value)
  844. def _handle_int_list(self, rrkwargs, name, value, **kwargs):
  845. rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
  846. _handle_INTERVAL = _handle_int
  847. _handle_COUNT = _handle_int
  848. _handle_BYSETPOS = _handle_int_list
  849. _handle_BYMONTH = _handle_int_list
  850. _handle_BYMONTHDAY = _handle_int_list
  851. _handle_BYYEARDAY = _handle_int_list
  852. _handle_BYEASTER = _handle_int_list
  853. _handle_BYWEEKNO = _handle_int_list
  854. _handle_BYHOUR = _handle_int_list
  855. _handle_BYMINUTE = _handle_int_list
  856. _handle_BYSECOND = _handle_int_list
  857. def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
  858. rrkwargs["freq"] = self._freq_map[value]
  859. def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
  860. global parser
  861. if not parser:
  862. from dateutil import parser
  863. try:
  864. rrkwargs["until"] = parser.parse(value,
  865. ignoretz=kwargs.get("ignoretz"),
  866. tzinfos=kwargs.get("tzinfos"))
  867. except ValueError:
  868. raise ValueError, "invalid until date"
  869. def _handle_WKST(self, rrkwargs, name, value, **kwargs):
  870. rrkwargs["wkst"] = self._weekday_map[value]
  871. def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg):
  872. l = []
  873. for wday in value.split(','):
  874. for i in range(len(wday)):
  875. if wday[i] not in '+-0123456789':
  876. break
  877. n = wday[:i] or None
  878. w = wday[i:]
  879. if n: n = int(n)
  880. l.append(weekdays[self._weekday_map[w]](n))
  881. rrkwargs["byweekday"] = l
  882. _handle_BYDAY = _handle_BYWEEKDAY
  883. def _parse_rfc_rrule(self, line,
  884. dtstart=None,
  885. cache=False,
  886. ignoretz=False,
  887. tzinfos=None):
  888. if line.find(':') != -1:
  889. name, value = line.split(':')
  890. if name != "RRULE":
  891. raise ValueError, "unknown parameter name"
  892. else:
  893. value = line
  894. rrkwargs = {}
  895. for pair in value.split(';'):
  896. name, value = pair.split('=')
  897. name = name.upper()
  898. value = value.upper()
  899. try:
  900. getattr(self, "_handle_"+name)(rrkwargs, name, value,
  901. ignoretz=ignoretz,
  902. tzinfos=tzinfos)
  903. except AttributeError:
  904. raise ValueError, "unknown parameter '%s'" % name
  905. except (KeyError, ValueError):
  906. raise ValueError, "invalid '%s': %s" % (name, value)
  907. return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
  908. def _parse_rfc(self, s,
  909. dtstart=None,
  910. cache=False,
  911. unfold=False,
  912. forceset=False,
  913. compatible=False,
  914. ignoretz=False,
  915. tzinfos=None):
  916. global parser
  917. if compatible:
  918. forceset = True
  919. unfold = True
  920. s = s.upper()
  921. if not s.strip():
  922. raise ValueError, "empty string"
  923. if unfold:
  924. lines = s.splitlines()
  925. i = 0
  926. while i < len(lines):
  927. line = lines[i].rstrip()
  928. if not line:
  929. del lines[i]
  930. elif i > 0 and line[0] == " ":
  931. lines[i-1] += line[1:]
  932. del lines[i]
  933. else:
  934. i += 1
  935. else:
  936. lines = s.split()
  937. if (not forceset and len(lines) == 1 and
  938. (s.find(':') == -1 or s.startswith('RRULE:'))):
  939. return self._parse_rfc_rrule(lines[0], cache=cache,
  940. dtstart=dtstart, ignoretz=ignoretz,
  941. tzinfos=tzinfos)
  942. else:
  943. rrulevals = []
  944. rdatevals = []
  945. exrulevals = []
  946. exdatevals = []
  947. for line in lines:
  948. if not line:
  949. continue
  950. if line.find(':') == -1:
  951. name = "RRULE"
  952. value = line
  953. else:
  954. name, value = line.split(':', 1)
  955. parms = name.split(';')
  956. if not parms:
  957. raise ValueError, "empty property name"
  958. name = parms[0]
  959. parms = parms[1:]
  960. if name == "RRULE":
  961. for parm in parms:
  962. raise ValueError, "unsupported RRULE parm: "+parm
  963. rrulevals.append(value)
  964. elif name == "RDATE":
  965. for parm in parms:
  966. if parm != "VALUE=DATE-TIME":
  967. raise ValueError, "unsupported RDATE parm: "+parm
  968. rdatevals.append(value)
  969. elif name == "EXRULE":
  970. for parm in parms:
  971. raise ValueError, "unsupported EXRULE parm: "+parm
  972. exrulevals.append(value)
  973. elif name == "EXDATE":
  974. for parm in parms:
  975. if parm != "VALUE=DATE-TIME":
  976. raise ValueError, "unsupported RDATE parm: "+parm
  977. exdatevals.append(value)
  978. elif name == "DTSTART":
  979. for parm in parms:
  980. raise ValueError, "unsupported DTSTART parm: "+parm
  981. if not parser:
  982. from dateutil import parser
  983. dtstart = parser.parse(value, ignoretz=ignoretz,
  984. tzinfos=tzinfos)
  985. else:
  986. raise ValueError, "unsupported property: "+name
  987. if (forceset or len(rrulevals) > 1 or
  988. rdatevals or exrulevals or exdatevals):
  989. if not parser and (rdatevals or exdatevals):
  990. from dateutil import parser
  991. set = rruleset(cache=cache)
  992. for value in rrulevals:
  993. set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
  994. ignoretz=ignoretz,
  995. tzinfos=tzinfos))
  996. for value in rdatevals:
  997. for datestr in value.split(','):
  998. set.rdate(parser.parse(datestr,
  999. ignoretz=ignoretz,
  1000. tzinfos=tzinfos))
  1001. for value in exrulevals:
  1002. set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
  1003. ignoretz=ignoretz,
  1004. tzinfos=tzinfos))
  1005. for value in exdatevals:
  1006. for datestr in value.split(','):
  1007. set.exdate(parser.parse(datestr,
  1008. ignoretz=ignoretz,
  1009. tzinfos=tzinfos))
  1010. if compatible and dtstart:
  1011. set.rdate(dtstart)
  1012. return set
  1013. else:
  1014. return self._parse_rfc_rrule(rrulevals[0],
  1015. dtstart=dtstart,
  1016. cache=cache,
  1017. ignoretz=ignoretz,
  1018. tzinfos=tzinfos)
  1019. def __call__(self, s, **kwargs):
  1020. return self._parse_rfc(s, **kwargs)
  1021. rrulestr = _rrulestr()
  1022. # vim:ts=4:sw=4:et