#!/usr/bin/python import sys, os sys.path.append(os.path.dirname(__file__)) import icalendar reload(icalendar) from icalendar import Calendar, cal, prop from dateutil import tz as tzmod from cStringIO import StringIO import pytz import urllib2 import datetime, time import rruler reload(rruler) SECONDS_PER_DAY=24*60*60 def seconds(timediff): return SECONDS_PER_DAY * timediff.days + timediff.seconds class ICalReader: def __init__(self, data): self.events = [] self.timezones = {} self.raw_data = data self.readEvents() def readEvents(self): self.events = [] self.timezones = {} parser = Calendar.from_string(self.raw_data) tzs = parser.walk("vtimezone") self.parseTzs(tzs) events = parser.walk("vevent") for event in events: res = self.parseEvent(event) if res: self.events.append(res) def parseTzs(self, tzs): if not tzs: return for tz in tzs: if 'X-LIC-LOCATION' in tz: del tz['X-LIC-LOCATION'] data = ''.join([str(i) for i in tzs]) data = '\r\n'.join([i for i in data.splitlines() if i.strip()]) fd = StringIO(data) times = tzmod.tzical(fd) for tz in times.keys(): self.timezones[tz] = times.get(tz) def parseEvent(self, e): for k in ["dtstart", "dtend", "summary"]: if not k in e: return if not isinstance(e['dtstart'].dt, datetime.datetime): return return ICalEvent.from_event(e, self) startDate = endDate = rule = summary = None startDate = self.parseDate(e.get("dtstart")) endDate = self.parseDate(e.get("dtend")) rule = e.get("RRULE") summary = e.get("summary") if e.get("exdate"): event.addExceptionDate(e['EXDATE'].ical()[7:]) if not startDate or not endDate or not summary: # Bad event return event = ICalEvent() event.raw_data = str(e) event.summary = summary event.startDate = startDate event.endDate = endDate if rule: event.addRecurrenceRule(rule) return event @staticmethod def toTz(date, tz): return datetime.datetime(date.year, date.month, date.day, date.hour, date.minute, date.second, tzinfo=tz) def parseDate(self, date): if not date: return tz = pytz.UTC if 'tzid' in date.params: tz = self.timezones[date.params['tzid']] for attr in ['hour', 'minute', 'second']: if not hasattr(date.dt, attr): return return self.toTz(date.dt, tz) # return datetime.datetime(date.dt.year, date.dt.month, date.dt.day, date.dt.hour, date.dt.minute, date.dt.second, tzinfo=tz) def selectEvents(self, selectFunction): self.events.sort() events = filter(selectFunction, self.events) return events def todaysEvents(self, event): return event.startsToday() def tomorrowsEvents(self, event): return event.startsTomorrow() def eventsFor(self, date): self.events.sort() ret = [] for event in self.events: if event.startsOn(date): ret.append(event) return re #class ICalEvent: # def __init__(self): # self.exceptionDates = [] # self.dateSet = None # # def __str__(self): # return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate) class ICalEvent(cal.Event): def __init__(self, *args, **kwargs): self.exceptionDates = [] self.dateSet = None self.__parent = super(ICalEvent, self) self.__parent.__init__(self, *args, **kwargs) @classmethod def from_event(cls, event, parent): x = cls(**dict(event)) x.__dict__ = event.__dict__ x.summary = x['summary'] x.timezone = x['dtstart'].dt.tzinfo x.startDate = parent.parseDate(x['dtstart']) x.endDate = parent.parseDate(x['dtend']) if not x.timezone: x.timezone = pytz.UTC x.startDate = parent.parseDate(x['dtstart']) x.endDate = parent.parseDate(x['dtend']) x.raw_data = str(x) if 'rrule' in event: x.addRecurrenceRule(event['rrule']) return x def __str__(self): return "%s (%s - %s)" % (self.summary, self.startDate, self.endDate) def __eq__(self, otherEvent): return self.startDate == otherEvent.startDate def __lt__(self, otherEvent): return self.startDate < otherEvent.startDate def __gt__(self, otherEvent): return self.startDate > otherEvent.startDate def __ge__(self, otherEvent): return self.startDate >= otherEvent.startDate def __le__(self, otherEvent): return self.startDate <= otherEvent.startDate def addExceptionDate(self, date): self.exceptionDates.append(date) def addRecurrenceRule(self, rule): self.dateSet = DateSet(self.startDate, self.endDate, rule) def startsToday(self): return self.startsOn(datetime.datetime.today()) def startsTomorrow(self): tomorrow = datetime.datetime.fromtimestamp(time.time() + SECONDS_PER_DAY) return self.startsOn(tomorrow) def startsOn(self, date): return (self.startDate.year == date.year and self.startDate.month == date.month and self.startDate.day == date.day or (self.dateSet and self.dateSet.includes(date))) def startTime(self): return self.startDate def schedule(self, timezone=None): if not timezone: return "%s UTC: %s" % (self.startDate.strftime("%d %b %H:%M"), self.summary.replace('Meeting','').strip()) return "%s: %s" % (self.startDate.astimezone(pytz.timezone(timezone)).strftime("%d %b %H:%M %Z"), self.summary.replace('Meeting','').strip()) def is_on(self): return self.startDate < datetime.datetime.now(pytz.UTC) and self.endDate > datetime.datetime.now(pytz.UTC) def has_passed(self): return self.endDate < datetime.datetime.now(pytz.UTC) def seconds_to_go(self): return seconds(self.startDate - datetime.datetime.now(pytz.UTC)) def seconds_ago(self): return seconds(datetime.datetime.now(pytz.UTC) - self.endDate) def time_to_go(self): if self.endDate < datetime.datetime.now(pytz.UTC): return False delta = self.startDate - datetime.datetime.now(pytz.UTC) s = '' if delta.days: if delta.days != 1: s = 's' return '%d day%s' % (delta.days, s) h = '' if delta.seconds > 7200: s = 's' if delta.seconds > 3600: h = '%d hour%s ' % (int(delta.seconds/3600),s) s = '' minutes = (delta.seconds % 3600) / 60 if minutes != 1: s = 's' return '%s%d minute%s' % (h,minutes,s) class DateSet: def __init__(self, startDate, endDate, rule): self.startDate = startDate self.endDate = endDate self.frequency = None self.count = None self.untilDate = None self.byMonth = None self.byDate = None self.parseRecurrenceRule(rule) def parseRecurrenceRule(self, rule): freq = rruler.rrule_map[rule.pop('freq')[0]] self.recurrence = rruler.rrule_wrapper(freq, **rule) # if 'freq' in rule: # self.frequency = rule['freq'] # if 'count' in rule: # self.count = rule['count'] # if 'until' in rule: ## self.untilDate = rule['until'][0].strftime("%Y%m%dT%H%M%SZ") # self.untilDate = rule['until'][0] # if 'interval' in rule: # self.interval = rule['interval'] # if 'bymonth' in rule: # self.myMonth = rule['bymonth'] # if 'byday' in rule: # self.byDay = rule['byday'] def includes(self, date): if isinstance(date, datetime.datetime): date = date.date() return date in [x.date() for x in list(self.recurrence)] or date == self.startDate.date() # if date == self.startDate: # return True # # if self.untilDate and date > self.untilDate: # return False # # if self.frequency == 'DAILY': # increment = 1 # if self.interval: # increment = self.interval # d = self.startDate # counter = 0 # while(d < date): # if self.count: # counter += 1 # if counter >= self.count: # return False # # d = d.replace(day=d.day+1) # # if (d.day == date.day and # d.year == date.year and # d.month == date.month): # return True # # elif self.frequency == 'WEEKLY': # if self.startDate.weekday() == date.weekday(): # return True # else: # if self.endDate: # for n in range(0, self.endDate.day - self.startDate.day): # newDate = self.startDate.replace(day=self.startDate.day+n) # if newDate.weekday() == date.weekday(): # return True # # elif self.frequency == 'MONTHLY': # if self.startDate.month == date.month: # if self.startDate.weekday() == date.weekday(): # return True # # elif self.frequency == 'YEARLY': # if (self.startDate.month == date.month) and (self.startDate.day == date.day): # return True # # return False