ical.js
ical.js copied to clipboard
Getting UTC timestamps of iCal with Timezone
I have some iCal-data with a timezone-definition (Europe/Copenhagen
; CET
/CEST
), but I can't get the dates specified in this timezone out in UTC.
Roughly:
BEGIN:VTIMEZONE
TZID:Europe/Copenhagen
...
END:VTIMEZONE
BEGIN:VEVENT
...
DTSTART;TZID=Europe/Copenhagen:20131120T113000
...
END:VEVENT
When I parse it, I it seem to believe the UTC-offset is 0, no matter how I poke at it.
event = new ICAL.Event(
ICAL.Component(ICAL.Parse(DATA)[1]).getFirstSubcomponent('vevent')
);
event.startDate.toJSDate();
// In computer localtime.
event.startDate.utcOffset();
// 0 - should be 1 or 2 hours, depending on summertime.
console.log(event.startDate);
{ wrappedJSObject: [Circular],
_time:
{ year: 2013,
month: 11,
day: 20,
hour: 11,
minute: 30,
second: 0,
isDate: false },
_pendingNormalization: false,
timezone: 'Europe/Copenhagen',
zone:
{ wrappedJSObject: [Circular],
expandedUntilYear: 0,
changes: [],
tzid: 'floating' } }
Can anyone give some pointers as to what I'm doing wrong?
Also: How do I generate startTime and endTime with timezone-definitions? ICAL.Time.fromData({...}, iCalTz)
doesn't add the timezone-definition...
It turns out the parser doesn't hook up the timestamps with TZID:<name>
to the corresponding VTIMEZONE
. If I monkey-patch ex. the startDate
w. startDate.zone = new ICAL.Timezone(comp.getFirstSubcomponent('vtimezone'));
it works fine.
@kewisch you have a moment to look at this?
I suspect it's because I'm parsing the VEVENT and VTIMEZONE separately (so they simply don't know about each other), but I can't find any way to instantiate a whole VCALENDAR in the item model layer (i.e. something like new ICAL.Calendar(ICAL.Parse(DATA)[1]);
)
I'm feeling I'm missing something obvious here, but I might be mistaken...
So in theory ICAL.Time.fromData({...}, iCalTz)
should work, I'm surprised it doesn't. Just setting the TZID Property doesn't set a timezone, because there is no definitive way to figure out what timezone object should be used. In Lightning with libical, we had a timezone service and the wrapper around components kept track of the timezones used with a refcount. I could imagine doing something similar in ical.js, but I think it should be separate to the existing components as far as possible, i.e. on the item model layer like you suggested with an ICAL.Calendar
.
The problem is the multiple sources a timezone definition can come from and where to keep track, paired with components/properties/etc being separate entities that should work on their own. A timezone definition can be made manually using a VTIMEZONE component on the VCALENDAR, but it can also happen implicitly by using a TZID that comes from the olson set.
Note also that the folks at calconnect have come to the conclusion that most clients and servers cope well with missing VTIMEZONEs, as long as the TZID is an olson name.
Hi, the RFC 5545 (p 64) states: "... Multiple "VTIMEZONE" calendar components can exist in an iCalendar object. In this situation, each "VTIMEZONE" MUST represent a unique time zone definition. This is necessary for some classes of events, such as airline flights, that start in one time zone and end in another. ... An individual "VTIMEZONE" calendar component MUST be specified for each unique "TZID" parameter value specified in the iCalendar object. ..." So according to that it can be assumed that all the VTIMEZONE components found in the iCalendar object to process are the ones used by the other non VTIMEZONE components (VEVENT, VTODO, etc). TimezoneService has a zones object and a register method so it could be the right place to store all the VTIMEZONE objects found in the iCalendar object. The moment for such storing action could be while parsing de iCal object so for every VTIMEZONE component found, we can check if the associated TZID is already present on the TimeService zones object literal and in case it isn't then add it. In case that the iCal object doesn't comply with what the RFC states then the zones mapping can still occur at parsing time but this time checking for every TZID property in the corresponding data types that can use it.
Based on my initial solution proposal and also considering what @msiebuhr wrote about VEVENT and VTIMEZONE I came to the conclusion that there's no problem in strict sense. If we want to get sure we have the necessary support for any timezone included in our iCal Object we can just get all the VTIMEZONE components and add them to the TimezoneService zones object if they're not already registered. Here is the link to the gist. Hope it helps. https://gist.github.com/mogarick/9246085 Since I'm not very familiarized with the parsing algorithms I'm not sure if it'd be better(/faster?) to make dynamic timezone autoregister get included at parsing time. What you think @kewisch
Anyone had any further thoughts on this? It's still the case that when you make a toJSDate() call, the TZID value in a VEVENT is ignored. This makes that call currently fairly useless without some hackery.
@kennethmac2000 what did you do to fix?
@kennethmac2000 what was your hackery solution ?
I used this. I found Gmail does not provide a top-level timezone, but Microsoft/Exchange does.
const getDateFromjCal = jCal => name => {
const date = jCal.getFirstSubcomponent('vevent').getFirstPropertyValue(name);
const vtimezone = jCal.getFirstSubcomponent('vtimezone');
if (vtimezone && moment(date.toJSDate()).utcOffset()) {
//in microsoft, need to use timezone component, in gmail, no timezone, just UTC
date.zone = new ICAL.Timezone(vtimezone);
}
return moment
.parseZone(date.toJSDate())
.utcOffset(date.utcOffset() / 60)
.format('YYYY-MM-DDTHH:mm:ssZZ'); //keep with timezone so that email server display it in the same original timezone
};
the name
argument in this case can be 'dtstamp'
, 'dtstart'
, 'dtend'
I've spent to much time looking into why the timestamp was not adressing the timezone correctly. I've arranged @nthgol answer for Luxon as followed
const getDateTimeFromComponent = (
jCal: ICAL.Component,
vevent: ICAL.Component,
propertyName: string): DateTime => {
const date = vevent.getFirstPropertyValue(propertyName);
const vtimezone = jCal.getFirstSubcomponent("vtimezone");
if (vtimezone) {
date.zone = new ICAL.Timezone(vtimezone);
}
return DateTime.fromJSDate(date.toJSDate())
.toUTC(date.utcOffset() / 60)
};
I'm not sure about removing parseZone, let me know if you have an improvement over this.
I've added a few timezone examples to https://github.com/kewisch/ical.js/wiki/Common-Use-Cases that will hopefully help. You need to register your timezones, ideally through the IANA timezone database, otherwise it won't know what Europe/Copenhagen
actually means.
It looks like I didn't migrate timezone file generation to the new build scripts, I'll have to take another look at that soon.