Skip to content

Commit

Permalink
Support use of timezones specified in ICS
Browse files Browse the repository at this point in the history
  • Loading branch information
leftmostcat committed Mar 7, 2023
1 parent 3f0838c commit 1d3e74b
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 28 deletions.
14 changes: 14 additions & 0 deletions lib/ical/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -502,5 +502,19 @@ class Component {
this.jCal, this._designSet
);
}

/**
* Returns the highest-level component containing this component. If the
* component has no parent, this component will be returned.
*
* @return {ICAL.Component}
*/
getRootComponent() {
if (!this.parent) {
return this;
}

return this.parent.getRootComponent();
}
}
export default Component;
13 changes: 13 additions & 0 deletions lib/ical/property.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,5 +404,18 @@ class Property {
this.jCal, this._designSet, true
);
}

/**
* Returns the highest-level component containing this property, if any.
*
* @return {ICAL.Component}
*/
getRootComponent() {
if (!this.parent) {
return null;
}

return this.parent.getRootComponent();
}
}
export default Property;
38 changes: 31 additions & 7 deletions lib/ical/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,25 +165,49 @@ class Time {
}

let zone;
let zoneId;

if (aValue[19] && aValue[19] === 'Z') {
zone = 'Z';
zone = Timezone.utcTimezone;
} else if (prop) {
zone = prop.getParameter('tzid');
zoneId = prop.getParameter('tzid');

if (prop.parent) {
if (prop.parent.name === 'standard' || prop.parent.name === 'daylight') {
zone = Timezone.floating;
} else {
const root = prop.getRootComponent();

const rootZones = root.getAllSubcomponents('vtimezone');
for (const rootZone of rootZones) {
if (rootZone.getFirstProperty('tzid').getFirstValue() === zoneId) {
zone = new Timezone({
component: rootZone,
tzid: zoneId,
});

break;
}
}
}
}
}

// 2012-10-10T10:10:10(Z)?
let time = new Time({
const timeData = {
year: strictParseInt(aValue.slice(0, 4)),
month: strictParseInt(aValue.slice(5, 7)),
day: strictParseInt(aValue.slice(8, 10)),
hour: strictParseInt(aValue.slice(11, 13)),
minute: strictParseInt(aValue.slice(14, 16)),
second: strictParseInt(aValue.slice(17, 19)),
timezone: zone
});
};

if (zoneId && !zone) {
timeData.timezone = zoneId;
}

return time;
// 2012-10-10T10:10:10(Z)?
return new Time(timeData, zone);
}

/**
Expand Down
18 changes: 18 additions & 0 deletions samples/timezone_from_file.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Nowhere/Middle
BEGIN:STANDARD
DTSTART:16010101T000000
TZOFFSETFROM:-0741
TZOFFSETTO:-0741
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTAMP:20230306T080000Z
DTSTART;TZID=Nowhere/Middle:20230306T134200
DTEND;TZID=Nowhere/Middle:20230306T144200
SUMMARY:A test event
END:VEVENT
END:VCALENDAR
24 changes: 24 additions & 0 deletions test/parse_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,28 @@ suite('parserv2', function() {
assert.deepEqual(unfold(input), expected);
});
});

suite('embedded timezones', function() {
let icsDataEmbeddedTimezones;
suiteSetup(async function() {
icsDataEmbeddedTimezones = await testSupport.loadSample('timezone_from_file.ics');
});

test('used in event date', function() {
const parsed = ICAL.parse(icsDataEmbeddedTimezones);
const component = new ICAL.Component(parsed);

const event = new ICAL.Event(component.getFirstSubcomponent('vevent'));
const startDate = event.startDate.toJSDate();
const endDate = event.endDate.toJSDate();

assert.equal(startDate.getUTCDate(), 6);
assert.equal(startDate.getUTCHours(), 21);
assert.equal(startDate.getUTCMinutes(), 23);

assert.equal(endDate.getUTCDate(), 6);
assert.equal(endDate.getUTCHours(), 22);
assert.equal(endDate.getUTCMinutes(), 23);
});
});
});
47 changes: 26 additions & 21 deletions test/recur_expansion_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ suite('recur_expansion', function() {
suite('initialization', function() {
test('successful', function() {
assert.deepEqual(
new Date(2012, 9, 2, 10),
subject.last.toJSDate()
subject.last.toJSDate(),
new Date('2012-10-02T17:00:00Z')
);

assert.instanceOf(subject.ruleIterators, Array);
Expand Down Expand Up @@ -85,31 +85,31 @@ suite('recur_expansion', function() {
suite('#_ensureRules', function() {
test('.ruleDates', function() {
let expected = [
new Date(2012, 10, 5, 10),
new Date(2012, 10, 10, 10),
new Date(2012, 10, 30, 10)
new Date('2012-11-05T18:00:00.000Z'),
new Date('2012-11-10T18:00:00.000Z'),
new Date('2012-11-30T18:00:00.000Z')
];


let dates = subject.ruleDates.map(function(time) {
return time.toJSDate();
});

assert.deepEqual(expected, dates);
assert.deepEqual(dates, expected);
});

test('.exDates', function() {
let expected = [
new Date(2012, 11, 4, 10),
new Date(2013, 1, 5, 10),
new Date(2013, 3, 2, 10)
new Date('2012-12-04T18:00:00.000Z'),
new Date('2013-02-05T18:00:00.000Z'),
new Date('2013-04-02T17:00:00.000Z')
];

let dates = subject.exDates.map(function(time) {
return time.toJSDate();
});

assert.deepEqual(expected, dates);
assert.deepEqual(dates, expected);
});
});

Expand Down Expand Up @@ -203,12 +203,12 @@ suite('recur_expansion', function() {
// I use JS dates widely because it is much easier
// to compare them via chai's deepEquals function
let expected = [
new Date(2012, 9, 2, 10),
new Date(2012, 10, 5, 10),
new Date(2012, 10, 6, 10),
new Date(2012, 10, 10, 10),
new Date(2012, 10, 30, 10),
new Date(2013, 0, 1, 10)
new Date('2012-10-02T17:00:00.000Z'),
new Date('2012-11-05T18:00:00.000Z'),
new Date('2012-11-06T18:00:00.000Z'),
new Date('2012-11-10T18:00:00.000Z'),
new Date('2012-11-30T18:00:00.000Z'),
new Date('2013-01-01T18:00:00.000Z')
];

test('6 items', function() {
Expand Down Expand Up @@ -238,11 +238,11 @@ suite('recur_expansion', function() {

let dates = [];
let expected = [
new Date(2012, 9, 2, 10),
new Date(2012, 10, 5, 10),
new Date(2012, 10, 6, 10),
new Date(2012, 10, 10, 10),
new Date(2012, 11, 4, 10)
new Date('2012-10-02T17:00:00.000Z'),
new Date('2012-11-05T18:00:00.000Z'),
new Date('2012-11-06T18:00:00.000Z'),
new Date('2012-11-10T18:00:00.000Z'),
new Date('2012-12-04T18:00:00.000Z')
];

while (inc++ < max && (next = subject.next())) {
Expand All @@ -263,6 +263,11 @@ suite('recur_expansion', function() {


suite('#toJSON', function() {
// While other tests in this file don't require specifying a timezone, we
// need to do so here because we're building the `RecurExpansion` from a
// limited subset of the ICS which does not include the timezone definition.
testSupport.useTimezones('America/Los_Angeles');

test('from start', function() {
let json = subject.toJSON();
let newIter = new ICAL.RecurExpansion(json);
Expand Down

0 comments on commit 1d3e74b

Please sign in to comment.