rrule.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375
  1. # -*- coding: utf-8 -*-
  2. """
  3. The rrule module offers a small, complete, and very fast, implementation of
  4. the recurrence rules documented in the
  5. `iCalendar RFC <http://www.ietf.org/rfc/rfc2445.txt>`_,
  6. including support for caching of results.
  7. """
  8. import itertools
  9. import datetime
  10. import calendar
  11. import sys
  12. from fractions import gcd
  13. from six import advance_iterator, integer_types
  14. from six.moves import _thread
  15. __all__ = ["rrule", "rruleset", "rrulestr",
  16. "YEARLY", "MONTHLY", "WEEKLY", "DAILY",
  17. "HOURLY", "MINUTELY", "SECONDLY",
  18. "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
  19. # Every mask is 7 days longer to handle cross-year weekly periods.
  20. M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +
  21. [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
  22. M365MASK = list(M366MASK)
  23. M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))
  24. MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
  25. MDAY365MASK = list(MDAY366MASK)
  26. M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0))
  27. NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
  28. NMDAY365MASK = list(NMDAY366MASK)
  29. M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366)
  30. M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
  31. WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55
  32. del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
  33. MDAY365MASK = tuple(MDAY365MASK)
  34. M365MASK = tuple(M365MASK)
  35. (YEARLY,
  36. MONTHLY,
  37. WEEKLY,
  38. DAILY,
  39. HOURLY,
  40. MINUTELY,
  41. SECONDLY) = list(range(7))
  42. # Imported on demand.
  43. easter = None
  44. parser = None
  45. class weekday(object):
  46. __slots__ = ["weekday", "n"]
  47. def __init__(self, weekday, n=None):
  48. if n == 0:
  49. raise ValueError("Can't create weekday with n == 0")
  50. self.weekday = weekday
  51. self.n = n
  52. def __call__(self, n):
  53. if n == self.n:
  54. return self
  55. else:
  56. return self.__class__(self.weekday, n)
  57. def __eq__(self, other):
  58. try:
  59. if self.weekday != other.weekday or self.n != other.n:
  60. return False
  61. except AttributeError:
  62. return False
  63. return True
  64. def __repr__(self):
  65. s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
  66. if not self.n:
  67. return s
  68. else:
  69. return "%s(%+d)" % (s, self.n)
  70. MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
  71. class rrulebase(object):
  72. def __init__(self, cache=False):
  73. if cache:
  74. self._cache = []
  75. self._cache_lock = _thread.allocate_lock()
  76. self._cache_gen = self._iter()
  77. self._cache_complete = False
  78. else:
  79. self._cache = None
  80. self._cache_complete = False
  81. self._len = None
  82. def __iter__(self):
  83. if self._cache_complete:
  84. return iter(self._cache)
  85. elif self._cache is None:
  86. return self._iter()
  87. else:
  88. return self._iter_cached()
  89. def _iter_cached(self):
  90. i = 0
  91. gen = self._cache_gen
  92. cache = self._cache
  93. acquire = self._cache_lock.acquire
  94. release = self._cache_lock.release
  95. while gen:
  96. if i == len(cache):
  97. acquire()
  98. if self._cache_complete:
  99. break
  100. try:
  101. for j in range(10):
  102. cache.append(advance_iterator(gen))
  103. except StopIteration:
  104. self._cache_gen = gen = None
  105. self._cache_complete = True
  106. break
  107. release()
  108. yield cache[i]
  109. i += 1
  110. while i < self._len:
  111. yield cache[i]
  112. i += 1
  113. def __getitem__(self, item):
  114. if self._cache_complete:
  115. return self._cache[item]
  116. elif isinstance(item, slice):
  117. if item.step and item.step < 0:
  118. return list(iter(self))[item]
  119. else:
  120. return list(itertools.islice(self,
  121. item.start or 0,
  122. item.stop or sys.maxsize,
  123. item.step or 1))
  124. elif item >= 0:
  125. gen = iter(self)
  126. try:
  127. for i in range(item+1):
  128. res = advance_iterator(gen)
  129. except StopIteration:
  130. raise IndexError
  131. return res
  132. else:
  133. return list(iter(self))[item]
  134. def __contains__(self, item):
  135. if self._cache_complete:
  136. return item in self._cache
  137. else:
  138. for i in self:
  139. if i == item:
  140. return True
  141. elif i > item:
  142. return False
  143. return False
  144. # __len__() introduces a large performance penality.
  145. def count(self):
  146. """ Returns the number of recurrences in this set. It will have go
  147. trough the whole recurrence, if this hasn't been done before. """
  148. if self._len is None:
  149. for x in self:
  150. pass
  151. return self._len
  152. def before(self, dt, inc=False):
  153. """ Returns the last recurrence before the given datetime instance. The
  154. inc keyword defines what happens if dt is an occurrence. With
  155. inc=True, if dt itself is an occurrence, it will be returned. """
  156. if self._cache_complete:
  157. gen = self._cache
  158. else:
  159. gen = self
  160. last = None
  161. if inc:
  162. for i in gen:
  163. if i > dt:
  164. break
  165. last = i
  166. else:
  167. for i in gen:
  168. if i >= dt:
  169. break
  170. last = i
  171. return last
  172. def after(self, dt, inc=False):
  173. """ Returns the first recurrence after the given datetime instance. The
  174. inc keyword defines what happens if dt is an occurrence. With
  175. inc=True, if dt itself is an occurrence, it will be returned. """
  176. if self._cache_complete:
  177. gen = self._cache
  178. else:
  179. gen = self
  180. if inc:
  181. for i in gen:
  182. if i >= dt:
  183. return i
  184. else:
  185. for i in gen:
  186. if i > dt:
  187. return i
  188. return None
  189. def between(self, after, before, inc=False):
  190. """ Returns all the occurrences of the rrule between after and before.
  191. The inc keyword defines what happens if after and/or before are
  192. themselves occurrences. With inc=True, they will be included in the
  193. list, if they are found in the recurrence set. """
  194. if self._cache_complete:
  195. gen = self._cache
  196. else:
  197. gen = self
  198. started = False
  199. l = []
  200. if inc:
  201. for i in gen:
  202. if i > before:
  203. break
  204. elif not started:
  205. if i >= after:
  206. started = True
  207. l.append(i)
  208. else:
  209. l.append(i)
  210. else:
  211. for i in gen:
  212. if i >= before:
  213. break
  214. elif not started:
  215. if i > after:
  216. started = True
  217. l.append(i)
  218. else:
  219. l.append(i)
  220. return l
  221. class rrule(rrulebase):
  222. """
  223. That's the base of the rrule operation. It accepts all the keywords
  224. defined in the RFC as its constructor parameters (except byday,
  225. which was renamed to byweekday) and more. The constructor prototype is::
  226. rrule(freq)
  227. Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
  228. or SECONDLY.
  229. Additionally, it supports the following keyword arguments:
  230. :param cache:
  231. If given, it must be a boolean value specifying to enable or disable
  232. caching of results. If you will use the same rrule instance multiple
  233. times, enabling caching will improve the performance considerably.
  234. :param dtstart:
  235. The recurrence start. Besides being the base for the recurrence,
  236. missing parameters in the final recurrence instances will also be
  237. extracted from this date. If not given, datetime.now() will be used
  238. instead.
  239. :param interval:
  240. The interval between each freq iteration. For example, when using
  241. YEARLY, an interval of 2 means once every two years, but with HOURLY,
  242. it means once every two hours. The default interval is 1.
  243. :param wkst:
  244. The week start day. Must be one of the MO, TU, WE constants, or an
  245. integer, specifying the first day of the week. This will affect
  246. recurrences based on weekly periods. The default week start is got
  247. from calendar.firstweekday(), and may be modified by
  248. calendar.setfirstweekday().
  249. :param count:
  250. How many occurrences will be generated.
  251. :param until:
  252. If given, this must be a datetime instance, that will specify the
  253. limit of the recurrence. If a recurrence instance happens to be the
  254. same as the datetime instance given in the until keyword, this will
  255. be the last occurrence.
  256. :param bysetpos:
  257. If given, it must be either an integer, or a sequence of integers,
  258. positive or negative. Each given integer will specify an occurrence
  259. number, corresponding to the nth occurrence of the rule inside the
  260. frequency period. For example, a bysetpos of -1 if combined with a
  261. MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will
  262. result in the last work day of every month.
  263. :param bymonth:
  264. If given, it must be either an integer, or a sequence of integers,
  265. meaning the months to apply the recurrence to.
  266. :param bymonthday:
  267. If given, it must be either an integer, or a sequence of integers,
  268. meaning the month days to apply the recurrence to.
  269. :param byyearday:
  270. If given, it must be either an integer, or a sequence of integers,
  271. meaning the year days to apply the recurrence to.
  272. :param byweekno:
  273. If given, it must be either an integer, or a sequence of integers,
  274. meaning the week numbers to apply the recurrence to. Week numbers
  275. have the meaning described in ISO8601, that is, the first week of
  276. the year is that containing at least four days of the new year.
  277. :param byweekday:
  278. If given, it must be either an integer (0 == MO), a sequence of
  279. integers, one of the weekday constants (MO, TU, etc), or a sequence
  280. of these constants. When given, these variables will define the
  281. weekdays where the recurrence will be applied. It's also possible to
  282. use an argument n for the weekday instances, which will mean the nth
  283. occurrence of this weekday in the period. For example, with MONTHLY,
  284. or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the
  285. first friday of the month where the recurrence happens. Notice that in
  286. the RFC documentation, this is specified as BYDAY, but was renamed to
  287. avoid the ambiguity of that keyword.
  288. :param byhour:
  289. If given, it must be either an integer, or a sequence of integers,
  290. meaning the hours to apply the recurrence to.
  291. :param byminute:
  292. If given, it must be either an integer, or a sequence of integers,
  293. meaning the minutes to apply the recurrence to.
  294. :param bysecond:
  295. If given, it must be either an integer, or a sequence of integers,
  296. meaning the seconds to apply the recurrence to.
  297. :param byeaster:
  298. If given, it must be either an integer, or a sequence of integers,
  299. positive or negative. Each integer will define an offset from the
  300. Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
  301. Sunday itself. This is an extension to the RFC specification.
  302. """
  303. def __init__(self, freq, dtstart=None,
  304. interval=1, wkst=None, count=None, until=None, bysetpos=None,
  305. bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
  306. byweekno=None, byweekday=None,
  307. byhour=None, byminute=None, bysecond=None,
  308. cache=False):
  309. super(rrule, self).__init__(cache)
  310. global easter
  311. if not dtstart:
  312. dtstart = datetime.datetime.now().replace(microsecond=0)
  313. elif not isinstance(dtstart, datetime.datetime):
  314. dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
  315. else:
  316. dtstart = dtstart.replace(microsecond=0)
  317. self._dtstart = dtstart
  318. self._tzinfo = dtstart.tzinfo
  319. self._freq = freq
  320. self._interval = interval
  321. self._count = count
  322. if until and not isinstance(until, datetime.datetime):
  323. until = datetime.datetime.fromordinal(until.toordinal())
  324. self._until = until
  325. if wkst is None:
  326. self._wkst = calendar.firstweekday()
  327. elif isinstance(wkst, integer_types):
  328. self._wkst = wkst
  329. else:
  330. self._wkst = wkst.weekday
  331. if bysetpos is None:
  332. self._bysetpos = None
  333. elif isinstance(bysetpos, integer_types):
  334. if bysetpos == 0 or not (-366 <= bysetpos <= 366):
  335. raise ValueError("bysetpos must be between 1 and 366, "
  336. "or between -366 and -1")
  337. self._bysetpos = (bysetpos,)
  338. else:
  339. self._bysetpos = tuple(bysetpos)
  340. for pos in self._bysetpos:
  341. if pos == 0 or not (-366 <= pos <= 366):
  342. raise ValueError("bysetpos must be between 1 and 366, "
  343. "or between -366 and -1")
  344. if (byweekno is None and byyearday is None and bymonthday is None and
  345. byweekday is None and byeaster is None):
  346. if freq == YEARLY:
  347. if bymonth is None:
  348. bymonth = dtstart.month
  349. bymonthday = dtstart.day
  350. elif freq == MONTHLY:
  351. bymonthday = dtstart.day
  352. elif freq == WEEKLY:
  353. byweekday = dtstart.weekday()
  354. # bymonth
  355. if bymonth is None:
  356. self._bymonth = None
  357. else:
  358. if isinstance(bymonth, integer_types):
  359. bymonth = (bymonth,)
  360. self._bymonth = tuple(sorted(set(bymonth)))
  361. # byyearday
  362. if byyearday is None:
  363. self._byyearday = None
  364. else:
  365. if isinstance(byyearday, integer_types):
  366. byyearday = (byyearday,)
  367. self._byyearday = tuple(sorted(set(byyearday)))
  368. # byeaster
  369. if byeaster is not None:
  370. if not easter:
  371. from dateutil import easter
  372. if isinstance(byeaster, integer_types):
  373. self._byeaster = (byeaster,)
  374. else:
  375. self._byeaster = tuple(sorted(byeaster))
  376. else:
  377. self._byeaster = None
  378. # bymonthay
  379. if bymonthday is None:
  380. self._bymonthday = ()
  381. self._bynmonthday = ()
  382. else:
  383. if isinstance(bymonthday, integer_types):
  384. bymonthday = (bymonthday,)
  385. self._bymonthday = tuple(sorted(set([x for x in bymonthday if x > 0])))
  386. self._bynmonthday = tuple(sorted(set([x for x in bymonthday if x < 0])))
  387. # byweekno
  388. if byweekno is None:
  389. self._byweekno = None
  390. else:
  391. if isinstance(byweekno, integer_types):
  392. byweekno = (byweekno,)
  393. self._byweekno = tuple(sorted(set(byweekno)))
  394. # byweekday / bynweekday
  395. if byweekday is None:
  396. self._byweekday = None
  397. self._bynweekday = None
  398. else:
  399. # If it's one of the valid non-sequence types, convert to a
  400. # single-element sequence before the iterator that builds the
  401. # byweekday set.
  402. if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"):
  403. byweekday = (byweekday,)
  404. self._byweekday = set()
  405. self._bynweekday = set()
  406. for wday in byweekday:
  407. if isinstance(wday, integer_types):
  408. self._byweekday.add(wday)
  409. elif not wday.n or freq > MONTHLY:
  410. self._byweekday.add(wday.weekday)
  411. else:
  412. self._bynweekday.add((wday.weekday, wday.n))
  413. if not self._byweekday:
  414. self._byweekday = None
  415. elif not self._bynweekday:
  416. self._bynweekday = None
  417. if self._byweekday is not None:
  418. self._byweekday = tuple(sorted(self._byweekday))
  419. if self._bynweekday is not None:
  420. self._bynweekday = tuple(sorted(self._bynweekday))
  421. # byhour
  422. if byhour is None:
  423. if freq < HOURLY:
  424. self._byhour = set((dtstart.hour,))
  425. else:
  426. self._byhour = None
  427. else:
  428. if isinstance(byhour, integer_types):
  429. byhour = (byhour,)
  430. if freq == HOURLY:
  431. self._byhour = self.__construct_byset(start=dtstart.hour,
  432. byxxx=byhour,
  433. base=24)
  434. else:
  435. self._byhour = set(byhour)
  436. self._byhour = tuple(sorted(self._byhour))
  437. # byminute
  438. if byminute is None:
  439. if freq < MINUTELY:
  440. self._byminute = set((dtstart.minute,))
  441. else:
  442. self._byminute = None
  443. else:
  444. if isinstance(byminute, integer_types):
  445. byminute = (byminute,)
  446. if freq == MINUTELY:
  447. self._byminute = self.__construct_byset(start=dtstart.minute,
  448. byxxx=byminute,
  449. base=60)
  450. else:
  451. self._byminute = set(byminute)
  452. self._byminute = tuple(sorted(self._byminute))
  453. # bysecond
  454. if bysecond is None:
  455. if freq < SECONDLY:
  456. self._bysecond = ((dtstart.second,))
  457. else:
  458. self._bysecond = None
  459. else:
  460. if isinstance(bysecond, integer_types):
  461. bysecond = (bysecond,)
  462. self._bysecond = set(bysecond)
  463. if freq == SECONDLY:
  464. self._bysecond = self.__construct_byset(start=dtstart.second,
  465. byxxx=bysecond,
  466. base=60)
  467. else:
  468. self._bysecond = set(bysecond)
  469. self._bysecond = tuple(sorted(self._bysecond))
  470. if self._freq >= HOURLY:
  471. self._timeset = None
  472. else:
  473. self._timeset = []
  474. for hour in self._byhour:
  475. for minute in self._byminute:
  476. for second in self._bysecond:
  477. self._timeset.append(
  478. datetime.time(hour, minute, second,
  479. tzinfo=self._tzinfo))
  480. self._timeset.sort()
  481. self._timeset = tuple(self._timeset)
  482. def _iter(self):
  483. year, month, day, hour, minute, second, weekday, yearday, _ = \
  484. self._dtstart.timetuple()
  485. # Some local variables to speed things up a bit
  486. freq = self._freq
  487. interval = self._interval
  488. wkst = self._wkst
  489. until = self._until
  490. bymonth = self._bymonth
  491. byweekno = self._byweekno
  492. byyearday = self._byyearday
  493. byweekday = self._byweekday
  494. byeaster = self._byeaster
  495. bymonthday = self._bymonthday
  496. bynmonthday = self._bynmonthday
  497. bysetpos = self._bysetpos
  498. byhour = self._byhour
  499. byminute = self._byminute
  500. bysecond = self._bysecond
  501. ii = _iterinfo(self)
  502. ii.rebuild(year, month)
  503. getdayset = {YEARLY: ii.ydayset,
  504. MONTHLY: ii.mdayset,
  505. WEEKLY: ii.wdayset,
  506. DAILY: ii.ddayset,
  507. HOURLY: ii.ddayset,
  508. MINUTELY: ii.ddayset,
  509. SECONDLY: ii.ddayset}[freq]
  510. if freq < HOURLY:
  511. timeset = self._timeset
  512. else:
  513. gettimeset = {HOURLY: ii.htimeset,
  514. MINUTELY: ii.mtimeset,
  515. SECONDLY: ii.stimeset}[freq]
  516. if ((freq >= HOURLY and
  517. self._byhour and hour not in self._byhour) or
  518. (freq >= MINUTELY and
  519. self._byminute and minute not in self._byminute) or
  520. (freq >= SECONDLY and
  521. self._bysecond and second not in self._bysecond)):
  522. timeset = ()
  523. else:
  524. timeset = gettimeset(hour, minute, second)
  525. total = 0
  526. count = self._count
  527. while True:
  528. # Get dayset with the right frequency
  529. dayset, start, end = getdayset(year, month, day)
  530. # Do the "hard" work ;-)
  531. filtered = False
  532. for i in dayset[start:end]:
  533. if ((bymonth and ii.mmask[i] not in bymonth) or
  534. (byweekno and not ii.wnomask[i]) or
  535. (byweekday and ii.wdaymask[i] not in byweekday) or
  536. (ii.nwdaymask and not ii.nwdaymask[i]) or
  537. (byeaster and not ii.eastermask[i]) or
  538. ((bymonthday or bynmonthday) and
  539. ii.mdaymask[i] not in bymonthday and
  540. ii.nmdaymask[i] not in bynmonthday) or
  541. (byyearday and
  542. ((i < ii.yearlen and i+1 not in byyearday and
  543. -ii.yearlen+i not in byyearday) or
  544. (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and
  545. -ii.nextyearlen+i-ii.yearlen not in byyearday)))):
  546. dayset[i] = None
  547. filtered = True
  548. # Output results
  549. if bysetpos and timeset:
  550. poslist = []
  551. for pos in bysetpos:
  552. if pos < 0:
  553. daypos, timepos = divmod(pos, len(timeset))
  554. else:
  555. daypos, timepos = divmod(pos-1, len(timeset))
  556. try:
  557. i = [x for x in dayset[start:end]
  558. if x is not None][daypos]
  559. time = timeset[timepos]
  560. except IndexError:
  561. pass
  562. else:
  563. date = datetime.date.fromordinal(ii.yearordinal+i)
  564. res = datetime.datetime.combine(date, time)
  565. if res not in poslist:
  566. poslist.append(res)
  567. poslist.sort()
  568. for res in poslist:
  569. if until and res > until:
  570. self._len = total
  571. return
  572. elif res >= self._dtstart:
  573. total += 1
  574. yield res
  575. if count:
  576. count -= 1
  577. if not count:
  578. self._len = total
  579. return
  580. else:
  581. for i in dayset[start:end]:
  582. if i is not None:
  583. date = datetime.date.fromordinal(ii.yearordinal+i)
  584. for time in timeset:
  585. res = datetime.datetime.combine(date, time)
  586. if until and res > until:
  587. self._len = total
  588. return
  589. elif res >= self._dtstart:
  590. total += 1
  591. yield res
  592. if count:
  593. count -= 1
  594. if not count:
  595. self._len = total
  596. return
  597. # Handle frequency and interval
  598. fixday = False
  599. if freq == YEARLY:
  600. year += interval
  601. if year > datetime.MAXYEAR:
  602. self._len = total
  603. return
  604. ii.rebuild(year, month)
  605. elif freq == MONTHLY:
  606. month += interval
  607. if month > 12:
  608. div, mod = divmod(month, 12)
  609. month = mod
  610. year += div
  611. if month == 0:
  612. month = 12
  613. year -= 1
  614. if year > datetime.MAXYEAR:
  615. self._len = total
  616. return
  617. ii.rebuild(year, month)
  618. elif freq == WEEKLY:
  619. if wkst > weekday:
  620. day += -(weekday+1+(6-wkst))+self._interval*7
  621. else:
  622. day += -(weekday-wkst)+self._interval*7
  623. weekday = wkst
  624. fixday = True
  625. elif freq == DAILY:
  626. day += interval
  627. fixday = True
  628. elif freq == HOURLY:
  629. if filtered:
  630. # Jump to one iteration before next day
  631. hour += ((23-hour)//interval)*interval
  632. if byhour:
  633. ndays, hour = self.__mod_distance(value=hour,
  634. byxxx=self._byhour,
  635. base=24)
  636. else:
  637. ndays, hour = divmod(hour+interval, 24)
  638. if ndays:
  639. day += ndays
  640. fixday = True
  641. timeset = gettimeset(hour, minute, second)
  642. elif freq == MINUTELY:
  643. if filtered:
  644. # Jump to one iteration before next day
  645. minute += ((1439-(hour*60+minute))//interval)*interval
  646. valid = False
  647. rep_rate = (24*60)
  648. for j in range(rep_rate // gcd(interval, rep_rate)):
  649. if byminute:
  650. nhours, minute = \
  651. self.__mod_distance(value=minute,
  652. byxxx=self._byminute,
  653. base=60)
  654. else:
  655. nhours, minute = divmod(minute+interval, 60)
  656. div, hour = divmod(hour+nhours, 24)
  657. if div:
  658. day += div
  659. fixday = True
  660. filtered = False
  661. if not byhour or hour in byhour:
  662. valid = True
  663. break
  664. if not valid:
  665. raise ValueError('Invalid combination of interval and ' +
  666. 'byhour resulting in empty rule.')
  667. timeset = gettimeset(hour, minute, second)
  668. elif freq == SECONDLY:
  669. if filtered:
  670. # Jump to one iteration before next day
  671. second += (((86399-(hour*3600+minute*60+second))
  672. // interval)*interval)
  673. rep_rate = (24*3600)
  674. valid = False
  675. for j in range(0, rep_rate // gcd(interval, rep_rate)):
  676. if bysecond:
  677. nminutes, second = \
  678. self.__mod_distance(value=second,
  679. byxxx=self._bysecond,
  680. base=60)
  681. else:
  682. nminutes, second = divmod(second+interval, 60)
  683. div, minute = divmod(minute+nminutes, 60)
  684. if div:
  685. hour += div
  686. div, hour = divmod(hour, 24)
  687. if div:
  688. day += div
  689. fixday = True
  690. if ((not byhour or hour in byhour) and
  691. (not byminute or minute in byminute) and
  692. (not bysecond or second in bysecond)):
  693. valid = True
  694. break
  695. if not valid:
  696. raise ValueError('Invalid combination of interval, ' +
  697. 'byhour and byminute resulting in empty' +
  698. ' rule.')
  699. timeset = gettimeset(hour, minute, second)
  700. if fixday and day > 28:
  701. daysinmonth = calendar.monthrange(year, month)[1]
  702. if day > daysinmonth:
  703. while day > daysinmonth:
  704. day -= daysinmonth
  705. month += 1
  706. if month == 13:
  707. month = 1
  708. year += 1
  709. if year > datetime.MAXYEAR:
  710. self._len = total
  711. return
  712. daysinmonth = calendar.monthrange(year, month)[1]
  713. ii.rebuild(year, month)
  714. def __construct_byset(self, start, byxxx, base):
  715. """
  716. If a `BYXXX` sequence is passed to the constructor at the same level as
  717. `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some
  718. specifications which cannot be reached given some starting conditions.
  719. This occurs whenever the interval is not coprime with the base of a
  720. given unit and the difference between the starting position and the
  721. ending position is not coprime with the greatest common denominator
  722. between the interval and the base. For example, with a FREQ of hourly
  723. starting at 17:00 and an interval of 4, the only valid values for
  724. BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not
  725. coprime.
  726. :param start:
  727. Specifies the starting position.
  728. :param byxxx:
  729. An iterable containing the list of allowed values.
  730. :param base:
  731. The largest allowable value for the specified frequency (e.g.
  732. 24 hours, 60 minutes).
  733. This does not preserve the type of the iterable, returning a set, since
  734. the values should be unique and the order is irrelevant, this will
  735. speed up later lookups.
  736. In the event of an empty set, raises a :exception:`ValueError`, as this
  737. results in an empty rrule.
  738. """
  739. cset = set()
  740. # Support a single byxxx value.
  741. if isinstance(byxxx, integer_types):
  742. byxxx = (byxxx, )
  743. for num in byxxx:
  744. i_gcd = gcd(self._interval, base)
  745. # Use divmod rather than % because we need to wrap negative nums.
  746. if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:
  747. cset.add(num)
  748. if len(cset) == 0:
  749. raise ValueError("Invalid rrule byxxx generates an empty set.")
  750. return cset
  751. def __mod_distance(self, value, byxxx, base):
  752. """
  753. Calculates the next value in a sequence where the `FREQ` parameter is
  754. specified along with a `BYXXX` parameter at the same "level"
  755. (e.g. `HOURLY` specified with `BYHOUR`).
  756. :param value:
  757. The old value of the component.
  758. :param byxxx:
  759. The `BYXXX` set, which should have been generated by
  760. `rrule._construct_byset`, or something else which checks that a
  761. valid rule is present.
  762. :param base:
  763. The largest allowable value for the specified frequency (e.g.
  764. 24 hours, 60 minutes).
  765. If a valid value is not found after `base` iterations (the maximum
  766. number before the sequence would start to repeat), this raises a
  767. :exception:`ValueError`, as no valid values were found.
  768. This returns a tuple of `divmod(n*interval, base)`, where `n` is the
  769. smallest number of `interval` repetitions until the next specified
  770. value in `byxxx` is found.
  771. """
  772. accumulator = 0
  773. for ii in range(1, base + 1):
  774. # Using divmod() over % to account for negative intervals
  775. div, value = divmod(value + self._interval, base)
  776. accumulator += div
  777. if value in byxxx:
  778. return (accumulator, value)
  779. class _iterinfo(object):
  780. __slots__ = ["rrule", "lastyear", "lastmonth",
  781. "yearlen", "nextyearlen", "yearordinal", "yearweekday",
  782. "mmask", "mrange", "mdaymask", "nmdaymask",
  783. "wdaymask", "wnomask", "nwdaymask", "eastermask"]
  784. def __init__(self, rrule):
  785. for attr in self.__slots__:
  786. setattr(self, attr, None)
  787. self.rrule = rrule
  788. def rebuild(self, year, month):
  789. # Every mask is 7 days longer to handle cross-year weekly periods.
  790. rr = self.rrule
  791. if year != self.lastyear:
  792. self.yearlen = 365+calendar.isleap(year)
  793. self.nextyearlen = 365+calendar.isleap(year+1)
  794. firstyday = datetime.date(year, 1, 1)
  795. self.yearordinal = firstyday.toordinal()
  796. self.yearweekday = firstyday.weekday()
  797. wday = datetime.date(year, 1, 1).weekday()
  798. if self.yearlen == 365:
  799. self.mmask = M365MASK
  800. self.mdaymask = MDAY365MASK
  801. self.nmdaymask = NMDAY365MASK
  802. self.wdaymask = WDAYMASK[wday:]
  803. self.mrange = M365RANGE
  804. else:
  805. self.mmask = M366MASK
  806. self.mdaymask = MDAY366MASK
  807. self.nmdaymask = NMDAY366MASK
  808. self.wdaymask = WDAYMASK[wday:]
  809. self.mrange = M366RANGE
  810. if not rr._byweekno:
  811. self.wnomask = None
  812. else:
  813. self.wnomask = [0]*(self.yearlen+7)
  814. # no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
  815. no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7
  816. if no1wkst >= 4:
  817. no1wkst = 0
  818. # Number of days in the year, plus the days we got
  819. # from last year.
  820. wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7
  821. else:
  822. # Number of days in the year, minus the days we
  823. # left in last year.
  824. wyearlen = self.yearlen-no1wkst
  825. div, mod = divmod(wyearlen, 7)
  826. numweeks = div+mod//4
  827. for n in rr._byweekno:
  828. if n < 0:
  829. n += numweeks+1
  830. if not (0 < n <= numweeks):
  831. continue
  832. if n > 1:
  833. i = no1wkst+(n-1)*7
  834. if no1wkst != firstwkst:
  835. i -= 7-firstwkst
  836. else:
  837. i = no1wkst
  838. for j in range(7):
  839. self.wnomask[i] = 1
  840. i += 1
  841. if self.wdaymask[i] == rr._wkst:
  842. break
  843. if 1 in rr._byweekno:
  844. # Check week number 1 of next year as well
  845. # TODO: Check -numweeks for next year.
  846. i = no1wkst+numweeks*7
  847. if no1wkst != firstwkst:
  848. i -= 7-firstwkst
  849. if i < self.yearlen:
  850. # If week starts in next year, we
  851. # don't care about it.
  852. for j in range(7):
  853. self.wnomask[i] = 1
  854. i += 1
  855. if self.wdaymask[i] == rr._wkst:
  856. break
  857. if no1wkst:
  858. # Check last week number of last year as
  859. # well. If no1wkst is 0, either the year
  860. # started on week start, or week number 1
  861. # got days from last year, so there are no
  862. # days from last year's last week number in
  863. # this year.
  864. if -1 not in rr._byweekno:
  865. lyearweekday = datetime.date(year-1, 1, 1).weekday()
  866. lno1wkst = (7-lyearweekday+rr._wkst) % 7
  867. lyearlen = 365+calendar.isleap(year-1)
  868. if lno1wkst >= 4:
  869. lno1wkst = 0
  870. lnumweeks = 52+(lyearlen +
  871. (lyearweekday-rr._wkst) % 7) % 7//4
  872. else:
  873. lnumweeks = 52+(self.yearlen-no1wkst) % 7//4
  874. else:
  875. lnumweeks = -1
  876. if lnumweeks in rr._byweekno:
  877. for i in range(no1wkst):
  878. self.wnomask[i] = 1
  879. if (rr._bynweekday and (month != self.lastmonth or
  880. year != self.lastyear)):
  881. ranges = []
  882. if rr._freq == YEARLY:
  883. if rr._bymonth:
  884. for month in rr._bymonth:
  885. ranges.append(self.mrange[month-1:month+1])
  886. else:
  887. ranges = [(0, self.yearlen)]
  888. elif rr._freq == MONTHLY:
  889. ranges = [self.mrange[month-1:month+1]]
  890. if ranges:
  891. # Weekly frequency won't get here, so we may not
  892. # care about cross-year weekly periods.
  893. self.nwdaymask = [0]*self.yearlen
  894. for first, last in ranges:
  895. last -= 1
  896. for wday, n in rr._bynweekday:
  897. if n < 0:
  898. i = last+(n+1)*7
  899. i -= (self.wdaymask[i]-wday) % 7
  900. else:
  901. i = first+(n-1)*7
  902. i += (7-self.wdaymask[i]+wday) % 7
  903. if first <= i <= last:
  904. self.nwdaymask[i] = 1
  905. if rr._byeaster:
  906. self.eastermask = [0]*(self.yearlen+7)
  907. eyday = easter.easter(year).toordinal()-self.yearordinal
  908. for offset in rr._byeaster:
  909. self.eastermask[eyday+offset] = 1
  910. self.lastyear = year
  911. self.lastmonth = month
  912. def ydayset(self, year, month, day):
  913. return list(range(self.yearlen)), 0, self.yearlen
  914. def mdayset(self, year, month, day):
  915. dset = [None]*self.yearlen
  916. start, end = self.mrange[month-1:month+1]
  917. for i in range(start, end):
  918. dset[i] = i
  919. return dset, start, end
  920. def wdayset(self, year, month, day):
  921. # We need to handle cross-year weeks here.
  922. dset = [None]*(self.yearlen+7)
  923. i = datetime.date(year, month, day).toordinal()-self.yearordinal
  924. start = i
  925. for j in range(7):
  926. dset[i] = i
  927. i += 1
  928. # if (not (0 <= i < self.yearlen) or
  929. # self.wdaymask[i] == self.rrule._wkst):
  930. # This will cross the year boundary, if necessary.
  931. if self.wdaymask[i] == self.rrule._wkst:
  932. break
  933. return dset, start, i
  934. def ddayset(self, year, month, day):
  935. dset = [None]*self.yearlen
  936. i = datetime.date(year, month, day).toordinal()-self.yearordinal
  937. dset[i] = i
  938. return dset, i, i+1
  939. def htimeset(self, hour, minute, second):
  940. tset = []
  941. rr = self.rrule
  942. for minute in rr._byminute:
  943. for second in rr._bysecond:
  944. tset.append(datetime.time(hour, minute, second,
  945. tzinfo=rr._tzinfo))
  946. tset.sort()
  947. return tset
  948. def mtimeset(self, hour, minute, second):
  949. tset = []
  950. rr = self.rrule
  951. for second in rr._bysecond:
  952. tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
  953. tset.sort()
  954. return tset
  955. def stimeset(self, hour, minute, second):
  956. return (datetime.time(hour, minute, second,
  957. tzinfo=self.rrule._tzinfo),)
  958. class rruleset(rrulebase):
  959. """ The rruleset type allows more complex recurrence setups, mixing
  960. multiple rules, dates, exclusion rules, and exclusion dates. The type
  961. constructor takes the following keyword arguments:
  962. :param cache: If True, caching of results will be enabled, improving
  963. performance of multiple queries considerably. """
  964. class _genitem(object):
  965. def __init__(self, genlist, gen):
  966. try:
  967. self.dt = advance_iterator(gen)
  968. genlist.append(self)
  969. except StopIteration:
  970. pass
  971. self.genlist = genlist
  972. self.gen = gen
  973. def __next__(self):
  974. try:
  975. self.dt = advance_iterator(self.gen)
  976. except StopIteration:
  977. self.genlist.remove(self)
  978. next = __next__
  979. def __lt__(self, other):
  980. return self.dt < other.dt
  981. def __gt__(self, other):
  982. return self.dt > other.dt
  983. def __eq__(self, other):
  984. return self.dt == other.dt
  985. def __ne__(self, other):
  986. return self.dt != other.dt
  987. def __init__(self, cache=False):
  988. super(rruleset, self).__init__(cache)
  989. self._rrule = []
  990. self._rdate = []
  991. self._exrule = []
  992. self._exdate = []
  993. def rrule(self, rrule):
  994. """ Include the given :py:class:`rrule` instance in the recurrence set
  995. generation. """
  996. self._rrule.append(rrule)
  997. def rdate(self, rdate):
  998. """ Include the given :py:class:`datetime` instance in the recurrence
  999. set generation. """
  1000. self._rdate.append(rdate)
  1001. def exrule(self, exrule):
  1002. """ Include the given rrule instance in the recurrence set exclusion
  1003. list. Dates which are part of the given recurrence rules will not
  1004. be generated, even if some inclusive rrule or rdate matches them.
  1005. """
  1006. self._exrule.append(exrule)
  1007. def exdate(self, exdate):
  1008. """ Include the given datetime instance in the recurrence set
  1009. exclusion list. Dates included that way will not be generated,
  1010. even if some inclusive rrule or rdate matches them. """
  1011. self._exdate.append(exdate)
  1012. def _iter(self):
  1013. rlist = []
  1014. self._rdate.sort()
  1015. self._genitem(rlist, iter(self._rdate))
  1016. for gen in [iter(x) for x in self._rrule]:
  1017. self._genitem(rlist, gen)
  1018. rlist.sort()
  1019. exlist = []
  1020. self._exdate.sort()
  1021. self._genitem(exlist, iter(self._exdate))
  1022. for gen in [iter(x) for x in self._exrule]:
  1023. self._genitem(exlist, gen)
  1024. exlist.sort()
  1025. lastdt = None
  1026. total = 0
  1027. while rlist:
  1028. ritem = rlist[0]
  1029. if not lastdt or lastdt != ritem.dt:
  1030. while exlist and exlist[0] < ritem:
  1031. advance_iterator(exlist[0])
  1032. exlist.sort()
  1033. if not exlist or ritem != exlist[0]:
  1034. total += 1
  1035. yield ritem.dt
  1036. lastdt = ritem.dt
  1037. advance_iterator(ritem)
  1038. rlist.sort()
  1039. self._len = total
  1040. class _rrulestr(object):
  1041. _freq_map = {"YEARLY": YEARLY,
  1042. "MONTHLY": MONTHLY,
  1043. "WEEKLY": WEEKLY,
  1044. "DAILY": DAILY,
  1045. "HOURLY": HOURLY,
  1046. "MINUTELY": MINUTELY,
  1047. "SECONDLY": SECONDLY}
  1048. _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3,
  1049. "FR": 4, "SA": 5, "SU": 6}
  1050. def _handle_int(self, rrkwargs, name, value, **kwargs):
  1051. rrkwargs[name.lower()] = int(value)
  1052. def _handle_int_list(self, rrkwargs, name, value, **kwargs):
  1053. rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
  1054. _handle_INTERVAL = _handle_int
  1055. _handle_COUNT = _handle_int
  1056. _handle_BYSETPOS = _handle_int_list
  1057. _handle_BYMONTH = _handle_int_list
  1058. _handle_BYMONTHDAY = _handle_int_list
  1059. _handle_BYYEARDAY = _handle_int_list
  1060. _handle_BYEASTER = _handle_int_list
  1061. _handle_BYWEEKNO = _handle_int_list
  1062. _handle_BYHOUR = _handle_int_list
  1063. _handle_BYMINUTE = _handle_int_list
  1064. _handle_BYSECOND = _handle_int_list
  1065. def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
  1066. rrkwargs["freq"] = self._freq_map[value]
  1067. def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
  1068. global parser
  1069. if not parser:
  1070. from dateutil import parser
  1071. try:
  1072. rrkwargs["until"] = parser.parse(value,
  1073. ignoretz=kwargs.get("ignoretz"),
  1074. tzinfos=kwargs.get("tzinfos"))
  1075. except ValueError:
  1076. raise ValueError("invalid until date")
  1077. def _handle_WKST(self, rrkwargs, name, value, **kwargs):
  1078. rrkwargs["wkst"] = self._weekday_map[value]
  1079. def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg):
  1080. l = []
  1081. for wday in value.split(','):
  1082. for i in range(len(wday)):
  1083. if wday[i] not in '+-0123456789':
  1084. break
  1085. n = wday[:i] or None
  1086. w = wday[i:]
  1087. if n:
  1088. n = int(n)
  1089. l.append(weekdays[self._weekday_map[w]](n))
  1090. rrkwargs["byweekday"] = l
  1091. _handle_BYDAY = _handle_BYWEEKDAY
  1092. def _parse_rfc_rrule(self, line,
  1093. dtstart=None,
  1094. cache=False,
  1095. ignoretz=False,
  1096. tzinfos=None):
  1097. if line.find(':') != -1:
  1098. name, value = line.split(':')
  1099. if name != "RRULE":
  1100. raise ValueError("unknown parameter name")
  1101. else:
  1102. value = line
  1103. rrkwargs = {}
  1104. for pair in value.split(';'):
  1105. name, value = pair.split('=')
  1106. name = name.upper()
  1107. value = value.upper()
  1108. try:
  1109. getattr(self, "_handle_"+name)(rrkwargs, name, value,
  1110. ignoretz=ignoretz,
  1111. tzinfos=tzinfos)
  1112. except AttributeError:
  1113. raise ValueError("unknown parameter '%s'" % name)
  1114. except (KeyError, ValueError):
  1115. raise ValueError("invalid '%s': %s" % (name, value))
  1116. return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
  1117. def _parse_rfc(self, s,
  1118. dtstart=None,
  1119. cache=False,
  1120. unfold=False,
  1121. forceset=False,
  1122. compatible=False,
  1123. ignoretz=False,
  1124. tzinfos=None):
  1125. global parser
  1126. if compatible:
  1127. forceset = True
  1128. unfold = True
  1129. s = s.upper()
  1130. if not s.strip():
  1131. raise ValueError("empty string")
  1132. if unfold:
  1133. lines = s.splitlines()
  1134. i = 0
  1135. while i < len(lines):
  1136. line = lines[i].rstrip()
  1137. if not line:
  1138. del lines[i]
  1139. elif i > 0 and line[0] == " ":
  1140. lines[i-1] += line[1:]
  1141. del lines[i]
  1142. else:
  1143. i += 1
  1144. else:
  1145. lines = s.split()
  1146. if (not forceset and len(lines) == 1 and (s.find(':') == -1 or
  1147. s.startswith('RRULE:'))):
  1148. return self._parse_rfc_rrule(lines[0], cache=cache,
  1149. dtstart=dtstart, ignoretz=ignoretz,
  1150. tzinfos=tzinfos)
  1151. else:
  1152. rrulevals = []
  1153. rdatevals = []
  1154. exrulevals = []
  1155. exdatevals = []
  1156. for line in lines:
  1157. if not line:
  1158. continue
  1159. if line.find(':') == -1:
  1160. name = "RRULE"
  1161. value = line
  1162. else:
  1163. name, value = line.split(':', 1)
  1164. parms = name.split(';')
  1165. if not parms:
  1166. raise ValueError("empty property name")
  1167. name = parms[0]
  1168. parms = parms[1:]
  1169. if name == "RRULE":
  1170. for parm in parms:
  1171. raise ValueError("unsupported RRULE parm: "+parm)
  1172. rrulevals.append(value)
  1173. elif name == "RDATE":
  1174. for parm in parms:
  1175. if parm != "VALUE=DATE-TIME":
  1176. raise ValueError("unsupported RDATE parm: "+parm)
  1177. rdatevals.append(value)
  1178. elif name == "EXRULE":
  1179. for parm in parms:
  1180. raise ValueError("unsupported EXRULE parm: "+parm)
  1181. exrulevals.append(value)
  1182. elif name == "EXDATE":
  1183. for parm in parms:
  1184. if parm != "VALUE=DATE-TIME":
  1185. raise ValueError("unsupported RDATE parm: "+parm)
  1186. exdatevals.append(value)
  1187. elif name == "DTSTART":
  1188. for parm in parms:
  1189. raise ValueError("unsupported DTSTART parm: "+parm)
  1190. if not parser:
  1191. from dateutil import parser
  1192. dtstart = parser.parse(value, ignoretz=ignoretz,
  1193. tzinfos=tzinfos)
  1194. else:
  1195. raise ValueError("unsupported property: "+name)
  1196. if (forceset or len(rrulevals) > 1 or rdatevals
  1197. or exrulevals or exdatevals):
  1198. if not parser and (rdatevals or exdatevals):
  1199. from dateutil import parser
  1200. rset = rruleset(cache=cache)
  1201. for value in rrulevals:
  1202. rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
  1203. ignoretz=ignoretz,
  1204. tzinfos=tzinfos))
  1205. for value in rdatevals:
  1206. for datestr in value.split(','):
  1207. rset.rdate(parser.parse(datestr,
  1208. ignoretz=ignoretz,
  1209. tzinfos=tzinfos))
  1210. for value in exrulevals:
  1211. rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
  1212. ignoretz=ignoretz,
  1213. tzinfos=tzinfos))
  1214. for value in exdatevals:
  1215. for datestr in value.split(','):
  1216. rset.exdate(parser.parse(datestr,
  1217. ignoretz=ignoretz,
  1218. tzinfos=tzinfos))
  1219. if compatible and dtstart:
  1220. rset.rdate(dtstart)
  1221. return rset
  1222. else:
  1223. return self._parse_rfc_rrule(rrulevals[0],
  1224. dtstart=dtstart,
  1225. cache=cache,
  1226. ignoretz=ignoretz,
  1227. tzinfos=tzinfos)
  1228. def __call__(self, s, **kwargs):
  1229. return self._parse_rfc(s, **kwargs)
  1230. rrulestr = _rrulestr()
  1231. # vim:ts=4:sw=4:et