Package screenlets :: Package plugins :: Module iCal
[hide private]
[frames] | no frames]

Source Code for Module screenlets.plugins.iCal

  1  ############################################################################ 
  2  # Programmers: Jiva DeVoe <jiva@devoesquared.com> 
  3  # Filename: iCal.py 
  4  # http://www.devoesquared.com/Software/iCal_Module 
  5  # 
  6  # Contributors / Alterations: 
  7  # stefan natchev: april 15, 2005 - added support for dates with ranges 
  8  # stefan natchev: april 16, 2005 - added more recurrance support 
  9  # Aaron A. Seigo aseigo@kde.org: January 2006 - patches for event lookup for multi-day events. (see occursOn) 
 10  # Danil Dotsenko dd@accentsolution.com: June 15, 2006 - reworked file rutine in ICalReader to work with generic paths and added fileFilter and check for correct header. 
 11  # Danil Dotsenko dd@accentsolution.com: -----||------ - reworked other functions for efficiency. 
 12  # Paul van Erk: July 3, 2006 - fixed a bug where all-day events would be counted as 2 days (and 2-day events as 3, etc) 
 13  # Danil Dotsenko dd@accentsolution.com: Sept 04, 2006 - changed the iCalReader class init rutine to init on the basis of raw data list, not filenames. This allows reading files from remote sources. 
 14  #  Warning! with this version (20060904) I am braking backward compatibility in __init__() arguments of ICalReader 
 15  # 
 16  # version 20060904 
 17  # 
 18  # "2-Clause" BSD license 
 19  # 
 20  # Redistribution and use in source and binary forms, with or without 
 21  # modification, are permitted provided that the following conditions 
 22  # are met: 
 23  # 
 24  # 1. Redistributions of source code must retain the above copyright 
 25  #    notice, this list of conditions and the following disclaimer. 
 26  # 2. Redistributions in binary form must reproduce the above copyright 
 27  #    notice, this list of conditions and the following disclaimer in the 
 28  #    documentation and/or other materials provided with the distribution. 
 29  # 
 30  # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
 31  # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 32  # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 33  # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
 34  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 35  # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 36  # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 37  # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 38  # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
 39  # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 40  ############################################################################ 
 41   
 42  import os 
 43  import os.path 
 44  import re 
 45  import datetime 
 46  import time 
 47   
48 -class ICalReader:
49 - def __init__(self, dataLines = None):
50 ''' 51 iCal.ICalReader([dataList]) 52 53 Initializes the iCal reader and parses the list type object for events. 54 The list type object with iCal contents is optional. 55 You can call readFiles(file name list) and readEvents(iCal data lines) separately. 56 ''' 57 self.events = [] 58 if dataLines: 59 self.readEvents(dataLines)
60
61 - def readFiles(self, fileList):
62 ''' 63 readFiles(fileList) 64 65 This function accepts ARRAY with list of local file names. 66 We put contents of the files into one big list and feed to readEvents() 67 We only work with files, no folders or wildcards. 68 ''' 69 dataLines = [] 70 for item in range(fileList.__len__()): 71 if os.path.isfile(fileList[item]): # not sure why I am doing it, your code should... 72 tempFile = open(fileList[item], 'r') 73 dataLines.extend(tempFile.readlines()) 74 tempFile.close() 75 if dataLines: 76 self.readEvents(dataLines)
77
78 - def readURL(self, url):
79 ''' 80 Put rutine to fetch and convert a resource into lines and feed it to readEvents() 81 ''' 82 dataLines = None 83 if str(url).lower().startswith('http') or str(url).lower().startswith('www'): 84 try: 85 import urllib2 86 except: print 'Please install urllib2' 87 tempFile = urllib2.urlopen(url) 88 dataLines = tempFile.readlines() 89 tempFile.close() 90 else: 91 92 tempFile = open(url,'r') 93 dataLines = tempFile.readlines() 94 tempFile.close() 95 if dataLines: 96 self.readEvents(dataLines)
97
98 - def readEvents(self, lines, emptyEvents=False):
99 if emptyEvents: 100 self.events = [] 101 # this is here for MY convenience. Don't rely on this. 102 # Instead just to iCalObject.events = {} in your code. 103 # The latter allows emptying events with functions like readURL and readFiles 104 mask = {} 105 mask['BEGIN'] = re.compile("^BEGIN:VEVENT") 106 mask['END'] = re.compile("^END:VEVENT") 107 inEvent= False 108 for line in lines: # this will only work if there is no such thing as nested VEVENTs. It assumes there is one END:VEVENT fro every openning BEGIN 109 if mask['BEGIN'].match(line): 110 eventLines = [] 111 inEvent = True 112 elif mask['END'].match(line) and inEvent: 113 # note: BEGIN: and END: are not included in the list. 114 self.events.append(self.parseEvent(eventLines)) 115 inEvent = False 116 elif inEvent: 117 eventLines.append(line)
118
119 - def parseEvent(self, lines):
120 event = ICalEvent() 121 startDate = None 122 rule = None 123 endDate = None 124 #it has to have something for a summary(?) -s 125 event.summary = '' 126 mask={} 127 mask['Summary']=re.compile("^SUMMARY:(.*)") 128 mask['DTStart']=re.compile("^DTSTART;.*:(.*).*") 129 mask['DTEnd']=re.compile("^DTEND;.*:(.*).*") 130 mask['DTStartTZ']=re.compile("^DTSTART:(.*)T.*Z") 131 mask['DTEndTZ']=re.compile("^DTEND:(.*)T.*Z") 132 mask['ExDate']=re.compile("^EXDATE.*:(.*)") 133 mask['RRule']=re.compile("^RRULE:(.*)") 134 timed = '1' 135 for line in lines: 136 if mask['Summary'].match(line): 137 event.summary = mask['Summary'].match(line).group(1) 138 #these are the dtstart/dtend with no time range 139 elif mask['DTStart'].match(line): 140 startDate = self.parseDate(mask['DTStart'].match(line).group(1)) 141 timed = '0' 142 elif mask['DTEnd'].match(line): 143 endDate = self.parseDate(mask['DTEnd'].match(line).group(1)) 144 timed = '0' 145 #these are the ones that are 'ranged' 146 elif mask['DTStartTZ'].match(line): 147 startDate = self.parseDate(mask['DTStartTZ'].match(line).group(1)) 148 elif mask['DTEndTZ'].match(line): 149 endDate = self.parseDate(mask['DTEndTZ'].match(line).group(1)) 150 elif mask['ExDate'].match(line): 151 event.addExceptionDate(self.parseDate(mask['ExDate'].match(line).group(1))) 152 elif mask['RRule'].match(line): 153 rule = mask['RRule'].match(line).group(1) 154 event.startDate = startDate 155 event.endDate = endDate 156 event.timed = timed 157 if rule: 158 event.addRecurrenceRule(rule) 159 return event
160 161 #def parseTodo(self, lines): 162 # todo = ICalTodo() 163 # startDate = None 164 # endDate = None 165
166 - def parseDate(self, dateStr):
167 year = int(dateStr[0:4]) 168 if year < 1970: 169 year = 1970 170 month = int(dateStr[4:4+2]) 171 day = int(dateStr[6:6+2]) 172 try: 173 hour = int(dateStr[9:9+2]) 174 minute = int(dateStr[11:11+2]) 175 except: 176 hour = 0 177 minute = 0 178 return datetime.datetime(year, month, day, hour, minute)
179
180 - def selectEvents(self, selectFunction):
181 note = datetime.datetime.today() 182 self.events.sort() 183 events = filter(selectFunction, self.events) 184 return events
185
186 - def todaysEvents(self, event):
187 return event.startsToday()
188
189 - def tomorrowsEvents(self, event):
190 return event.startsTomorrow()
191
192 - def afterTodaysEvents(self, event):
193 return event.startsAfterToday()
194
195 - def eventsFor(self, date):
196 note = datetime.datetime.today() 197 self.events.sort() 198 ret = [] 199 for event in self.events: 200 #if event.startsOn(date): 201 if event.occursOn(date): 202 ret.append(event) 203 return ret 204 205 year = int(dateStr[0:4]) 206 if year < 1970: 207 year = 1970 208 209 month = int(dateStr[4:4+2]) 210 day = int(dateStr[6:6+2]) 211 try: 212 hour = int(dateStr[9:9+2]) 213 minute = int(dateStr[11:11+2]) 214 except: 215 hour = 0 216 minute = 0
217
218 -class ICalEvent:
219 - def __init__(self):
220 self.exceptionDates = [] 221 self.dateSet = None
222
223 - def __str__(self):
224 return self.summary
225
226 - def __eq__(self, otherEvent):
227 return self.startDate == otherEvent.startDate
228
229 - def addExceptionDate(self, date):
230 self.exceptionDates.append(date)
231
232 - def addRecurrenceRule(self, rule):
233 self.dateSet = DateSet(self.startDate, self.endDate, rule)
234
235 - def startsToday(self):
236 return self.startsOn(datetime.datetime.today())
237
238 - def startsTomorrow(self):
239 tomorrow = datetime.datetime.fromtimestamp(time.time() + 86400) 240 return self.startsOn(tomorrow)
241
242 - def startsAfterToday(self):
243 return self.startsAfter(datetime.datetime.today())
244
245 - def startsOn(self, date):
246 return (self.startDate.year == date.year and 247 self.startDate.month == date.month and 248 self.startDate.day == date.day or 249 (self.dateSet and self.dateSet.includes(date)))
250
251 - def occursOn(self, date):
252 if (self.timed == '0'): 253 return (self.startsOn(date) or (self.startDate < date and self.endDate >= date) and self.endDate != date) 254 else: 255 return (self.startsOn(date) or (self.startDate < date and self.endDate >= date))
256
257 - def startsAfter(self, date):
258 return (self.startDate > date)
259
260 - def startTime(self):
261 return self.startDate
262 263 #class ICalTodo: 264 265 #strange... 266 #class DateParser:
267 -def parse(dateStr):
268 year = int(dateStr[0:4]) 269 if year < 1970: 270 year = 1970 271 272 month = int(dateStr[4:4+2]) 273 day = int(dateStr[6:6+2]) 274 try: 275 hour = int(dateStr[9:9+2]) 276 minute = int(dateStr[11:11+2]) 277 except: 278 hour = 0 279 minute = 0 280 return datetime.datetime(year, month, day, hour, minute)
281 282
283 -class DateSet:
284 - def __init__(self, startDate, endDate, rule):
285 self.startDate = startDate 286 self.endDate = endDate 287 self.startWeekNumber = startDate.isocalendar()[1] 288 self.frequency = None 289 self.count = None 290 self.interval = 1 291 self.untilDate = None 292 self.byMonth = None 293 self.byDate = None 294 self.parseRecurrenceRule(rule)
295
296 - def parseRecurrenceRule(self, rule):
297 if re.compile("FREQ=(.*?);").match(rule): 298 self.frequency = re.compile("FREQ=(.*?);").match(rule).group(1) 299 300 if re.compile("COUNT=(\d*)").search(rule): 301 self.count = int(re.compile("COUNT=(\d*)").search(rule).group(1)) 302 303 if re.compile("UNTIL=(.*?);").search(rule): 304 #homebrewed 305 self.untilDate = parse(re.compile("UNTIL=(.*?);").search(rule).group(1)) 306 if re.compile("INTERVAL=(\d*);").search(rule): 307 self.interval = int(re.compile("INTERVAL=(\d*);").search(rule).group(1)) 308 309 if re.compile("BYMONTH=(.*?);").search(rule): 310 self.byMonth = re.compile("BYMONTH=(.*?);").search(rule).group(1) 311 312 if re.compile("BYDAY=(.*?);").search(rule): 313 self.byDay = re.compile("BYDAY=(.*?);").search(rule).group(1)
314 315 316
317 - def includes(self, date):
318 if date == self.startDate: 319 return True 320 321 if self.untilDate and date > self.untilDate: 322 return False 323 324 if self.frequency == 'DAILY': 325 if self.interval: 326 increment = self.interval 327 else: 328 increment = 1 329 d = self.startDate 330 counter = 0 331 while(d < date): 332 if self.count: 333 counter += 1 334 if counter >= self.count: 335 return False 336 337 d = d.replace(day=d.day+1) 338 339 if (d.day == date.day and 340 d.year == date.year and 341 d.month == date.month): 342 return True 343 344 elif self.frequency == 'WEEKLY': 345 if self.startDate.weekday() == date.weekday(): 346 #make sure the interval is proper -Milo 347 if self.startWeekNumber % self.interval == date.isocalendar()[1] % self.interval: 348 return True 349 else: 350 return False 351 else: 352 if self.endDate: 353 for n in range(0, self.endDate.day - self.startDate.day): 354 newDate = self.startDate.replace(day=self.startDate.day+n) 355 if newDate.weekday() == date.weekday(): 356 return True 357 358 elif self.frequency == 'MONTHLY': 359 pass 360 361 elif self.frequency == 'YEARLY': 362 pass 363 364 return False
365 366 367 if __name__ == '__main__': 368 reader = ICalReader() 369 # reader.readURL('http://www.google.com/calendar/ical/4dmslp71qlvjhl1q6g6f88gfv4@group.calendar.google.com/public/basic.ics') 370 reader.readURL('file:///home/dd/musor/icalout.ics') 371 for event in reader.events: 372 print event 373