diff --git a/assets/ics2csv.py b/assets/ics2csv.py new file mode 100644 index 0000000..30cc221 --- /dev/null +++ b/assets/ics2csv.py @@ -0,0 +1,101 @@ +import icalendar + +def DumpComponentHeader(file, component): + fields = [] + inclusiveUnitCounter = 0 + fields += map(lambda x: x + ' - required', component.required) + fields += map(lambda x: x + ' - singletons', component.singletons) + for units in component.inclusive: + fields += map(lambda x: x + ' - inclusive{}'.format(inclusiveUnitCounter), units) + inclusiveUnitCounter += 1 + fields += ('exclusive - name', 'exclusive - data') + fields += ('multiple', ) + file.write(','.join(fields)) + file.write('\n') + +def DumpComponentData(file, component): + data = [] + gotten_instance = None + + for item in component.required: + gotten_instance = component.get(item) + if gotten_instance is not None: + data.append(AdvancedFormater(gotten_instance)) + else: + data.append('') + for item in component.singletons: + gotten_instance = component.get(item) + if gotten_instance is not None: + data.append(AdvancedFormater(gotten_instance)) + else: + data.append('') + for units in component.inclusive: + for item in units: + gotten_instance = component.get(item) + if gotten_instance is not None: + data.append(AdvancedFormater(gotten_instance)) + else: + data.append('') + + gotten_name = "" + gotten_data = "" + for item in component.exclusive: + gotten_instance = component.get(item) + if gotten_instance is not None: + gotten_name = item + gotten_data = AdvancedFormater(gotten_instance) + break + data.append(gotten_name) + data.append(gotten_data) + + for item in component.multiple: + gotten_instance = component.get(item) + if gotten_instance is not None: + data.append('- {} -'.format(item)) + data.append(AdvancedFormater(gotten_instance)) + else: + data.append('') + + file.write(','.join(data)) + file.write('\n') + +def AdvancedFormater(data): + if isinstance(data, icalendar.prop.vDDDTypes): + return str(data.dt) + else: + return str(data) + +# read file +icsFile = open('test.ics', 'rb') +cal = icalendar.Calendar.from_ical(icsFile.read()) +icsFile.close() + +# analyse file +csvEvent = open('event.csv', 'w') +csvEventHeader = False +csvAlarm = open('alarm.csv', 'w') +csvAlarmHeader = False + +eventCount = 0 +alarmCount = 0 +miscCount = 0 +for component in cal.walk(): + if component.name == 'VEVENT': + eventCount += 1 + if not csvEventHeader: + DumpComponentHeader(csvEvent, component) + csvEventHeader = True + DumpComponentData(csvEvent, component) + elif component.name == 'VALARM': + alarmCount += 1 + if not csvAlarmHeader: + DumpComponentHeader(csvAlarm, component) + csvAlarmHeader = True + DumpComponentData(csvAlarm, component) + else: + miscCount += 1 + + +csvEvent.close() +csvAlarm.close() +print('Event count: {}\nAlarm count: {}\nMisc count: {}'.format(eventCount, alarmCount, miscCount)) \ No newline at end of file diff --git a/assets/ics_converter.py b/assets/ics_converter.py new file mode 100644 index 0000000..8b5212c --- /dev/null +++ b/assets/ics_converter.py @@ -0,0 +1,156 @@ +import icalendar +import sys +import os +import database +import json +import datetime +import dt as localdt + +def AdvancedDatetTimeGet(dt, isStartDateTime): + if isinstance(dt, datetime.datetime): + gottenDatetime = int(dt.timestamp() / 60) + elif isinstance(dt, datetime.date): + gottenDatetime = int(datetime.datetime( + dt.year, + dt.month, + dt.day, + 0 if isStartDateTime else 23, + 0 if isStartDateTime else 59, + 0 if isStartDateTime else 59, + 0, tzinfo=LOCAL_TZ + ).timestamp() / 60) + else: + raise Exception('Unexpected data') + + timezoneOffset = LOCAL_UTC_OFFSET + return (gottenDatetime, timezoneOffset) + +def AdvancedDateTimeAnalyser(component): + startDatetimeRef = component.get('DTSTART').dt + (startDatetime, timezoneOffset) = AdvancedDatetTimeGet(startDatetimeRef, True) + + if component.get('DTEND') is not None: + (endDatetime, _) = AdvancedDatetTimeGet(startDatetimeRef, False) + elif component.get('DURATION') is not None: + endDurationRef = component.get('DURATION').dt + if isinstance(endDurationRef, datetime.timedelta): + endDatetime = startDatetime + int(endDurationRef.total_seconds() / 60) + else: + raise Exception('Unexpected data') + else: + raise Exception('Unexpected data') + + return (startDatetime, endDatetime, timezoneOffset) + +def LoopRulesConverter(component): + jsonData = component.get('RRULE') + if jsonData is None: + return "" + + loopRules = "" + loopStopRules = "" + freq = jsonData.get('FREQ')[0] + if freq == 'MONTHLY': + loopRules = 'MSA{}'.format(str(jsonData.get('INTERVAL')[0])) + elif freq == 'WEEKLY': + occupiedWeek = [False, ] * 7 + for item in jsonData.get('BYDAY'): + occupiedWeek[WEEK_DICT[item]] = True + loopRules = 'W{}{}'.format( + ''.join(map(lambda x: 'T' if x else 'F', occupiedWeek)), + str(jsonData.get('INTERVAL')[0]) + ) + elif freq == 'YEARLY': + loopRules = 'YS{}'.format(str(jsonData.get('INTERVAL')[0])) + else: + raise Exception('Unexpected data') + + if jsonData.get('COUNT') is not None: + loopStopRules = 'T{}'.format(str(jsonData.get('COUNT')[0])) + else: + loopStopRules = 'F' + + return loopRules + '-' + loopStopRules + +# ============================ read args +icsFilePath = sys.argv[1] +if not os.path.isfile(icsFilePath): + print('Fail to load ics file') + sys.exit(1) + +# read file +icsFile = open(icsFilePath, 'rb') +cal = icalendar.Calendar.from_ical(icsFile.read()) +icsFile.close() + +# ============================ init const +utfOffset = float(input('Input this ics file\'s utc offset (time unit: hour)>')) +LOCAL_UTC_OFFSET = int(utfOffset * 60) +LOCAL_TZ = localdt.UTCTimezone(LOCAL_UTC_OFFSET) +WEEK_DICT = { + "SU": 6, "MO": 0, "TU": 1, "WE": 2, "TH": 3, "FR": 4, "SA": 5, +} + +# ============================ pick database + +db = database.CalendarDatabase() +db.open() +username = input('Input username >') +password = input('Input password >') +(status, error, token) = db.common_webLogin(username, password, 'Python backend', '127.0.0.1') +if not status: + print('Fail to login.') + sys.exit(1) +(status, error, collectionList) = db.collection_getFullOwn(token) +if not status: + print('Database return an error') + sys.exit(1) +print('Pick a collection to insert imported events') +counter = 0 +for i in collectionList: + print('{}\t{}'.format(counter, i[1])) + counter += 1 +pickedIndex = int(input()) +collectionUuid = collectionList[pickedIndex][0] + +# ============================ analyse file +eventCount = 0 +allCount = 0 +for component in cal.walk(): + allCount += 1 + # only import event chunk + if component.name == 'VEVENT': + eventCount += 1 + title = str(component.get('SUMMARY')) + descriptionPrototype = { + 'color': '#1e90ff', + 'description': None + } + descriptionList = [] + if component.get('DESCRIPTION') is not None and str(component.get('DESCRIPTION')) != '': + descriptionList.append(component.get('DESCRIPTION')) + if component.get('LOCATION') is not None and str(component.get('LOCATION')) != '': + descriptionList.append(component.get('LOCATION')) + descriptionPrototype['description'] = '\n'.join(descriptionList) + description = json.dumps(descriptionPrototype) + + (eventDateTimeStart, eventDateTimeEnd, timezoneOffset) = AdvancedDateTimeAnalyser(component) + loopRules = LoopRulesConverter(component) + + (status, _, _) = db.calendar_add( + token, + collectionUuid, + title, + description, + eventDateTimeStart, + eventDateTimeEnd, + loopRules, + timezoneOffset + ) + if not status: + print('Database return an error') + sys.exit(1) + +db.common_logout(token) +db.close() +print('All chunk: {}\nEvent count: {}'.format(allCount, eventCount)) \ No newline at end of file