from icalendar import Calendar, Event import logging from pytz import utc import datetime class EventConverter(Event): """Convert icalendar event to google calendar resource ( https://developers.google.com/calendar/v3/reference/events#resource-representations ) """ def _str_prop(self, prop): """decoded string property Arguments: prop - propperty name Returns: string value """ return self.decoded(prop).decode(encoding='utf-8') def _datetime_str_prop(self, prop): """utc datetime as string from property Arguments: prop -- property name Returns: utc datetime value as string in iso format """ date = self.decoded(prop) if not isinstance(date, datetime.datetime): date = datetime.datetime( date.year, date.month, date.day, tzinfo=utc) date = date.replace(microsecond=1) return utc.normalize(date.astimezone(utc)).replace(tzinfo=None).isoformat() + 'Z' def _gcal_start(self): """ event start dict from icalendar event Raises: ValueError -- if DTSTART not date or datetime Returns: dict """ start_date = self.decoded('DTSTART') if isinstance(start_date, datetime.datetime): return { 'dateTime': self._datetime_str_prop('DTSTART') } else: if isinstance(start_date, datetime.date): return { 'date': start_date.isoformat() } raise ValueError('DTSTART must be date or datetime') def _gcal_end(self): """event end dict from icalendar event Raises: ValueError -- if DTEND not date or datetime ValueError -- if no DTEND or DURATION ValueError -- if end date/datetime not found Returns: dict """ if 'DTEND' in self: end_date = self.decoded('DTEND') if isinstance(end_date, datetime.datetime): return { 'dateTime': self._datetime_str_prop('DTEND') } else: if isinstance(end_date, datetime.date): return { 'date': end_date.isoformat() } raise ValueError('DTEND must be date or datetime') else: if 'DURATION' in self: start_date = self.decoded('DTSTART') duration = self.decoded('DURATION') end_date = start_date + duration if isinstance(start_date, datetime.datetime): return { 'dateTime': utc.normalize(end_date.astimezone(utc)).replace(tzinfo=None, microsecond=1).isoformat() + 'Z' } else: if isinstance(start_date, datetime.date): return { 'date': datetime.date(end_date.year, end_date.month, end_date.day).isoformat() } raise ValueError('no DTEND or DURATION') raise ValueError('end date/time not found') def _put_to_gcal(self, gcal_event, prop, func, ics_prop=None): """get property from ical event if exist, and put to gcal event Arguments: gcal_event -- dest event prop -- property name func -- function to convert ics_prop -- ical property name (default: {None}) """ if not ics_prop: ics_prop = prop if ics_prop in self: gcal_event[prop] = func(ics_prop) def to_gcal(self): """Convert Returns: dict - google calendar#event resource """ event = { 'iCalUID': self._str_prop('UID') } event['start'] = self._gcal_start() event['end'] = self._gcal_end() self._put_to_gcal(event, 'summary', self._str_prop) self._put_to_gcal(event, 'description', self._str_prop) self._put_to_gcal(event, 'location', self._str_prop) self._put_to_gcal(event, 'created', self._datetime_str_prop) self._put_to_gcal( event, 'updated', self._datetime_str_prop, 'LAST-MODIFIED') self._put_to_gcal( event, 'transparency', lambda prop: self._str_prop(prop).lower(), 'TRANSP') return event class CalendarConverter(): """Convert icalendar events to google calendar resources """ logger = logging.getLogger('CalendarConverter') def __init__(self, calendar=None): self.calendar = calendar def load(self, filename): """ load calendar from ics file """ with open(filename, 'r', encoding='utf-8') as f: self.calendar = Calendar.from_ical(f.read()) self.logger.info('%s loaded', filename) def loads(self, string): """ load calendar from ics string """ self.calendar = Calendar.from_ical(string) def events_to_gcal(self): """Convert events to google calendar resources """ ics_events = self.calendar.walk(name='VEVENT') self.logger.info('%d events readed', len(ics_events)) result = list( map(lambda event: EventConverter(event).to_gcal(), ics_events)) self.logger.info('%d events converted', len(result)) return result