Hi,
Kolab seems to have trouble working with unknown non olson timezones in appointments. During import of such appointments either by the import function or by accepting an invitation via email with non olson timezones in any dates, these dates/time are simply imported as a numeric value in the local time zone without any conversion. These happens for instance during import from WebEX invitations.
Example:
pykolab-0.8.7-2.5.el7.kolab_16.noarch php-sabre-vobject-3.5.3-4.1.el7.kolab_16.noarch
ICS File:
BEGIN:VCALENDAR PRODID:-//Microsoft Corporation//Outlook 10.0 MIMEDIR//EN VERSION:2.0 METHOD:REQUEST BEGIN:VTIMEZONE TZID:Eastern Time BEGIN:STANDARD DTSTART:20161101T020000 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:Standard Time END:STANDARD BEGIN:DAYLIGHT DTSTART:20160301T020000 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:Daylight Savings Time END:DAYLIGHT END:VTIMEZONE BEGIN:VEVENT ATTENDEE;CN="";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:jdoe@example.org ORGANIZER;CN="name via Cisco WebEx":MAILTO:webex@example.org DTSTART;TZID="Eastern Time":20180430T100000 DTEND;TZID="Eastern Time":20180430T110000 LOCATION:WebEX Meeting TRANSP:OPAQUE SEQUENCE:1524696863 UID:baed324f-61b2-4388-b85c-f8bd07b48712 DTSTAMP:20180430T140000Z DESCRIPTION:JOIN WEBEX MEETING SUMMARY:Test WebEX Meeting PRIORITY:5 CLASS:PUBLIC END:VEVENT END:VCALENDAR
This meeting is scheduled at 10 am local timezone (Europe/Berlin in my test case). It should be scheduled at 7pm local time.
Looking into the code it seems the error happens down in the sabre-io vobject part. There seems to be no handling for VTIMEZONE objects while converting dates.
I have attached a patch for VTimeZone.php and DateTime.php to detect such errors and do a VTIMEZONE based conversion of all dates to the local timezone.
--- BUILD/sabre-vobject-3.5.3/lib/Component/VTimeZone.php 2016-10-07 05:20:40.000000000 +0200 +++ sabre-vobject-3.5.3/lib/Component/VTimeZone.php 2018-04-27 15:04:06.088342389 +0200 @@ -31,6 +31,77 @@ } /** + * Returns the nearest match in a timezone rrule + * + * @param \DateTime $dtstart + * @param \Sabre\VObject\Property\ICalendar\Recur $rrule + * @param \DateTime $TimeToMatch + * @return \Sabre\VObject\Component\VEvent + */ + private function getNearestMatch( $dtstart, $rrule, $TimeToMatch) { + $ve = new VEvent($this->root, "STANDARD"); + $ve->__set("DTSTART",$dtstart); + $ve->__set("RRULE",$rrule); + + $it = new \Sabre\VObject\Recur\EventIterator($ve, null, $TimeToMatch->getTimezone()); + $tzdiff = null; + $return = null; + + // find the last reoccurance before the given date + while ( $it->valid() ) { + $diff = $TimeToMatch->diff($it->getDtStart()); + + if ( !isset($tzdiff) or ($diff->invert >0) ) { + $tzdiff = $diff; + $return = $it->getEventObject(); + } else { + break; + } + + $it->next(); + } + + return $return; + } + + /** + * Returns the UTC offset for a given time (time is threaded as given in this VTIMEZONE) + * This is calculated by searching for the nearest starting point of an time switching + * event in the VTIMEZONE. + * If for any reason the timezone could not be found the returned value will be 0 + * + * @param \DateTime $dt + * @return \int + */ + public function getUTCOffset(\DateTime $dt) { + + $tz_offset = null; + $std_event = null; + + foreach ( $this->STANDARD as $tz_standard) { + $tmp_event = $this->getNearestMatch( $tz_standard->DTSTART->getDateTime(), $tz_standard->RRULE, $dt); + if (( !isset($std_event) ) or ( $dt->diff($std_event->DTSTART->getDateTime())->days > $dt->diff($tmp_event->DTSTART->getDateTime())->days)) { + $std_event = $tmp_event; + $tz_offset = (string) $tz_standard->TZOFFSETTO; + } + } + + foreach ( $this->DAYLIGHT as $tz_daylight) { + $tmp_event = $this->getNearestMatch( $tz_daylight->DTSTART->getDateTime(), $tz_daylight->RRULE, $dt); + if ( $dt->diff($std_event->DTSTART->getDateTime())->days > $dt->diff($tmp_event->DTSTART->getDateTime())->days) { + $std_event = $tmp_event; + $tz_offset = (string) $tz_daylight->TZOFFSETTO; + } + } + + if (preg_match('/(\-|\+|)([0-9][0-9])([0-9][0-9])/', $tz_offset, $matches)) + return (60 * (int) $matches[3] + 3600 * (int) $matches[2]) * (int) ( $matches[1]."1" ); + else + return 0; + } + + + /** * A simple list of validation rules. * * This is simply a list of properties, and how many times they either --- BUILD/sabre-vobject-3.5.3/lib/Property/ICalendar/DateTime.php 2016-10-07 05:20:40.000000000 +0200 +++ sabre-vobject-3.5.3/lib/Property/ICalendar/DateTime.php 2018-04-27 15:04:06.085342378 +0200 @@ -155,6 +155,12 @@ * property or floating time, we will use the DateTimeZone argument to * figure out the exact date. * + * TODO: + * If the timezone is not known to php, we need to convert the object + * time into an olson timezone time. This will only be possible if the + * object has a VTIMEZONE definition or the given object timezone is + * mappable to an olson timezone. + * * @param DateTimeZone $timeZone * @return \DateTime[] */ @@ -162,19 +168,36 @@ // Does the property have a TZID? $tzid = $this['TZID']; - + $need_to_convert_to_local_timezone = false; + if ($tzid) { - $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root); + try { + $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root, true); + } catch(\InvalidArgumentException $e) { + // TimeZoneMapping failed utterly ... this means work + + $need_to_convert_to_local_timezone = true; + $timeZone = new DateTimeZone( date_default_timezone_get() ); + } + } $dts = array(); foreach($this->getParts() as $part) { - $dts[] = DateTimeParser::parse($part, $timeZone); + $new_dts_value = DateTimeParser::parse($part, $timeZone); + + // Do the actual converting from object timezone to local timezone + if ( $need_to_convert_to_local_timezone ) + foreach ( $this->root->VTIMEZONE as $vtimezone) + if ( strcmp( $tzid, (string) $vtimezone->TZID) == 0 ) + $new_dts_value->modify(($new_dts_value->getOffset() - $vtimezone->getUTCOffset($new_dts_value))." seconds"); + + $dts[] = $new_dts_value; } return $dts; } - + /** * Sets the property as a DateTime object. *
Kind Regards,
JuWo