Fix handling EXDATEs in different timezone than the event timezone (Bifrost#T154553)

This commit is contained in:
Aleksander Machniak 2018-12-13 12:36:20 +00:00
parent 779af09a68
commit 83ac298cd7
4 changed files with 59 additions and 29 deletions

View file

@ -325,10 +325,13 @@ class kolab_calendar extends kolab_storage_folder_api
// skip the first instance of a recurring event if listed in exdate
if ($virtual && !empty($event['recurrence']['EXDATE'])) {
$event_date = $event['start']->format('Ymd');
$exdates = (array)$event['recurrence']['EXDATE'];
$event_tz = $event['start']->getTimezone();
foreach ($exdates as $exdate) {
if ($exdate->format('Ymd') == $event_date) {
foreach ((array) $event['recurrence']['EXDATE'] as $exdate) {
$ex = clone $exdate;
$ex->setTimezone($event_tz);
if ($ex->format('Ymd') == $event_date) {
$add = false;
break;
}

View file

@ -1072,19 +1072,18 @@ class libvcalendar implements Iterator
// add EXDATEs each one per line (for Thunderbird Lightning)
if (is_array($exdates)) {
foreach ($exdates as $ex) {
if ($ex instanceof \DateTime) {
$exd = clone $event['start'];
$exd->setDate($ex->format('Y'), $ex->format('n'), $ex->format('j'));
$exd->setTimeZone(new \DateTimeZone('UTC'));
$ve->add($this->datetime_prop($cal, 'EXDATE', $exd, true));
foreach ($exdates as $exdate) {
if ($exdate instanceof DateTime) {
$ve->add($this->datetime_prop($cal, 'EXDATE', $exdate));
}
}
}
// add RDATEs
if (!empty($rdates)) {
foreach ((array)$rdates as $rdate) {
$ve->add($this->datetime_prop($cal, 'RDATE', $rdate));
if (is_array($rdates)) {
foreach ($rdates as $rdate) {
if ($ex instanceof DateTime) {
$ve->add($this->datetime_prop($cal, 'RDATE', $rdate));
}
}
}
}

View file

@ -203,17 +203,20 @@ abstract class kolab_format
* @param mixed Date/Time value either as unix timestamp, date string or PHP DateTime object
* @param DateTimeZone The timezone the date/time is in. Use global default if Null, local time if False
* @param boolean True of the given date has no time component
* @return object The libkolabxml date/time object
* @param DateTimeZone The timezone to convert the date to before converting to cDateTime
*
* @return cDateTime The libkolabxml date/time object
*/
public static function get_datetime($datetime, $tz = null, $dateonly = false)
public static function get_datetime($datetime, $tz = null, $dateonly = false, $dest_tz = null)
{
// use timezone information from datetime of global setting
// use timezone information from datetime or global setting
if (!$tz && $tz !== false) {
if ($datetime instanceof DateTime)
$tz = $datetime->getTimezone();
if (!$tz)
$tz = self::$timezone;
}
$result = new cDateTime();
try {
@ -225,14 +228,26 @@ abstract class kolab_format
else if (is_string($datetime) && strlen($datetime)) {
$datetime = $tz ? new DateTime($datetime, $tz) : new DateTime($datetime);
}
else if ($datetime instanceof DateTime) {
$datetime = clone $datetime;
}
}
catch (Exception $e) {}
if ($datetime instanceof DateTime) {
if ($dest_tz instanceof DateTimeZone && $dest_tz !== $datetime->getTimezone()) {
$datetime->setTimezone($dest_tz);
$tz = $dest_tz;
}
$result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j'));
if (!$dateonly)
$result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s'));
if ($dateonly) {
// Dates should be always in local time only
return $result;
}
$result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s'));
// libkolabxml throws errors on some deprecated timezone names
$utc_aliases = array('UTC', 'GMT', '+00:00', 'Z', 'Etc/GMT', 'Etc/UTC');
@ -254,16 +269,19 @@ abstract class kolab_format
/**
* Convert the given cDateTime into a PHP DateTime object
*
* @param object cDateTime The libkolabxml datetime object
* @return object DateTime PHP datetime instance
* @param cDateTime The libkolabxml datetime object
* @param DateTimeZone The timezone to convert the date to
*
* @return DateTime PHP datetime instance
*/
public static function php_datetime($cdt)
public static function php_datetime($cdt, $dest_tz = null)
{
if (!is_object($cdt) || !$cdt->isValid())
if (!is_object($cdt) || !$cdt->isValid()) {
return null;
}
$d = new DateTime;
$d->setTimezone(self::$timezone);
$d->setTimezone($dest_tz ?: self::$timezone);
try {
if ($tzs = $cdt->timezone()) {

View file

@ -174,6 +174,10 @@ abstract class kolab_format_xcal extends kolab_format
}
}
if ($object['start'] instanceof DateTime) {
$start_tz = $object['start']->getTimezone();
}
// read recurrence rule
if (($rr = $this->obj->recurrenceRule()) && $rr->isValid()) {
$rrule_type_map = array_flip($this->rrule_type_map);
@ -185,7 +189,7 @@ abstract class kolab_format_xcal extends kolab_format
if (($count = $rr->count()) && $count > 0) {
$object['recurrence']['COUNT'] = $count;
}
else if ($until = self::php_datetime($rr->end())) {
else if ($until = self::php_datetime($rr->end(), $start_tz)) {
$refdate = $this->get_reference_date();
if ($refdate && $refdate instanceof DateTime && !$refdate->_dateonly) {
$until->setTime($refdate->format('G'), $refdate->format('i'), 0);
@ -214,16 +218,18 @@ abstract class kolab_format_xcal extends kolab_format
if ($exdates = $this->obj->exceptionDates()) {
for ($i=0; $i < $exdates->size(); $i++) {
if ($exdate = self::php_datetime($exdates->get($i)))
if ($exdate = self::php_datetime($exdates->get($i), $start_tz)) {
$object['recurrence']['EXDATE'][] = $exdate;
}
}
}
}
if ($rdates = $this->obj->recurrenceDates()) {
for ($i=0; $i < $rdates->size(); $i++) {
if ($rdate = self::php_datetime($rdates->get($i)))
if ($rdate = self::php_datetime($rdates->get($i), $start_tz)) {
$object['recurrence']['RDATE'][] = $rdate;
}
}
}
@ -415,6 +421,10 @@ abstract class kolab_format_xcal extends kolab_format
$this->obj->setOrganizer($organizer);
}
if ($object['start'] instanceof DateTime) {
$start_tz = $object['start']->getTimezone();
}
// save recurrence rule
$rr = new RecurrenceRule;
$rr->setFrequency(RecurrenceRule::FreqNone);
@ -470,13 +480,13 @@ abstract class kolab_format_xcal extends kolab_format
if ($object['recurrence']['COUNT'])
$rr->setCount(intval($object['recurrence']['COUNT']));
else if ($object['recurrence']['UNTIL'])
$rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true));
$rr->setEnd(self::get_datetime($object['recurrence']['UNTIL'], null, true, $start_tz));
if ($rr->isValid()) {
// add exception dates (only if recurrence rule is valid)
$exdates = new vectordatetime;
foreach ((array)$object['recurrence']['EXDATE'] as $exdate)
$exdates->push(self::get_datetime($exdate, null, true));
$exdates->push(self::get_datetime($exdate, null, true, $start_tz));
$this->obj->setExceptionDates($exdates);
}
else {
@ -494,7 +504,7 @@ abstract class kolab_format_xcal extends kolab_format
if (!empty($object['recurrence']['RDATE'])) {
$rdates = new vectordatetime;
foreach ((array)$object['recurrence']['RDATE'] as $rdate)
$rdates->push(self::get_datetime($rdate, null, true));
$rdates->push(self::get_datetime($rdate, null, true, $start_tz));
$this->obj->setRecurrenceDates($rdates);
}
@ -760,7 +770,7 @@ abstract class kolab_format_xcal extends kolab_format
$error_logged = true;
rcube::raise_error(array(
'code' => 900,
'message' => "required kolabcalendaring module not found"
'message' => "Required kolabcalendaring module not found"
), true);
}