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 convertion in case a given timezone has a RRULE in the future and the start date in the past of the datetime in question #421

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
135 changes: 67 additions & 68 deletions lib/ical/timezone.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,88 +318,87 @@
return changebase;
}

if (!aComponent.hasProperty("rrule") && !aComponent.hasProperty("rdate")) {
// add one change for the start date
change = init_changes();
change.year = dtstart.year;
change.month = dtstart.month;
change.day = dtstart.day;
change.hour = dtstart.hour;
change.minute = dtstart.minute;
change.second = dtstart.second;

ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
changes.push(change);

var props = aComponent.getAllProperties("rdate");
for (var rdatekey in props) {
/* istanbul ignore if */
if (!props.hasOwnProperty(rdatekey)) {
continue;
}
var rdate = props[rdatekey];
var time = rdate.getFirstValue();
change = init_changes();
change.year = dtstart.year;
change.month = dtstart.month;
change.day = dtstart.day;
change.hour = dtstart.hour;
change.minute = dtstart.minute;
change.second = dtstart.second;

ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
changes.push(change);
} else {
var props = aComponent.getAllProperties("rdate");
for (var rdatekey in props) {
/* istanbul ignore if */
if (!props.hasOwnProperty(rdatekey)) {
continue;
}
var rdate = props[rdatekey];
var time = rdate.getFirstValue();
change = init_changes();

change.year = time.year;
change.month = time.month;
change.day = time.day;

if (time.isDate) {
change.hour = dtstart.hour;
change.minute = dtstart.minute;
change.second = dtstart.second;
change.year = time.year;
change.month = time.month;
change.day = time.day;

if (dtstart.zone != ICAL.Timezone.utcTimezone) {
ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
}
} else {
change.hour = time.hour;
change.minute = time.minute;
change.second = time.second;
if (time.isDate) {
change.hour = dtstart.hour;
change.minute = dtstart.minute;
change.second = dtstart.second;

if (time.zone != ICAL.Timezone.utcTimezone) {
ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
}
if (dtstart.zone != ICAL.Timezone.utcTimezone) {
ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
}
} else {
change.hour = time.hour;
change.minute = time.minute;
change.second = time.second;

changes.push(change);
if (time.zone != ICAL.Timezone.utcTimezone) {
ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
}
}

var rrule = aComponent.getFirstProperty("rrule");

if (rrule) {
rrule = rrule.getFirstValue();
change = init_changes();
changes.push(change);
}

if (rrule.until && rrule.until.zone == ICAL.Timezone.utcTimezone) {
rrule.until.adjust(0, 0, 0, change.prevUtcOffset);
rrule.until.zone = ICAL.Timezone.localTimezone;
}
var rrule = aComponent.getFirstProperty("rrule");

var iterator = rrule.iterator(dtstart);
if (rrule) {
rrule = rrule.getFirstValue();
change = init_changes();

var occ;
while ((occ = iterator.next())) {
change = init_changes();
if (occ.year > aYear || !occ) {
break;
}
if (rrule.until && rrule.until.zone == ICAL.Timezone.utcTimezone) {
rrule.until.adjust(0, 0, 0, change.prevUtcOffset);
rrule.until.zone = ICAL.Timezone.localTimezone;
}

change.year = occ.year;
change.month = occ.month;
change.day = occ.day;
change.hour = occ.hour;
change.minute = occ.minute;
change.second = occ.second;
change.isDate = occ.isDate;
var iterator = rrule.iterator(dtstart);

ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
changes.push(change);
var occ;
while ((occ = iterator.next())) {
change = init_changes();
if (occ.year > aYear || !occ) {
break;
}

change.year = occ.year;
change.month = occ.month;
change.day = occ.day;
change.hour = occ.hour;
change.minute = occ.minute;
change.second = occ.second;
change.isDate = occ.isDate;

ICAL.Timezone.adjust_change(change, 0, 0, 0,
-change.prevUtcOffset);
changes.push(change);
}
}

Expand Down
22 changes: 22 additions & 0 deletions samples/timezones/Europe/Berlin.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
BEGIN:VCALENDAR
PRODID:-//tzurl.org//NONSGML Olson 2012h//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR

20 changes: 20 additions & 0 deletions samples/timezones/Europe/Berlin2.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
BEGIN:VCALENDAR
PRODID:-//tzurl.org//NONSGML Olson 2012h//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Berlin2
BEGIN:STANDARD
DTSTART:20191027T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20190331T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RDATE:20200329T020000
TZNAME:CEST
END:DAYLIGHT
END:VTIMEZONE
END:VCALENDAR
20 changes: 20 additions & 0 deletions samples/timezones/Europe/Vienna.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
BEGIN:VCALENDAR
PRODID:-//tzurl.org//NONSGML Olson 2012h//EN
BEGIN:VTIMEZONE
TZID:Europe/Vienna
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
40 changes: 38 additions & 2 deletions test/timezone_test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
suite('timezone', function() {
var icsData;
var timezone;
var icsData, icsDataTwo;
var timezone, timezoneTwo;

function timezoneTest(tzid, name, testCb) {
if (typeof(name) === 'function') {
testCb = name;
name = 'parse';
}

var tzidTwo;
if (Array.isArray(tzid)) {
tzidTwo = tzid[1]
tzid = tzid[0]
}

suite(tzid, function() {
if (tzid == "UTC") {
setup(function() {
Expand All @@ -18,16 +24,30 @@ suite('timezone', function() {
timezone = ICAL.Timezone.localTimezone;
});
} else {
icsData = null;
icsDataTwo = null;
testSupport.defineSample('timezones/' + tzid + '.ics', function(data) {
icsData = data;
});
if (tzidTwo) {
testSupport.defineSample('timezones/' + tzidTwo + '.ics', function(data) {
icsDataTwo = data;
});
}

setup(function() {
var parsed = ICAL.parse(icsData);
var vcalendar = new ICAL.Component(parsed);
var comp = vcalendar.getFirstSubcomponent('vtimezone');

timezone = new ICAL.Timezone(comp);
if (icsDataTwo !== null) {
var parsed = ICAL.parse(icsDataTwo);
var vcalendar = new ICAL.Component(parsed);
var comp = vcalendar.getFirstSubcomponent('vtimezone');

timezoneTwo = new ICAL.Timezone(comp);
}
});
}

Expand Down Expand Up @@ -236,6 +256,22 @@ suite('timezone', function() {
var subject3 = subject2.convertToZone(ICAL.Timezone.localTimezone);
assert.equal(subject3.toString(), '2012-03-11T01:59:00');
});
timezoneTest(['Europe/Vienna', 'Europe/Berlin'], 'convert from Europe/Berlin', function() {
var subject = new ICAL.Time.fromString('2019-10-25T17:02:00Z');
subject.zone = timezoneTwo;
var subject2 = subject.convertToZone(timezone);

assert.equal(subject2.zone.tzid, timezone.tzid);
assert.equal(subject2.toString(), '2019-10-25T17:02:00');
})
timezoneTest(['Europe/Vienna', 'Europe/Berlin2'], 'convert from Europe/Berlin2', function() {
var subject = new ICAL.Time.fromString('2019-10-25T15:30:00Z');
subject.zone = timezone;
var subject2 = subject.convertToZone(timezoneTwo);

assert.equal(subject2.zone.tzid, timezoneTwo.tzid);
assert.equal(subject2.toString(), '2019-10-25T15:30:00');
})
});

suite('#fromData', function() {
Expand Down