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

Fix timezone issues when using rrule #329

Merged
merged 10 commits into from
Sep 2, 2024
7 changes: 6 additions & 1 deletion ical.js
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,12 @@ module.exports = {
// If the original date has a TZID, add it
if (curr.start.tz) {
const tz = getTimeZone(curr.start.tz);
rule += `;DTSTART;TZID=${tz}:${curr.start.toISOString().replace(/[-:]/g, '')}`;
// If a timezone is provided, rrule requires the time to be local
const adjustedTimeString = curr.start
.toLocaleString('sv', {timeZone: tz})
.replace(/ /g, 'T')
.replace(/[-:]/g, '');
rule += `;DTSTART;TZID=${tz}:${adjustedTimeString}`;
} else {
rule += `;DTSTART=${curr.start.toISOString().replace(/[-:]/g, '')}`;
}
Expand Down
6 changes: 5 additions & 1 deletion test/test-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
** */
process.env.TZ = 'America/San_Francisco';

const moment = require('moment-timezone');
/* Setup moment timezone defaults */
moment.tz.link('Etc/Unknown|Etc/GMT');
moment.tz.setDefault('America/San_Francisco');

const assert = require('assert');
const vows = require('vows');
const _ = require('underscore');
const moment = require('moment-timezone');
const ical = require('../node-ical.js');

console.log('START Async Tests');
Expand Down
100 changes: 97 additions & 3 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
** */
process.env.TZ = 'America/San_Francisco';

const moment = require('moment-timezone');
/* Setup moment timezone defaults */
moment.tz.link('Etc/Unknown|Etc/GMT');
moment.tz.setDefault('America/San_Francisco');

const assert = require('assert');
const vows = require('vows');
const _ = require('underscore');
const moment = require('moment-timezone');
const ical = require('../node-ical.js');

vows
Expand Down Expand Up @@ -150,8 +154,10 @@ vows
});
},
'tzid offset correctly applied'(event) {
const start = new Date('2002-10-28T22:00:00.000Z');
assert.equal(event.start.valueOf(), start.valueOf());
assert.ok(moment.tz.zone(event.start.tz), 'zone does not exist');
const ref = '2002-10-28T22:00:00Z';
const start = moment(event.start).tz(event.start.tz);
assert.equal(start.utc().format(), ref);
}
}
},
Expand Down Expand Up @@ -1051,6 +1057,94 @@ vows
assert.equal(task.summary, 'test export import');
}
}
},
'with test23.ics (testing dtstart of rrule with timezones)': {
topic() {
return ical.parseFile('./test/test23.ics');
},
'first event': {
topic(events) {
return _.select(_.values(events), x => {
return x.uid === '000021a';
})[0];
},
'datetype is date-time'(topic) {
assert.equal(topic.datetype, 'date-time');
},
'has GMT+1 timezone'(topic) {
assert.equal(topic.start.tz, 'Europe/Berlin');
},
'starts 14 Jul 2022 @ 12:00:00 (UTC)'(topic) {
assert.equal(topic.start.toISOString(), '2022-07-14T12:00:00.000Z');
}
},
'recurring yearly first event (14 july)': {
topic(events) {
/* Skip on windows since rrule.between/after broken, cf. https://github.com/jkbrzt/rrule/issues/608 */
if (process.platform === 'win32') {
return new Date(2023, 6, 14, 12, 0, 0);
}

const ev = _.select(_.values(events), x => {
return x.uid === '000021a';
})[0];
return ev.rrule.between(new Date(2023, 0, 1), new Date(2024, 0, 1))[0];
},
'dt start well set'(topic) {
assert.equal(topic.toDateString(), new Date(2023, 6, 14).toDateString());
},
'starts 14 Jul 2023 @ 12:00:00 (UTC)'(topic) {
assert.equal(topic.toISOString(), '2023-07-14T12:00:00.000Z');
}
},
'second event': {
topic(events) {
return _.select(_.values(events), x => {
return x.uid === '000021b';
})[0];
},
'datetype is date-time'(event) {
assert.equal(event.datetype, 'date-time');
},
'start date': {
topic(event) {
return event.start;
},
'has correct timezone'(start) {
assert.equal(start.tz, 'Etc/GMT-2');
},
'starts 15 Jul 2022 @ 12:00:00 (UTC)'(start) {
assert.equal(start.toISOString(), '2022-07-15T12:00:00.000Z');
}
},
'has recurrences': {
topic(event) {
return event.rrule;
},
'that are defined'(rrule) {
assert.ok(rrule, 'no rrule defined');
},
'that have timezone info'(rrule) {
assert.ok(rrule.options.tzid, 'no tzid property on rrule');
},
'that keep correct timezone info in recurrences'(rrule) {
assert.equal(rrule.options.tzid, 'Etc/GMT-2');
}
},
'has a first recurrence': {
topic(event) {
/* Skip on windows since rrule.between/after broken, cf. https://github.com/jkbrzt/rrule/issues/608 */
if (process.platform === 'win32') {
return new Date(2023, 6, 15, 12, 0, 0);
}

return event.rrule.between(new Date(2023, 0, 1), new Date(2024, 0, 1))[0];
},
'that starts 15 Jul 2023 @ 12:00:00 (UTC)'(rc) {
assert.equal(rc.toISOString(), '2023-07-15T12:00:00.000Z');
}
}
}
}
})
.export(module);
21 changes: 21 additions & 0 deletions test/test21-mod.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
TRANSP:OPAQUE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
CREATED:20221004T073016Z
LAST-MODIFIED:20221011T063437Z
DTSTAMP:20221011T063437Z
DTSTART;TZID="(GMT +01:00)":20221004T140000
DTEND;TZID="(GMT +01:00)":20221004T150000
SUMMARY:Music School
CLASS:PUBLIC
UID:0000021
X-MOZ-SNOOZE-TIME:20221004T113000Z
X-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE:20221014T203413Z
X-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE:20221014T203413Z
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-OWNERAPPTID:-1
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
RRULE:FREQ=WEEKLY;UNTIL=20221201T020000;BYDAY=TU
END:VEVENT
END:VCALENDAR
26 changes: 26 additions & 0 deletions test/test23.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:Fictitious Recurrence Test Calendar
BEGIN:VEVENT
CREATED:20221018T221500Z
DTSTAMP:20221019T171200Z
UID:000021a
SUMMARY:Party
RRULE:FREQ=YEARLY
DTSTART;TZID=Europe/Berlin:20220714T140000
DTEND;TZID=Europe/Berlin:20220714T210000
TRANSP:OPAQUE
SEQUENCE:5
END:VEVENT
BEGIN:VEVENT
CREATED:20221019T181700Z
DTSTAMP:20221019T191200Z
UID:000021b
SUMMARY:Party next day
RRULE:FREQ=YEARLY
DTSTART;TZID=Etc/GMT-2:20220715T140000
DTEND;TZID=Etc/GMT-2:20220715T210000
TRANSP:OPAQUE
SEQUENCE:5
END:VEVENT
END:VCALENDAR
Loading