1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 import datetime
25 import time
26
27 from icalendar import Calendar
28 from dateutil import rrule, tz, parser
29
30 from flumotion.extern.log.log import Loggable
31
32
33
34
36 STDOFFSET = datetime.timedelta(seconds=-time.timezone)
37 if time.daylight:
38 DSTOFFSET = datetime.timedelta(seconds=-time.altzone)
39 else:
40 DSTOFFSET = STDOFFSET
41 DSTDIFF = DSTOFFSET - STDOFFSET
42 ZERO = datetime.timedelta(0)
43
49
55
58
60 tt = (dt.year, dt.month, dt.day,
61 dt.hour, dt.minute, dt.second,
62 dt.weekday(), 0, -1)
63 return time.localtime(time.mktime(tt)).tm_isdst > 0
64 LOCAL = LocalTimezone()
65
66
68 """
69 I represent a start or an end point linked to an event instance
70 of an event.
71 """
72
73 - def __init__(self, eventInstance, which, timestamp):
74 """
75 @param eventInstance: An instance of an event.
76 @type eventInstance: EventInstance
77 @param which: 'start' or 'end'
78 @type which: str
79 @param timestamp: Timestamp of this point. It will
80 be used when comparing Points.
81 @type timestamp: datetime
82 """
83 self.which = which
84 self.timestamp = timestamp
85 self.eventInstance = eventInstance
86
88 return "Point '%s' at %r for %r" % (
89 self.which, self.timestamp, self.event)
90
94
95
97 """
98 I represent one event instance of an event.
99 """
100
102 """
103 @type event: L{Event}
104 @type start: L{datetime.datetime}
105 @type end: L{datetime.datetime}
106 """
107 self.event = event
108 self.start = start
109 self.end = end
110
111
112 self.currentStart = start
113 self.currentEnd = end
114
116 """
117 Get a list of start and end points.
118 @rtype: L{Point}
119 """
120 ret = []
121
122 ret.append(Point(self, 'start', self.start))
123 ret.append(Point(self, 'end', self.end))
124
125 return ret
126
127
129 """
130 If d is date, convert it to datetime.
131 @type d: It can be anything, even None. However, it will convert only if
132 it is an event instance of date.
133 @return: If d was an event instance of date, it returns the equivalent
134 datetime.Otherwise, it returns d.
135 """
136 if isinstance(d, datetime.date) and not isinstance(d, datetime.datetime):
137 return datetime.datetime(d.year, d.month, d.day, tzinfo=LOCAL)
138 return d
139
140
142 """
143 I represent a EVENT entry in a calendar for our purposes.
144 I can have recurrence.
145 I can be scheduled between a start time and an end time,
146 returning a list of start and end points.
147 I can have exception dates.
148 """
149
150 - def __init__(self, uid, start, end, content, rrule=None,
151 recurrenceid=None, exdates=None, now=None):
152 """
153 @param uid: identifier of the event.
154 @type uid: str
155 @param start: start time of the event.
156 @type start: L{datetime.datetime}
157 @param end: end time of the event.
158 @type end: L{datetime.datetime}
159 @param content: label to describe the content
160 @type content: str
161 @param rrule: a RRULE string
162 @type rrule: str
163 @param recurrenceid: a RECURRENCE-ID string. It is used on
164 recurrence events.
165 @type recurrenceid: str
166 @param exdates: list of exceptions. It is commonly used with
167 recurrence events.
168 @type exdates: list of L{datetime.datetime} or None
169 """
170 if not now:
171 now = datetime.datetime.now(LOCAL)
172 self.end = self.__addTimeZone(end, LOCAL)
173 self.start = self.__addTimeZone(start, LOCAL)
174 self.content = content
175 self.uid = uid
176 self.rrule = rrule
177 self.recurrenceid = recurrenceid
178 if exdates:
179 self.exdates = []
180 for exdate in exdates:
181 exdate = self.__addTimeZone(exdate, LOCAL)
182 self.exdates.append(exdate)
183 else:
184 self.exdates = None
185 self.now = now
186
187
188 self.currentStart = start
189 self.currentEnd = end
190
192 if dateTime.tzinfo is not None:
193 return dateTime
194 return datetime.datetime(dateTime.year, dateTime.month, dateTime.day,
195 dateTime.hour, dateTime.minute, dateTime.second,
196 dateTime.microsecond, now)
197
199 return "<Event %r >" % (self.toTuple())
200
202 return (self.uid, self.start, self.end, self.content, self.rrule,
203 self.exdates)
204
207
210
213
215 return not self.__eq__(other)
216
217
219 """
220 I represent a set of EVENT entries in a calendar sharing the same uid.
221 I can have recurrence.
222 I can be scheduled between a start time and an end time,
223 returning a list of start and end points in UTC.
224 I can have exception dates.
225 """
226
228 """
229 @param uid: the uid shared among the events on this set
230 @type uid: str
231 """
232 self.uid = uid
233 self._events = []
234
236 return "<EventSet for uid %r >" % (
237 self.uid)
238
240 """
241 Add an event to the set. The event must have the same uid as the set.
242 """
243 if self.uid != event.uid:
244 self.debug("my uid %s does not match Event uid %s",
245 self.uid, event.uid)
246 return
247 self._events.append(event)
248
250 """
251 Remove and event from the set.
252 """
253 if self.uid != event.uid:
254 self.debug("my uid %s does not match Event uid %s",
255 self.uid, event.uid)
256 self._events.remove(event)
257
259 """
260 Get an ordered list of start and end points between the given start
261 and end for this set of Events.
262 @param start: The start point.
263 @type start: datetime
264 @param end: The end point
265 @type end: datetime
266 """
267 points = []
268
269 eventInstances = self._getEventInstances(start, end)
270 for i in eventInstances:
271 points.extend(i.getPoints())
272 points.sort()
273
274 return points
275
277
278
279 eventInstances = []
280
281 recurring = None
282
283
284 for v in self._events:
285 if v.rrule:
286 if recurring:
287 self.debug("Cannot have two RRULE EVENTs with UID %s",
288 self.uid)
289 return []
290 recurring = v
291
292
293 if recurring:
294 eventInstances = self._getEventInstancesRecur(recurring, start, end)
295
296
297
298 for v in self._events:
299
300 if v.rrule:
301 continue
302
303 if v.recurrenceid:
304 recurDateTime = parser.parse(v.recurrenceid.ical())
305
306
307 for i in eventInstances[:]:
308 if i.start == recurDateTime:
309 eventInstances.remove(i)
310 break
311
312
313 i = self._getEventInstanceSingle(v, start, end)
314 if i:
315 eventInstances.append(i)
316
317
318
319 for i in eventInstances[:]:
320 if i.start < start:
321 i.start = start
322 if start >= i.end:
323 eventInstances.remove(i)
324 if i.end > end:
325 i.end = end
326
327 return eventInstances
328
341
343
344
345
346 ret = []
347
348
349
350
351 delta = event.end - event.start
352
353 startRecurRule = rrule.rrulestr(event.rrule, dtstart=event.start)
354
355 for startTime in startRecurRule:
356
357 if startTime + delta < start:
358 continue
359
360
361 if startTime >= end:
362 break
363
364
365 if event.exdates:
366 if startTime in event.exdates:
367 continue
368
369 endTime = startTime + delta
370
371 i = EventInstance(event, startTime, endTime)
372
373 ret.append(i)
374
375 return ret
376
378 """
379 Return the list of events
380 @rtype: L{Event}
381 """
382 return self._events
383
384
386 """
387 Take a Calendar object and return a list of
388 EventSet objects.
389
390 @param cal: The calendar to "parse"
391 @type cal: icalendar.Calendar
392
393 @rtype: list of {EventSet}
394 """
395 events = []
396
397 def vDDDToDatetime(v):
398 """
399 Convert a vDDDType to a datetime, respecting timezones
400 @param v: the time to convert
401 @type v: vDDDType
402
403 """
404 dt = toDateTime(v.dt)
405 if dt.tzinfo is None:
406 tzinfo = tz.gettz(v.params['TZID'])
407 dt = datetime.datetime(dt.year, dt.month, dt.day,
408 dt.hour, dt.minute, dt.second,
409 dt.microsecond, tzinfo)
410 return dt
411
412 for event in cal.walk('vevent'):
413
414 start = vDDDToDatetime(event.get('dtstart', None))
415 end = vDDDToDatetime(event.get('dtend', None))
416 summary = event.decoded('SUMMARY', None)
417 uid = event['UID']
418 recur = event.get('RRULE', None)
419 recurrenceid = event.get('RECURRENCE-ID', None)
420 exdates = event.get('EXDATE', [])
421
422
423 if not isinstance(exdates, list):
424 exdates = [exdates, ]
425
426
427
428 exdates = [vDDDToDatetime(i) for i in exdates]
429
430
431
432
433
434
435
436 e = Event(uid, start, end, summary,
437 recur and recur.ical() or None, recurrenceid, exdates)
438
439 events.append(e)
440 eventSets = {}
441 for event in events:
442 if not event.uid in eventSets.keys():
443 eventSets[event.uid] = EventSet(event.uid)
444
445 eventSets[event.uid].addEvent(event)
446 return eventSets.values()
447
449 """
450 Parse a given file into EventSets.
451
452 @type file: file object
453
454 @rtype: list of {EventSet}
455 """
456 data = file.read()
457 cal = Calendar.from_string(data)
458 file.close()
459 return parseCalendar(cal)
460