~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/python-dateutil-0.4/dateutil/rrule.py

  • Committer: Aaron Bentley
  • Date: 2005-06-07 18:52:04 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050607185204-5fc1f0e3d393b909
Added NEWS, obsoleted bzr-pull

Show diffs side-by-side

added added

removed removed

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