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