#!/usr/bin/env python3 import icalendar import recurring_ical_events import requests import datetime import pickle import pprint import json import config as cfg numberOfDaysToShow = 5 #numberOfDaysToShow = 1 useCachedData = False storeDataInCache = False includedCalendarIDs = [] for m in cfg.calendarsMetadata: excluded = False for excludedCalendar in cfg.excludedCalendarIDs: #if excludedCalendar == m["calendarName"]: if excludedCalendar == m["displayName"]: excluded = True if excluded: continue includedCalendarIDs.append(m["calendarName"]) def fetchCalendar(calendarName): url = cfg.urlBase + "/" + calendarName + cfg.urlSuffix #print(f"Fetching calendar \"{calendarName}\" from {url}...") response = requests.get(url, auth=(cfg.userN, cfg.passW)) return response.text def fetchCalendarsMetadata(): size = 0 icsData = {} for id in includedCalendarIDs: icsData[id] = fetchCalendar(id) #print(id, icsData[id]) size += len(icsData[id]) #print(f"Fetched {(float(size) / 1024 / 1024):.4} MB") return icsData def get_calendar_data(): # Fetch calendars if useCachedData: #print("Using cached data") with open('cacheData.pkl', 'rb') as fp: icsData = pickle.load(fp) else: icsData = fetchCalendarsMetadata() if storeDataInCache: # save dictionary to .pkl file (for debugging only) with open('cacheData.pkl', 'wb') as fp: pickle.dump(icsData, fp) #print('ICS data saved successfully to file') # Prepare list of days firstDay = datetime.date.today() lastDay = firstDay + datetime.timedelta(days=numberOfDaysToShow) #print("Showing all events between %s and %s" % (firstDay, lastDay)) days = [] day = firstDay while day < lastDay: days.append(day) day += datetime.timedelta(days=1) #print("Days to list: %s" % days) # extracting events events = {} for day in days: dayId = day.strftime("%m/%d/%Y") events[dayId] = {} #print(f"Extracting events for {day}...") for id in includedCalendarIDs: calendarData = icalendar.Calendar.from_ical(icsData[id]) es = recurring_ical_events.of(calendarData).at(day) for e in es: #print(e) start = e["DTSTART"].dt startDate = start.strftime("%m/%d/%Y") startTime = start.strftime("%H:%M") end = e["DTEND"].dt endDate = end.strftime("%m/%d/%Y") endTime = end.strftime("%H:%M") summary = str(e["SUMMARY"]) for m in cfg.calendarsMetadata: if id == m["calendarName"]: name = m["displayName"] break d = {"calendar": name, "summary": summary, "startDate": startDate, "startTime": startTime, "endDate": endDate, "endTime": endTime } uid = start.strftime("%H-%M") + "_" + str(e["UID"]) events[dayId][uid] = d #print(d) # Sort by time myKeys = list(events[dayId].keys()) myKeys.sort() events[dayId] = {i: events[dayId][i] for i in myKeys} #pprint.pprint(events) metaData = {} for m in cfg.calendarsMetadata: #print(m) if m["calendarName"] in includedCalendarIDs: metaData[m["displayName"]] = { "displayname": m["displayName"], "color": m["color"] } # Converting to JSON return json.dumps({"metadata": metaData, "events": events}, indent=4) # # # # numberOfDaysToShow = 5 # # #excludedCalendars = ['todo', 'todogemeinsam', 'contact_birthdays', 'geburtstage', 'infos'] # excludedCalendars = ['ToDo', 'ToDo Gemeinsam', 'Geburtstage von Kontakten', 'Geburtstage', 'Infos'] # #excludedCalendars = ['todo', 'todogemeinsam', 'contact_birthdays', 'geburtstage', 'gemeinsam', 'evtermine_shared_by_anita', 'jael'] # #excludedCalendars = ['gemeinsam', 'contact_birthdays', 'geburtstage'] # # client = caldav.DAVClient(url=cfg.url, username=cfg.userN, password=cfg.passW) # principal = client.principal() # calendars = principal.calendars() # # firstDay = datetime.date.today() # lastDay = firstDay + datetime.timedelta(days=numberOfDaysToShow) # #print("Showing all events between %s and %s" % (firstDay, lastDay)) # # days = [] # # day = firstDay # while day < lastDay: # days.append(day) # day += datetime.timedelta(days=1) # # #print("Days to list: %s" % days) # # # Get offset from local time to UTC, see also https://stackoverflow.com/questions/3168096/getting-computers-utc-offset-in-python # # TODO: This should be done for every event individually! # ts = time.time() # utcOffset = (datetime.datetime.fromtimestamp(ts) - # datetime.datetime.utcfromtimestamp(ts)).total_seconds() # utcOffset = int(utcOffset / 3600) # in hours # #print("UTC offset:", utcOffset) # # #print("All calendars:") # #for calendar in calendars: # #print(" %s" % str(calendar).split("/")[-2]) # # calendarsFiltered = [] # # # exclude some calendars: # for calendar in calendars: # #print(excludedCalendars) # #print(calendar) # #if str(calendar).split("/")[-2] in excludedCalendars: # if str(calendar) in excludedCalendars: # #print("Ignoring %s" % str(calendar).split("/")[-2]) # #print("Ignoring %s" % str(calendar)) # continue # else: # #print("Adding %s" % str(calendar).split("/")[-2]) # #print("Adding %s" % str(calendar)) # calendarsFiltered.append(calendar) # # # #print("Filtered calendars:") # #for calendar in calendarsFiltered: # #print(" %s" % str(calendar).split("/")[-2]) # # eventsList = {} # metaData = {} # # for day in days: # eventsList[day] = {} # # #print("processing calendars") # for calendar in calendarsFiltered: # #calendarName = str(calendar).split("/")[-2] # calendarName = str(calendar) # metaData[calendarName] = {} # metaData[calendarName]['displayname'] = list(calendar.get_properties([caldav.dav.DisplayName()]).values())[0] # metaData[calendarName]['color'] = list(calendar.get_properties([caldav.elements.ical.CalendarColor()]).values())[0] # #print(metaData) # # for day in days: # #print(" Looking for events between %s and %s" % (day, day + datetime.timedelta(days=1))) # results = calendar.date_search(day, day + datetime.timedelta(days=1)) # get event for selected day (24h) # # for eventraw in results: # eventUuid = str(eventraw).split("/")[-1].split(".")[0] # #print(" - %s" % eventUuid) # # # print(str(eventraw)) # #event = icalendar.Calendar.from_ical(eventraw._data) # # url = "https://" + cfg.userN + ":" + cfg.passW + "@" + str(eventraw).replace("Event: https://", "") # # print(url) # data = requests.get(url).text # # event = icalendar.Calendar.from_ical(data) # for component in event.walk(): # if component.name == "VEVENT": # eventsList[day][eventUuid] = {} # ##eventsList[day][eventUuid]["calendar"] = str(calendar).split("/")[-2] # eventsList[day][eventUuid]["calendar"] = str(calendar) # # # print(" summary: %s" % component.get('summary')) # eventsList[day][eventUuid]["summary"] = str(component.get('summary')) # # # print(" description: %s" % component.get('description')) # #eventsList[day][eventUuid]["description"] = str(component.get('description')) # # startDate = component.get('dtstart') # # print(" dtstart: %s == %s?" % (startDate.dt.strftime('%m/%d/%Y %H:%M'), day)) # if startDate.dt.strftime('%m/%d/%Y') == day.strftime('%m/%d/%Y'): # event starts today # # print(" single day event") # eventsList[day][eventUuid]["startDate"] = startDate.dt.strftime('%m/%d/%Y') # eventsList[day][eventUuid]["startTime"] = (startDate.dt + datetime.timedelta(hours=utcOffset)).strftime('%H:%M') # else: # event started before today, set startdate to day and start time to midnight (for multi day events with start/end time) # # print(" multi day event") # eventsList[day][eventUuid]["startDate"] = day.strftime('%m/%d/%Y') # # eventsList[day][eventUuid]["startTime"] = "00:00" # No longer working! # eventsList[day][eventUuid]["startTime"] = (startDate.dt + datetime.timedelta(hours=utcOffset)).strftime('%H:%M') # # try: # endDate = component.get('dtend') # #print(" dtend: %s" % endDate.dt.strftime('%m/%d/%Y %H:%M')) # if endDate.dt.strftime('%m/%d/%Y') == day.strftime('%m/%d/%Y'): # event ends today # eventsList[day][eventUuid]["endDate"] = endDate.dt.strftime('%m/%d/%Y') # eventsList[day][eventUuid]["endTime"] = (endDate.dt + datetime.timedelta(hours=utcOffset)).strftime('%H:%M') # else: # event ends after today, set enddate to day and end time to midnight (for multi day events with start/end time) # eventsList[day][eventUuid]["endDate"] = day.strftime('%m/%d/%Y') # # eventsList[day][eventUuid]["endTime"] = "24:00" # No longer working! # eventsList[day][eventUuid]["endDate"] = endDate.dt.strftime('%m/%d/%Y') # # except AttributeError as ae: # event has no endtime, use duration # #print(" %s" % ae) # duration = component.get('duration') # #print(" Event misses end time, using duration") # #print(" duration: %s" % duration.dt) # if (startDate.dt + duration.dt).strftime('%m/%d/%Y') == day.strftime('%m/%d/%Y'): # event ends today # eventsList[day][eventUuid]["endDate"] = (startDate.dt + duration.dt).strftime('%m/%d/%Y') # eventsList[day][eventUuid]["endTime"] = (startDate.dt + duration.dt + datetime.timedelta(hours=utcOffset)).strftime('%H:%M') # else: # event ends after today, set enddate to day and end time to midnight (for multi day events with start/end time) # eventsList[day][eventUuid]["endDate"] = day.strftime('%m/%d/%Y') # eventsList[day][eventUuid]["endTime"] = "24:00" # # #print('') # #break # # # # order by time per day, see https://www.geeksforgeeks.org/python-sort-nested-dictionary-by-key/ # for day in days: # sortedbyTimePerDay = OrderedDict(sorted(eventsList[day].items(), key = lambda x: getitem(x[1], 'startTime'))) # eventsList[day] = sortedbyTimePerDay # # # Sort days # eventsList = OrderedDict(sorted(eventsList.items())) # # # # reformat day key (datetime => string) # for day in days: # eventsList[day.strftime('%m/%d/%Y')] = eventsList.pop(day) # # # #pp.pprint(eventsList) # # data = {} # data['metadata'] = metaData # data['events'] = OrderedDict(eventsList) # # #pp.pprint(data) # # # # Converting to JSON # dataJson = json.dumps(data, indent=4) # return dataJson if __name__ == '__main__': print(get_calendar_data())