from datetime import datetime, timedelta, time, date from dateutil.relativedelta import relativedelta from dateutil import tz import urllib2 import json import logging import sys import copy import os from dateutil.rrule import rrulestr import icalendar import subprocess import pytz berlin = pytz.timezone('Europe/Berlin') def fetch_calendar(logger): """Fetches the calendar events and returns a icalendar.Calendar instance. """ try: response = urllib2.urlopen(CALENDARIUM_IMPORT_URL) logger.debug('Fetched calendar from %s' % CALENDARIUM_IMPORT_URL) except Exception: sys.excepthook(*sys.exc_info()) content = None else: content = response.read() if content is None: # If we couldn't fetch the content, pull it from the copied file # if it's not there, just fail fatally as we don't have any data with open(CALENDARIUM_EXPORT, 'r') as export_file: content = export_file.read() else: with open(CALENDARIUM_EXPORT + '.new', 'w') as export_file: export_file.write(content) os.rename(CALENDARIUM_EXPORT + '.new', CALENDARIUM_EXPORT) try: with open(TODAY_EXPORT, 'r') as today_file: cached_today = today_file.read() except Exception: cached_today = None real_today = str(date.today()) + '\n' if real_today != cached_today: with open(TODAY_EXPORT, 'w') as today_file: today_file.write(real_today) regen = True else: regen = False return (regen,icalendar.Calendar.from_ical(content)) def get_events(calendar, after, before): """Yields all events in calendar as dictionary. Only events after (including) after will be shown. Recurring events til before (inclusively) will be shown. """ for event in calendar.walk('vevent'): event_fields = [ # dest, src, default ('name', 'summary', 'unknown Event'), ('description', 'description', ''), ] event_info = {} for fieldinfo in event_fields: try: event_info[fieldinfo[0]] = event[fieldinfo[1]].format().encode("utf-8") event_info[fieldinfo[0]] = event_info[fieldinfo[0]].decode("string-escape") except KeyError: event_info[fieldinfo[0]] = fieldinfo[2] start = icalendar.vDDDTypes.from_ical(event['dtstart'].to_ical()) if 'dtend' in event: end = icalendar.vDDDTypes.from_ical(event['dtend'].to_ical()) else: end = start + icalendar.vDDDTypes.from_ical(event['duration'].to_ical()) if not isinstance(start, datetime): start = datetime.combine(start, time()) if not isinstance(end, datetime): end = datetime.combine(end, time()) if end.hour == 0 and end.minute == 0 and end.second == 0: end -= timedelta(seconds=1) if start.tzinfo is not None: start = start.astimezone(berlin).replace(tzinfo=None) if end.tzinfo is not None: end = end.astimezone(berlin).replace(tzinfo=None) if 'rrule' in event: rrule = rrulestr(event['rrule'].to_ical(), dtstart=start) if rrule._until is not None and rrule._until.tzinfo is not None: rrule._until = rrule._until.astimezone(berlin).replace(tzinfo=None) duration = end - start try: for occurence in rrule.between(after, before, True): event_info['start'] = occurence event_info['end'] = occurence + duration event_info['recurring'] = True yield copy.deepcopy(event_info) except TypeError: import pdb pdb.set_trace() else: if start >= after: event_info['start'] = start event_info['end'] = end event_info['recurring'] = False yield event_info class DatetimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() return json.JSONEncoder.default(self,obj) def put_events(calendar, after, before, destination): events = [] for event in get_events(calendar, after, before): event['start'] = event['start'].isoformat() event['end'] = event['end'].isoformat() events.append(event) if os.path.exists(destination): with open(destination, 'r') as dfile: current = dfile.read() else: current = None output = json.dumps(events, indent=4) if current != output: with open(destination + '.new', 'w') as dfile: dfile.write(output) os.rename(destination + '.new', destination) return True return False base_path = os.path.dirname(os.path.abspath(__file__)) #CALENDARIUM_IMPORT_URL = 'https://sublab.org:5232/calendars/events' #CALENDARIUM_IMPORT_URL = 'https://posteo.de/calendars/ics/ruku1tibpa2b2evfwsxrbvwcy2n60j9g' CALENDARIUM_IMPORT_URL = 'https://calendar.google.com/calendar/ical/termine.sublab%40gmail.com/public/basic.ics' CALENDARIUM_EXPORT = os.path.join(base_path, '../public/calendar.ics') TODAY_EXPORT = os.path.join(base_path, 'get_calendar.today') if __name__ == '__main__': logging.basicConfig(stream=sys.stderr, level=logging.ERROR) logger = logging.getLogger('calendar_feed') regen, calendar = fetch_calendar(logger) now = datetime.now() after = now - relativedelta(days=1) before = now + relativedelta(months=+1) after_tab = datetime(now.year,now.month,1); if now.month + 2 > 12: before_tab = datetime(now.year+1,now.month - 10,1) else: before_tab = datetime(now.year,now.month+2,1) changed = put_events(calendar, after, before, os.path.join(base_path, '../template/calendar.json')) tab_changed = put_events(calendar, after_tab, before_tab, os.path.join(base_path, '../template/tabcalendar.json')) if changed or tab_changed or regen: subprocess.call(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'template.py'))