Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for timezones #539

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 28 additions & 25 deletions src/calendars/parsing/ics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { DateTime } from "luxon";
import { rrulestr } from "rrule";

function getDate(t: ical.Time): string {
return DateTime.fromSeconds(t.toUnixTime(), { zone: "UTC" }).toISODate();
return DateTime.fromSeconds(t.toUnixTime()).toISODate();
}

function getTime(t: ical.Time): string {
if (t.isDate) {
return "00:00";
}
return DateTime.fromSeconds(t.toUnixTime(), { zone: "UTC" }).toISOTime({
return DateTime.fromSeconds(t.toUnixTime()).toISOTime({
includeOffset: false,
includePrefix: false,
suppressMilliseconds: true,
Expand All @@ -31,7 +31,15 @@ function specifiesEnd(iCalEvent: ical.Event) {
);
}

function icsToOFC(input: ical.Event): OFCEvent {
function icsToOFC(input: ical.Event, tz: ical.Timezone | null): OFCEvent {
const tzConvert = (time: ical.Time) => {
let convertedTime = time;
if (tz != null && tz.tzid != null && tz.tzid.length > 0) {
convertedTime = time.convertToZone(tz);
}
return convertedTime.convertToZone(ical.Timezone.utcTimezone);
};

if (input.isRecurring()) {
const rrule = rrulestr(
input.component.getFirstProperty("rrule").getFirstValue().toString()
Expand All @@ -46,34 +54,27 @@ function icsToOFC(input: ical.Event): OFCEvent {
return getDate(exdate);
});

const startDate = tzConvert(input.startDate);
return {
type: "rrule",
title: input.summary,
id: `ics::${input.uid}::${getDate(input.startDate)}::recurring`,
id: `ics::${input.uid}::${getDate(startDate)}::recurring`,
rrule: rrule.toString(),
skipDates: exdates,
startDate: getDate(
input.startDate.convertToZone(ical.Timezone.utcTimezone)
),
startDate: getDate(startDate),
...(allDay
? { allDay: true }
: {
allDay: false,
startTime: getTime(
input.startDate.convertToZone(
ical.Timezone.utcTimezone
)
),
endTime: getTime(
input.endDate.convertToZone(ical.Timezone.utcTimezone)
),
startTime: getTime(startDate),
endTime: getTime(tzConvert(input.endDate)),
}),
};
} else {
const date = getDate(input.startDate);
const date = getDate(tzConvert(input.startDate));
const endDate =
specifiesEnd(input) && input.endDate
? getDate(input.endDate)
? getDate(tzConvert(input.endDate))
: undefined;
const allDay = input.startDate.isDate;
return {
Expand All @@ -86,8 +87,8 @@ function icsToOFC(input: ical.Event): OFCEvent {
? { allDay: true }
: {
allDay: false,
startTime: getTime(input.startDate),
endTime: getTime(input.endDate),
startTime: getTime(tzConvert(input.startDate)),
endTime: getTime(tzConvert(input.endDate)),
}),
};
}
Expand All @@ -97,10 +98,6 @@ export function getEventsFromICS(text: string): OFCEvent[] {
const jCalData = ical.parse(text);
const component = new ical.Component(jCalData);

// TODO: Timezone support
// const tzc = component.getAllSubcomponents("vtimezone");
// const tz = new ical.Timezone(tzc[0]);

const events: ical.Event[] = component
.getAllSubcomponents("vevent")
.map((vevent) => new ical.Event(vevent))
Expand All @@ -116,17 +113,23 @@ export function getEventsFromICS(text: string): OFCEvent[] {
}
});

let tz: ical.Timezone | null = null;
const tzc = component.getAllSubcomponents("vtimezone");
if (tzc != null) {
tz = new ical.Timezone(tzc[0]);
}

// Events with RECURRENCE-ID will have duplicated UIDs.
// We need to modify the base event to exclude those recurrence exceptions.
const baseEvents = Object.fromEntries(
events
.filter((e) => e.recurrenceId === null)
.map((e) => [e.uid, icsToOFC(e)])
.map((e) => [e.uid, icsToOFC(e, tz)])
);

const recurrenceExceptions = events
.filter((e) => e.recurrenceId !== null)
.map((e): [string, OFCEvent] => [e.uid, icsToOFC(e)]);
.map((e): [string, OFCEvent] => [e.uid, icsToOFC(e, tz)]);

for (const [uid, event] of recurrenceExceptions) {
const baseEvent = baseEvents[uid];
Expand Down