2
Copyright (c) 2003 Gustavo Niemeyer <niemeyer@conectiva.com>
4
This module offers extensions to the standard python 2.3+
7
__author__ = "Gustavo Niemeyer <niemeyer@conectiva.com>"
8
__license__ = "PSF License"
16
__all__ = ["rrule", "rruleset", "rrulestr",
17
"FREQ_YEARLY", "FREQ_MONTHLY", "FREQ_WEEKLY", "FREQ_DAILY",
18
"FREQ_HOURLY", "FREQ_MINUTELY", "FREQ_SECONDLY",
19
"MO", "TU", "WE", "TH", "FR", "SA", "SU"]
21
# Every mask is 7 days longer to handle cross-year weekly periods.
22
M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30+
23
[7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
24
M365MASK = list(M366MASK)
25
M29, M30, M31 = range(1,30), range(1,31), range(1,32)
26
MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
27
MDAY365MASK = list(MDAY366MASK)
28
M29, M30, M31 = range(-29,0), range(-30,0), range(-31,0)
29
NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
30
NMDAY365MASK = list(NMDAY366MASK)
31
M366RANGE = (0,31,60,91,121,152,182,213,244,274,305,335,366)
32
M365RANGE = (0,31,59,90,120,151,181,212,243,273,304,334,365)
33
WDAYMASK = [0,1,2,3,4,5,6]*55
34
del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
35
MDAY365MASK = tuple(MDAY365MASK)
36
M365MASK = tuple(M365MASK)
44
FREQ_SECONDLY) = range(7)
50
class weekday(object):
51
__slots__ = ["weekday", "n"]
53
def __init__(self, weekday, n=0):
54
self.weekday = weekday
57
def __call__(self, n):
61
return self.__class__(self.weekday, n)
63
def __eq__(self, other):
65
if self.weekday != other.weekday or self.n != other.n:
67
except AttributeError:
72
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
76
return "%s(%+d)" % (s, self.n)
78
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
81
def __init__(self, cache=False):
84
self._cache_lock = thread.allocate_lock()
85
self._cache_gen = self._iter()
86
self._cache_complete = False
89
self._cache_complete = False
93
if self._cache_complete:
94
return iter(self._cache)
95
elif self._cache is None:
98
return self._iter_cached()
100
def _iter_cached(self):
102
gen = self._cache_gen
104
acquire = self._cache_lock.acquire
105
release = self._cache_lock.release
109
if self._cache_complete:
113
cache.append(gen.next())
114
except StopIteration:
115
self._cache_gen = gen = None
116
self._cache_complete = True
125
def __getitem__(self, item):
126
if self._cache_complete:
127
return self._cache[item]
128
elif isinstance(item, slice):
129
if item.step and item.step < 0:
130
return list(iter(self))[item]
132
return list(itertools.islice(self,
134
item.stop or sys.maxint,
139
for i in range(item+1):
141
except StopIteration:
145
return list(iter(self))[item]
147
def __contains__(self, item):
148
if self._cache_complete:
149
return item in self._cache
156
# __len__() introduces a large performance penality.
158
if self._len is None:
162
def before(self, dt, inc=False):
163
if self._cache_complete:
180
def after(self, dt, inc=False):
181
if self._cache_complete:
195
def between(self, after, before, inc=False):
196
if self._cache_complete:
224
class rrule(rrulebase):
225
def __init__(self, freq, dtstart=None,
226
interval=1, wkst=None, count=None, until=None, bysetpos=None,
227
bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
228
byweekno=None, byweekday=None,
229
byhour=None, byminute=None, bysecond=None,
231
rrulebase.__init__(self, cache)
234
dtstart = datetime.datetime.now().replace(microsecond=0)
235
elif not isinstance(dtstart, datetime.datetime):
236
dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
238
dtstart = dtstart.replace(microsecond=0)
239
self._dtstart = dtstart
240
self._tzinfo = dtstart.tzinfo
242
self._interval = interval
244
if until and not isinstance(until, datetime.datetime):
245
until = datetime.datetime.fromordinal(until.toordinal())
248
self._wkst = calendar.firstweekday()
249
elif type(wkst) is int:
252
self._wkst = wkst.weekday
254
self._bysetpos = None
255
elif type(bysetpos) is int:
256
self._bysetpos = (bysetpos,)
258
self._bysetpos = tuple(bysetpos)
259
if not (byweekno or byyearday or bymonthday or
260
byweekday is not None or byeaster is not None):
261
if freq == FREQ_YEARLY:
263
bymonth = dtstart.month
264
bymonthday = dtstart.day
265
elif freq == FREQ_MONTHLY:
266
bymonthday = dtstart.day
267
elif freq == FREQ_WEEKLY:
268
byweekday = dtstart.weekday()
272
elif type(bymonth) is int:
273
self._bymonth = (bymonth,)
275
self._bymonth = tuple(bymonth)
278
self._byyearday = None
279
elif type(byyearday) is int:
280
self._byyearday = (byyearday,)
282
self._byyearday = tuple(byyearday)
284
if byeaster is not None:
286
from dateutil import easter
287
if type(byeaster) is int:
288
self._byeaster = (byeaster,)
290
self._byeaster = tuple(byeaster)
292
self._byeaster = None
295
self._bymonthday = ()
296
self._bynmonthday = ()
297
elif type(bymonthday) is int:
299
self._bynmonthday = (bymonthday,)
300
self._bymonthday = ()
302
self._bymonthday = (bymonthday,)
303
self._bynmonthday = ()
305
self._bymonthday = tuple([x for x in bymonthday if x > 0])
306
self._bynmonthday = tuple([x for x in bymonthday if x < 0])
309
self._byweekno = None
310
elif type(byweekno) is int:
311
self._byweekno = (byweekno,)
313
self._byweekno = tuple(byweekno)
314
# byweekday / bynweekday
315
if byweekday is None:
316
self._byweekday = None
317
self._bynweekday = None
318
elif type(byweekday) is int:
319
self._byweekday = (byweekday,)
320
self._bynweekday = None
321
elif hasattr(byweekday, "n"):
322
if byweekday.n == 0 or freq > FREQ_MONTHLY:
323
self._byweekday = (byweekday.weekday,)
324
self._bynweekday = None
326
self._bynweekday = ((byweekday.weekday, byweekday.n),)
327
self._byweekday = None
330
self._bynweekday = []
331
for wday in byweekday:
332
if type(wday) is int:
333
self._byweekday.append(wday)
334
elif wday.n == 0 or freq > FREQ_MONTHLY:
335
self._byweekday.append(wday.weekday)
337
self._bynweekday.append((wday.weekday, wday.n))
338
self._byweekday = tuple(self._byweekday)
339
self._bynweekday = tuple(self._bynweekday)
340
if not self._byweekday:
341
self._byweekday = None
342
elif not self._bynweekday:
343
self._bynweekday = None
346
if freq < FREQ_HOURLY:
347
self._byhour = (dtstart.hour,)
350
elif type(byhour) is int:
351
self._byhour = (byhour,)
353
self._byhour = tuple(byhour)
356
if freq < FREQ_MINUTELY:
357
self._byminute = (dtstart.minute,)
359
self._byminute = None
360
elif type(byminute) is int:
361
self._byminute = (byminute,)
363
self._byminute = tuple(byminute)
366
if freq < FREQ_SECONDLY:
367
self._bysecond = (dtstart.second,)
369
self._bysecond = None
370
elif type(bysecond) is int:
371
self._bysecond = (bysecond,)
373
self._bysecond = tuple(bysecond)
375
if self._freq >= FREQ_HOURLY:
379
for hour in self._byhour:
380
for minute in self._byminute:
381
for second in self._bysecond:
382
self._timeset.append(
383
datetime.time(hour, minute, second,
384
tzinfo=self._tzinfo))
386
self._timeset = tuple(self._timeset)
389
year, month, day, hour, minute, second, weekday, yearday, _ = \
390
self._dtstart.timetuple()
392
# Some local variables to speed things up a bit
394
interval = self._interval
397
bymonth = self._bymonth
398
byweekno = self._byweekno
399
byyearday = self._byyearday
400
byweekday = self._byweekday
401
byeaster = self._byeaster
402
bymonthday = self._bymonthday
403
bynmonthday = self._bynmonthday
404
bysetpos = self._bysetpos
405
byhour = self._byhour
406
byminute = self._byminute
407
bysecond = self._bysecond
410
ii.rebuild(year, month)
412
getdayset = {FREQ_YEARLY:ii.ydayset,
413
FREQ_MONTHLY:ii.mdayset,
414
FREQ_WEEKLY:ii.wdayset,
415
FREQ_DAILY:ii.ddayset,
416
FREQ_HOURLY:ii.ddayset,
417
FREQ_MINUTELY:ii.ddayset,
418
FREQ_SECONDLY:ii.ddayset}[freq]
420
if freq < FREQ_HOURLY:
421
timeset = self._timeset
423
gettimeset = {FREQ_HOURLY:ii.htimeset,
424
FREQ_MINUTELY:ii.mtimeset,
425
FREQ_SECONDLY:ii.stimeset}[freq]
426
if ((freq >= FREQ_HOURLY and
427
self._byhour and hour not in self._byhour) or
428
(freq >= FREQ_MINUTELY and
429
self._byminute and minute not in self._byminute) or
430
(freq >= FREQ_SECONDLY and
431
self._bysecond and minute not in self._bysecond)):
434
timeset = gettimeset(hour, minute, second)
439
# Get dayset with the right frequency
440
dayset, start, end = getdayset(year, month, day)
442
# Do the "hard" work ;-)
444
for i in dayset[start:end]:
445
if ((bymonth and ii.mmask[i] not in bymonth) or
446
(byweekno and not ii.wnomask[i]) or
447
(byyearday and (i%ii.yearlen)+1 not in byyearday) or
448
(byweekday and ii.wdaymask[i] not in byweekday) or
449
(ii.nwdaymask and not ii.nwdaymask[i]) or
450
(byeaster and not ii.eastermask[i]) or
451
((bymonthday or bynmonthday) and
452
ii.mdaymask[i] not in bymonthday and
453
ii.nmdaymask[i] not in bynmonthday)):
458
if bysetpos and timeset:
462
daypos, timepos = divmod(pos, len(timeset))
464
daypos, timepos = divmod(pos-1, len(timeset))
466
i = [x for x in dayset[start:end]
467
if x is not None][daypos]
468
time = timeset[timepos]
472
date = datetime.date.fromordinal(ii.yearordinal+i)
473
res = datetime.datetime.combine(date, time)
474
if res not in poslist:
478
if until and res > until:
481
elif res >= self._dtstart:
490
for i in dayset[start:end]:
492
date = datetime.date.fromordinal(ii.yearordinal+i)
494
res = datetime.datetime.combine(date, time)
495
if until and res > until:
498
elif res >= self._dtstart:
507
# Handle frequency and interval
509
if freq == FREQ_YEARLY:
511
if year > datetime.MAXYEAR:
514
ii.rebuild(year, month)
515
elif freq == FREQ_MONTHLY:
518
div, mod = divmod(month, 12)
524
if year > datetime.MAXYEAR:
527
ii.rebuild(year, month)
528
elif freq == FREQ_WEEKLY:
530
day += -(weekday+1+(6-wkst))+self._interval*7
532
day += -(weekday-wkst)+self._interval*7
535
elif freq == FREQ_DAILY:
538
elif freq == FREQ_HOURLY:
540
# Jump to one iteration before next day
541
hour += ((23-hour)//interval)*interval
544
div, mod = divmod(hour, 24)
549
if not byhour or hour in byhour:
551
timeset = gettimeset(hour, minute, second)
552
elif freq == FREQ_MINUTELY:
554
# Jump to one iteration before next day
555
minute += ((1439-(hour*60+minute))//interval)*interval
558
div, mod = divmod(minute, 60)
562
div, mod = divmod(hour, 24)
568
if ((not byhour or hour in byhour) and
569
(not byminute or minute in byminute)):
571
timeset = gettimeset(hour, minute, second)
572
elif freq == FREQ_SECONDLY:
574
# Jump to one iteration before next day
575
second += (((86399-(hour*3600+minute*60+second))
576
//interval)*interval)
578
second += self._interval
579
div, mod = divmod(second, 60)
583
div, mod = divmod(minute, 60)
587
div, mod = divmod(hour, 24)
592
if ((not byhour or hour in byhour) and
593
(not byminute or minute in byminute) and
594
(not bysecond or second in bysecond)):
596
timeset = gettimeset(hour, minute, second)
598
if fixday and day > 28:
599
daysinmonth = calendar.monthrange(year, month)[1]
600
if day > daysinmonth:
601
while day > daysinmonth:
607
if year > datetime.MAXYEAR:
610
daysinmonth = calendar.monthrange(year, month)[1]
611
ii.rebuild(year, month)
613
class _iterinfo(object):
614
__slots__ = ["rrule", "lastyear", "lastmonth",
615
"yearlen", "yearordinal", "yearweekday",
616
"mmask", "mrange", "mdaymask", "nmdaymask",
617
"wdaymask", "wnomask", "nwdaymask", "eastermask"]
619
def __init__(self, rrule):
620
for attr in self.__slots__:
621
setattr(self, attr, None)
624
def rebuild(self, year, month):
625
# Every mask is 7 days longer to handle cross-year weekly periods.
627
if year != self.lastyear:
628
self.yearlen = 365+calendar.isleap(year)
629
firstyday = datetime.date(year, 1, 1)
630
self.yearordinal = firstyday.toordinal()
631
self.yearweekday = firstyday.weekday()
633
wday = datetime.date(year, 1, 1).weekday()
634
if self.yearlen == 365:
635
self.mmask = M365MASK
636
self.mdaymask = MDAY365MASK
637
self.nmdaymask = NMDAY365MASK
638
self.wdaymask = WDAYMASK[wday:]
639
self.mrange = M365RANGE
641
self.mmask = M366MASK
642
self.mdaymask = MDAY366MASK
643
self.nmdaymask = NMDAY366MASK
644
self.wdaymask = WDAYMASK[wday:]
645
self.mrange = M366RANGE
650
self.wnomask = [0]*(self.yearlen+7)
651
#no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
652
no1wkst = firstwkst = (7-self.yearweekday+rr._wkst)%7
655
# Number of days in the year, plus the days we got
657
wyearlen = self.yearlen+(self.yearweekday-rr._wkst)%7
659
# Number of days in the year, minus the days we
661
wyearlen = self.yearlen-no1wkst
662
div, mod = divmod(wyearlen, 7)
663
numweeks = div+mod//4
664
for n in rr._byweekno:
667
if not (0 < n <= numweeks):
671
if no1wkst != firstwkst:
678
if self.wdaymask[i] == rr._wkst:
680
if 1 in rr._byweekno:
681
# Check week number 1 of next year as well
682
# TODO: Check -numweeks for next year.
683
i = no1wkst+numweeks*7
684
if no1wkst != firstwkst:
687
# If week starts in next year, we
688
# don't care about it.
692
if self.wdaymask[i] == rr._wkst:
695
# Check last week number of last year as
696
# well. If no1wkst is 0, either the year
697
# started on week start, or week number 1
698
# got days from last year, so there are no
699
# days from last year's last week number in
701
if -1 not in rr._byweekno:
702
lyearweekday = datetime.date(year-1,1,1).weekday()
703
lno1wkst = (7-lyearweekday+rr._wkst)%7
704
lyearlen = 365+calendar.isleap(year-1)
707
lnumweeks = 52+(lyearlen+
708
(lyearweekday-rr._wkst)%7)%7//4
710
lnumweeks = 52+(self.yearlen-no1wkst)%7//4
713
if lnumweeks in rr._byweekno:
714
for i in range(no1wkst):
717
if (rr._bynweekday and
718
(month != self.lastmonth or year != self.lastyear)):
720
if rr._freq == FREQ_YEARLY:
722
for month in rr._bymonth:
723
ranges.append(self.mrange[month-1:month+1])
725
ranges = [(0, self.yearlen)]
726
elif rr._freq == FREQ_MONTHLY:
727
ranges = [self.mrange[month-1:month+1]]
729
# Weekly frequency won't get here, so we may not
730
# care about cross-year weekly periods.
731
self.nwdaymask = [0]*self.yearlen
732
for first, last in ranges:
734
for wday, n in rr._bynweekday:
737
i -= (self.wdaymask[i]-wday)%7
740
i += (7-self.wdaymask[i]+wday)%7
741
if first <= i <= last:
742
self.nwdaymask[i] = 1
745
self.eastermask = [0]*(self.yearlen+7)
746
eyday = easter.easter(year).toordinal()-self.yearordinal
747
for offset in rr._byeaster:
748
self.eastermask[eyday+offset] = 1
751
self.lastmonth = month
753
def ydayset(self, year, month, day):
754
return range(self.yearlen), 0, self.yearlen
756
def mdayset(self, year, month, day):
757
set = [None]*self.yearlen
758
start, end = self.mrange[month-1:month+1]
759
for i in range(start, end):
761
return set, start, end
763
def wdayset(self, year, month, day):
764
# We need to handle cross-year weeks here.
765
set = [None]*(self.yearlen+7)
766
i = datetime.date(year, month, day).toordinal()-self.yearordinal
771
#if (not (0 <= i < self.yearlen) or
772
# self.wdaymask[i] == self.rrule._wkst):
773
# This will cross the year boundary, if necessary.
774
if self.wdaymask[i] == self.rrule._wkst:
778
def ddayset(self, year, month, day):
779
set = [None]*self.yearlen
780
i = datetime.date(year, month, day).toordinal()-self.yearordinal
784
def htimeset(self, hour, minute, second):
787
for minute in rr._byminute:
788
for second in rr._bysecond:
789
set.append(datetime.time(hour, minute, second,
794
def mtimeset(self, hour, minute, second):
797
for second in rr._bysecond:
798
set.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
802
def stimeset(self, hour, minute, second):
803
return (datetime.time(hour, minute, second,
804
tzinfo=self.rrule._tzinfo),)
807
class rruleset(rrulebase):
810
def __init__(self, genlist, gen):
814
except StopIteration:
816
self.genlist = genlist
822
except StopIteration:
823
self.genlist.remove(self)
825
def __cmp__(self, other):
826
return cmp(self.dt, other.dt)
828
def __init__(self, cache=False):
829
rrulebase.__init__(self, cache)
835
def rrule(self, rrule):
836
self._rrule.append(rrule)
838
def rdate(self, rdate):
839
self._rdate.append(rdate)
841
def exrule(self, exrule):
842
self._exrule.append(exrule)
844
def exdate(self, exdate):
845
self._exdate.append(exdate)
849
self._genitem(rlist, iter(self._rdate).next)
850
for gen in [iter(x).next for x in self._rrule]:
851
self._genitem(rlist, gen)
854
self._genitem(exlist, iter(self._exdate).next)
855
for gen in [iter(x).next for x in self._exrule]:
856
self._genitem(exlist, gen)
862
if not lastdt or lastdt != ritem.dt:
863
while exlist and exlist[0] < ritem:
866
if not exlist or ritem != exlist[0]:
876
_freq_map = {"YEARLY": FREQ_YEARLY,
877
"MONTHLY": FREQ_MONTHLY,
878
"WEEKLY": FREQ_WEEKLY,
880
"HOURLY": FREQ_HOURLY,
881
"MINUTELY": FREQ_MINUTELY,
882
"SECONDLY": FREQ_SECONDLY}
884
_weekday_map = {"MO":0,"TU":1,"WE":2,"TH":3,"FR":4,"SA":5,"SU":6}
886
def _handle_int(self, rrkwargs, name, value, **kwargs):
887
rrkwargs[name.lower()] = int(value)
889
def _handle_int_list(self, rrkwargs, name, value, **kwargs):
890
rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
892
_handle_INTERVAL = _handle_int
893
_handle_COUNT = _handle_int
894
_handle_BYSETPOS = _handle_int_list
895
_handle_BYMONTH = _handle_int_list
896
_handle_BYMONTHDAY = _handle_int_list
897
_handle_BYYEARDAY = _handle_int_list
898
_handle_BYEASTER = _handle_int_list
899
_handle_BYWEEKNO = _handle_int_list
900
_handle_BYHOUR = _handle_int_list
901
_handle_BYMINUTE = _handle_int_list
902
_handle_BYSECOND = _handle_int_list
904
def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
905
rrkwargs["freq"] = self._freq_map[value]
907
def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
910
from dateutil import parser
912
rrkwargs["until"] = parser.parse(value,
913
ignoretz=kwargs.get("ignoretz"),
914
tzinfos=kwargs.get("tzinfos"))
916
raise ValueError, "invalid until date"
918
def _handle_WKST(self, rrkwargs, name, value, **kwargs):
919
rrkwargs["wkst"] = self._weekday_map[value]
921
def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwarsg):
923
for wday in value.split(','):
924
for i in range(len(wday)):
925
if wday[i] not in '+-0123456789':
930
l.append(weekdays[self._weekday_map[w]](n))
931
rrkwargs["byweekday"] = l
933
_handle_BYDAY = _handle_BYWEEKDAY
935
def _parse_rfc_rrule(self, line,
940
if line.find(':') != -1:
941
name, value = line.split(':')
943
raise ValueError, "unknown parameter name"
947
for pair in value.split(';'):
948
name, value = pair.split('=')
950
value = value.upper()
952
getattr(self, "_handle_"+name)(rrkwargs, name, value,
955
except AttributeError:
956
raise "unknown parameter '%s'" % name
957
except (KeyError, ValueError):
958
raise "invalid '%s': %s" % (name, value)
959
return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
961
def _parse_rfc(self, s,
975
raise ValueError, "empty string"
977
lines = s.splitlines()
979
while i < len(lines):
980
line = lines[i].rstrip()
983
elif i > 0 and line[0] == " ":
984
lines[i-1] += line[1:]
990
if (not forceset and len(lines) == 1 and
991
(s.find(':') == -1 or s.startswith('RRULE:'))):
992
return self._parse_rfc_rrule(lines[0], cache=cache,
993
dtstart=dtstart, ignoretz=ignoretz,
1003
if line.find(':') == -1:
1007
name, value = line.split(':', 1)
1008
parms = name.split(';')
1010
raise ValueError, "empty property name"
1015
raise ValueError, "unsupported RRULE parm: "+parm
1016
rrulevals.append(value)
1017
elif name == "RDATE":
1019
if parm != "VALUE=DATE-TIME":
1020
raise ValueError, "unsupported RDATE parm: "+parm
1021
rdatevals.append(value)
1022
elif name == "EXRULE":
1024
raise ValueError, "unsupported EXRULE parm: "+parm
1025
exrulevals.append(value)
1026
elif name == "EXDATE":
1028
if parm != "VALUE=DATE-TIME":
1029
raise ValueError, "unsupported RDATE parm: "+parm
1030
exdatevals.append(value)
1031
elif name == "DTSTART":
1033
raise ValueError, "unsupported DTSTART parm: "+parm
1035
from dateutil import parser
1036
dtstart = parser.parse(value, ignoretz=ignoretz,
1039
raise ValueError, "unsupported property: "+name
1040
if (forceset or len(rrulevals) > 1 or
1041
rdatevals or exrulevals or exdatevals):
1042
if not parser and (rdatevals or exdatevals):
1043
from dateutil import parser
1044
set = rruleset(cache=cache)
1045
for value in rrulevals:
1046
set.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
1049
for value in rdatevals:
1050
for datestr in value.split(','):
1051
set.rdate(parser.parse(datestr,
1054
for value in exrulevals:
1055
set.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
1058
for value in exdatevals:
1059
for datestr in value.split(','):
1060
set.exdate(parser.parse(datestr,
1063
if compatible and dtstart:
1067
return self._parse_rfc_rrule(rrulevals[0],
1073
def __call__(self, s, **kwargs):
1074
return self._parse_rfc(s, **kwargs)
1076
rrulestr = _rrulestr()