import {Event} from './event.interface.js'; /** * Interface to external classes - Turns the given events into an ical string * @param events */ export const convertToIcal = async (events: Event[]): Promise => { try { let ical: iCalFile = {body: []}; generateHeaderInfo(ical); generateFooterInfo(ical); for (let event of events) { addEventToFile(ical, event); } return serializeIcalFile(ical); } catch (err) { throw err; } }; /** * Method to serialize an iCalFile object into an ical string * @param ical */ const serializeIcalFile = (ical: iCalFile): string => { let returnString = ''; returnString += ical.header; for (let event of ical.body) { returnString += serializeIcalEvent(event); } returnString += ical.footer; return returnString; }; /** * Method to serialize a single ical event into an ical event string * @param icalevent */ const serializeIcalEvent = (icalevent: iCalEvent): string => { let returnString = ''; returnString += icalevent.header; returnString += 'UID:' + icalevent.uid; returnString += 'DTSTAMP:' + icalevent.created; returnString += 'ORGANIZER:' + icalevent.organizer; if(icalevent.wholeDay) { returnString += 'DTSTART;VALUE=DATE:' + icalevent.start; returnString += 'DTEND;VALUE=DATE:' + icalevent.end; } else { returnString += 'DTSTART;TZID=Europe/Berlin:' + icalevent.start; returnString += 'DTEND;TZID=Europe/Berlin:' + icalevent.end; } if(!isNullOrBlank(icalevent.repeatFrequency)) returnString += 'RRULE:FREQ=' + icalevent.repeatFrequency; returnString += 'SUMMARY:' + icalevent.summary; if(!isNullOrBlank(icalevent.description)) returnString += 'DESCRIPTION:' + icalevent.description; if(!isNullOrBlank(icalevent.location)) returnString += 'LOCATION:' + icalevent.location; if(!isNullOrBlank(icalevent.url)) returnString += 'URL:' + icalevent.url; returnString += icalevent.footer; return returnString; }; /** * Method to generate the ical header string * @param ical */ const generateHeaderInfo = (ical: iCalFile) => { ical.header = 'BEGIN:VCALENDAR\n' + 'VERSION:2.0\n' + 'PRODID:-//Nachklang e.V./Nachklang Calendar//NONSGML v1.0//EN\n' + 'CALSCALE:GREGORIAN\n' + 'BEGIN:VTIMEZONE\n' + 'TZID:Europe/Berlin\n' + 'LAST-MODIFIED:20201011T015911Z\n' + 'TZURL:http://tzurl.org/zoneinfo-outlook/Europe/Berlin\n' + 'X-LIC-LOCATION:Europe/Berlin\n' + 'BEGIN:DAYLIGHT\n' + 'TZNAME:CEST\n' + 'TZOFFSETFROM:+0100\n' + 'TZOFFSETTO:+0200\n' + 'DTSTART:19700329T020000\n' + 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\n' + 'END:DAYLIGHT\n' + 'BEGIN:STANDARD\n' + 'TZNAME:CET\n' + 'TZOFFSETFROM:+0200\n' + 'TZOFFSETTO:+0100\n' + 'DTSTART:19701025T030000\n' + 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\n' + 'END:STANDARD\n' + 'END:VTIMEZONE\n'; }; /** * Method to generate the ical footer info * @param ical */ const generateFooterInfo = (ical: iCalFile) => { ical.footer = 'END:VCALENDAR'; }; /** * Method to add events to the iCalFile object * @param ical * @param event */ const addEventToFile = (ical: iCalFile, event: Event) => { ical.body.push(createIcalEvent(event)); }; /** * Method to turn an event object into an iCalEvent object * @param event */ const createIcalEvent = (event: Event): iCalEvent => { let description = event.description ? event.description + '\n' : ''; let location = event.location ? event.location + '\n' : ''; let url = event.url ? event.url + '\n' : ''; return { header: 'BEGIN:VEVENT\n', uid: event.uuid + '\n', created: formatDate(event.createdDate) + 'Z\n', organizer: event.createdBy + '\n', start: formatDate(event.startDateTime, event.wholeDay) + '\n', end: formatDate(event.endDateTime, event.wholeDay, true) + '\n', repeatFrequency: event.repeatFrequency ? event.repeatFrequency + '\n' : '', summary: event.name + '\n', description: description, location: location, url: url, wholeDay: event.wholeDay, footer: 'END:VEVENT\n' }; }; /** * Helper method to format dates in a valid iCal format * @param date * @param wholeDayFormat * @param isEndDate */ const formatDate = (date: Date, wholeDayFormat: boolean = false, isEndDate: boolean = false): string => { let returnString = ''; // We need to do this for whole day events as otherwise the event ends one day too early if(wholeDayFormat && isEndDate) date.setDate(date.getDate() + 1) returnString += date.getFullYear(); returnString += (date.getMonth() + 1).toString().padStart(2, '0'); // +1 Because JS sucks returnString += date.getDate().toString().padStart(2, '0'); if(!wholeDayFormat) { returnString += 'T'; returnString += date.getHours().toString().padStart(2, '0'); returnString += date.getMinutes().toString().padStart(2, '0'); returnString += date.getSeconds().toString().padStart(2, '0'); } return returnString; }; export interface iCalFile { header?: string; body: iCalEvent[]; footer?: string; } export interface iCalEvent { header: string; uid: string; created: string; organizer: string; start: string; end: string; summary: string; description: string; location: string; url: string; wholeDay: boolean; repeatFrequency: string; footer: string; } /** * Checks if a given string is null, undefined or blank * @param str The string to check */ function isNullOrBlank(str: string | null): boolean { return str === null || str === undefined || str.trim() === ''; }