vobject icon indicating copy to clipboard operation
vobject copied to clipboard

Detecting timezone based on VTIMEZONE values

Open evert opened this issue 9 years ago • 20 comments

Since forever we've been mapping VTIMEZONE objects to PHP timezones based on the TZID, X-LIC-* properties and others.

This system works pretty well, but every now and then we run across a VTIMEZONE object that we don't support. For those we should really fall back on parsing VTIMEZONE objects and figuring which PHP timezone they map to based on their values.

See #247

There is a $15 open bounty on this issue. Add to the bounty at Bountysource.

evert avatar Jul 23 '15 17:07 evert

+1 from me

gjn avatar Jul 23 '15 18:07 gjn

Related to #44. Also some previous research here: https://gist.github.com/SimonSimCity/9950755

DominikTo avatar Jul 29 '15 14:07 DominikTo

:+1:

j03k64 avatar Apr 11 '16 14:04 j03k64

We are experiencing a related issue with iCalendar attachments received from Microsoft Exchange Server 2010, where the sender is in the UK (Europe/London timezone). For some reason, Exchange sometimes uses a TZID of 'GMT' for these users, but include STANDARD and DAYLIGHT DST rules and specifies Europe/London times.

I'm mentioning this because even though the VTIMEZONE's TZID is supported, it is incorrect. In this case at least, a PHP timezone found by examining the DST rules should be used in preference to that indicated by TZID, and not as a fallback.

This example illustrates the problem:

// Event with 'GMT' timezone as received from Microsoft Exchange Server 2010
$eventGmt = <<<EVENT
BEGIN:VCALENDAR
METHOD:REQUEST
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
BEGIN:VTIMEZONE
TZID:GMT Standard Time
BEGIN:STANDARD
DTSTART:16010101T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0000
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T010000
TZOFFSETFROM:+0000
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
ORGANIZER;CN=Mr Person;SENT-BY="MAILTO:[email protected]":MAILTO
 :[email protected]
ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;CN=Mrs Colleague
 s:MAILTO:[email protected]
DESCRIPTION;LANGUAGE=en-US:\n\n
SUMMARY;LANGUAGE=en-US:GMT Meeting
DTSTART;TZID=GMT Standard Time:20160807T080000
DTEND;TZID=GMT Standard Time:20160807T090000
UID:04000B16B00B1E50008200E00074C5B7101A82E0080005007B0F59FC1D101000000000000000
 0100000002DA3DB6FC7061B4680DAEF792F5776F8
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20160608T150813Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION;LANGUAGE=en-US:Meeting Venue
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-OWNERAPPTID:1896376288
X-MICROSOFT-CDO-BUSYSTATUS:TENTATIVE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DISALLOW-COUNTER:FALSE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:REMINDER
TRIGGER;RELATED=START:-PT15M
END:VALARM
END:VEVENT
END:VCALENDAR
EVENT;

// Read the 'GMT' event
$cal = Sabre\VObject\Reader::read($eventGmt);
$vevent = current($cal->select('VEVENT'));

echo $vevent->DTSTART->getDateTime()->format('r').' '.$vevent->DTSTART->getDateTime()->getTimezone()->getName(). "<br>\n";
echo $vevent->DTEND->getDateTime()->format('r').' '.$vevent->DTEND->getDateTime()->getTimezone()->getName()."<br><br>\n\n";

// Read the same event but with Europe/London TZID
$cal = Sabre\VObject\Reader::read(str_replace('GMT Standard Time', 'Europe/London', $eventGmt));
$vevent = current($cal->select('VEVENT'));

echo $vevent->DTSTART->getDateTime()->format('r').' '.$vevent->DTSTART->getDateTime()->getTimezone()->getName()."<br>\n";
echo $vevent->DTEND->getDateTime()->format('r').' '.$vevent->DTEND->getDateTime()->getTimezone()->getName()."\n";

lightstream avatar Aug 11 '16 13:08 lightstream

Makes sense! Having this in place would be hugely beneficial

evert avatar Aug 19 '16 01:08 evert

@evert how would you suggest that we handle the getDateTime method call on something like DTSTART since we need a Timezone for that no? Seems like we would have to map the custom timezone definition to something that the php "knows" since it doesn't look like you can set transitions or anything on the DateTimeZone standard php class without creating a custom class to extend it.

j03k64 avatar Aug 22 '16 20:08 j03k64

The goal of this enhancement would be to read the information from VTIMEZONE, and based on the rules specified in DAYLIGHT and STANDARD, we would try to find the closest match to an olson identifer.

So I would still want to map to PHP's DateTimeZone, but instead of making an educated guess based on some properties, getTransitions and match it up with the data contained in VTIMEZONE.

But even for those cases I would only want to this if we don't have an exact match for TZID, because this system would definitely be a lot slower, and possibly less accurate. If an iCalendar object states TZID=Europe/Amsterdam, I still want to assume that that's correct and ignore the information from VTIMEZONE.

evert avatar Aug 22 '16 21:08 evert

@evert if that is the case, wouldn't it be just much easier (although more hacky) to add a mapping table from the Exchange Identifiers (since Exchange seems to be the biggest violator) to the php ones as a simple workaround with a lot less work for now?

j03k64 avatar Aug 23 '16 00:08 j03k64

Exchange timezones are mapped here: https://github.com/fruux/sabre-vobject/blob/master/lib/timezonedata/exchangezones.php

DominikTo avatar Aug 23 '16 07:08 DominikTo

@DominikTo @evert then I guess it is missing a few since I received one not mentioned there (I guess that was the point of this issue ... to automatically figure that out without needing to maintain the mapping). Want me to create a pull request for these additional mapping names or wait until it is solved via parsing the VTIMEZONE records?

BEGIN:VCALENDAR
METHOD:PUBLISH
PRODID:Microsoft Exchange Server 2010
VERSION:2.0
X-WR-CALNAME:Calendar
BEGIN:VTIMEZONE
TZID:(UTC-05:00) Eastern Time (US & Canada)
BEGIN:STANDARD
DTSTART:16010101T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010101T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
DESCRIPTION:3 Homes for Builder requiring Photography & Walk through Video
 of each home.
UID:040000003232E00074C5B7876A82E00800000000FD0CCC288FF8D101000000000000000
 01000000006F59CB9230499438F3AB6F603BBFA69
SUMMARY:PHOTO & VIDEOS for Joe Smith
DTSTART;TZID="(UTC-05:00) Eastern Time (US & Canada)":20160823T100000
DTEND;TZID="(UTC-05:00) Eastern Time (US & Canada)":20160823T170000
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20160822T184151Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION:
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:BUSY
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT
END:VCALENDAR

j03k64 avatar Aug 23 '16 07:08 j03k64

The goal of this issue is to solve situations where the 'automatic mapping' fails. However, until then, I would be happy to receive patches or bug reports for obvious missing entries.

However, I'm doubting a little bit if the sample file you submitted here is correct. Exchange-generated iCalendar files usually have the X-MICROSOFT-CDO-TZID property set, which appears to be stripped from your sample file.

So my answer to situations like this is always, please find out what software generates this file, so we can determine how widespread the issue is and based on that figure out if we need a workaround. Is this a reproducible case?

The reason this is important for me, is because I want to make absolutely sure we're not adding a workaround that we'll have to maintain for years to come for some persons script that does a poor job handing iCalendar. At the very least I must know what produces this.

Anyway, this is off-topic for this github issue, so please open a new one if you want to continue the conversation.

evert avatar Aug 23 '16 16:08 evert

@evert The links that you and @jpirkey have posted above seem to indicate that the case I reported above (invites from Exchange in Europe/London timezone with TZID:GMT Standard Time) should already be correctly handled, or am I misunderstanding?

Microsoft's Exchange Identifiers map "GMT Standard Time" to "Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London", and the Sabre mappings map "Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London" to "Europe/Lisbon" ie the correct timezone.

If this is correct, I can do some further investigations into why it's not working as expected and open a new issue if appropriate.

Thank you.

lightstream avatar Sep 02 '16 12:09 lightstream

@lightstream I believe it works fine (at least for the scenarios I have encountered). The issue I reported was due to the timezone offset being prepended from some exchange installs: https://github.com/fruux/sabre-vobject/issues/344 .. so now we strip that off and try to find the identifier without it.

j03k64 avatar Sep 02 '16 13:09 j03k64

@jpirkey I read your linked issue. The problem we have is that invites received from Exchange with TZID:GMT Standard Time are parsed into DateTime objects with timezone of GMT, when they should have a timezone of Europe/London.

What I wasn't sure of was whether Sabre is applying the Exchange mappings from the link you posted, or just the ones in the file linked by @evert if that makes sense!

lightstream avatar Sep 02 '16 13:09 lightstream

Just the ones in the the mapping files. Not only the file @evert linked to, but also the ones in the other mapping files under lib/timezonedata .. they are merged together to create one mapping in TimeZoneUtil::loadTzMaps

j03k64 avatar Sep 02 '16 14:09 j03k64

Thanks for that @jpirkey, gives me a starting point to investigate my issue further.

lightstream avatar Sep 02 '16 15:09 lightstream

@lightstream it looks like the mapping is there in the windowszones.php file:

'GMT Standard Time'               => 'Europe/London',

j03k64 avatar Sep 02 '16 16:09 j03k64

Thank you very much for all your help on this @jpirkey, it does look like it should be handling these timezones correctly.

I think my issue isn't relevant here after all. I will report it to the Kolab Calendar project (a roundcube plugin that uses Sabre).

Apologies to all for polluting this thread...

lightstream avatar Sep 05 '16 09:09 lightstream

For future commenters, if you have a timezone related problem, and you're not sure how it pertains to this ticket, open a new one!

evert avatar Sep 08 '16 19:09 evert

2.2.54 [RFC2445] Section 4.6.5, Time Zone Component V0058: The specification describes the VTIMEZONE component. Microsoft® Office Outlook® 2007, Microsoft® Outlook® 2010, Microsoft® Outlook® 15 Technical Preview On import, Outlook attempts to approximate VTIMEZONE components to a VTIMEZONE with one annually-recurring standard-to-Daylight Saving transition date, and one annually-recurring Daylight Saving-to-standard transition date. The approximation process is specified in [MS-OXCICAL] section 2.1.3.1.1.19. Only the following properties are used to approximate a VTIMEZONE, all other properties are ignored: TZID (in VTIMEZONE components), DTSTART (in DAYLIGHT or STANDARD components), RRULE (in DAYLIGHT or STANDARD components), TZOFFSETFROM (in DAYLIGHT or STANDARD components), and TZOFFSETTO (in DAYLIGHT or STANDARD components). If a time zone cannot be approximated or parsed, it is ignored.

I think this problem not just to add mapping, the reason is the Microsoft handle the timezone different others. Above section contents on the pdf of [MS-STANOICAL].

cisiqo avatar Dec 08 '16 05:12 cisiqo