Merge branch 'calendar-timestamp2datetime-refactoring'

This commit is contained in:
Thomas Bruederli 2012-07-11 10:00:52 +02:00
commit 000c0410e2
24 changed files with 1632 additions and 5409 deletions

View file

@ -16,3 +16,8 @@ are used. They are packaged in a slightly modified version with this plugin.
iCalendar parsing is done with the help of the Horde_iCalendar class. A copy
of that class with all its dependencies is part of this package. In order
to update it, execute lib/get_horde_icalendar.sh > lib/Horde_iCalendar.php
IMPORTANT
---------
The calendar module makes heavy use of PHP's DateTime as well as DateInterval
classes. The latter one requires at least PHP 5.3.0 to run.

View file

@ -8,7 +8,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -872,7 +872,7 @@ class calendar extends rcube_plugin
$events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name']);
$count = $errors = 0;
$rangestart = $_REQUEST['_range'] ? strtotime("now -" . intval($_REQUEST['_range']) . " months") : 0;
$rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0;
foreach ($events as $event) {
// TODO: correctly handle recurring events which start before $rangestart
if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart)))
@ -927,6 +927,8 @@ class calendar extends rcube_plugin
if ($calendars[$calid]) {
$calname = $calendars[$calid]['name'] ? $calendars[$calid]['name'] : $calid;
$calname = preg_replace('/[^a-z0-9_.-]/i', '', html_entity_decode($calname)); // to 7bit ascii
if (empty($calname)) $calname = $calid;
$events = $this->driver->load_events($start, $end, null, $calid, 0);
}
else
@ -1107,12 +1109,17 @@ class calendar extends rcube_plugin
}
/**
* Convert the given date string into a GMT-based time stamp
* Shift dates into user's current timezone
*/
function fromGMT($datetime)
private function adjust_timezone($dt)
{
$ts = is_numeric($datetime) ? $datetime : strtotime($datetime);
return $ts + $this->gmt_offset;
if (is_numeric($dt))
$dt = new DateTime('@'.$td);
else if (is_string($dt))
$dt = new DateTime($dt);
$dt->setTimezone($this->timezone);
return $dt;
}
/**
@ -1139,16 +1146,19 @@ class calendar extends rcube_plugin
// compose a human readable strings for alarms_text and recurrence_text
if ($event['alarms'])
$event['alarms_text'] = $this->_alarms_text($event['alarms']);
if ($event['recurrence'])
if ($event['recurrence']) {
$event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
if ($event['recurrence']['UNTIL'])
$event['recurrence']['UNTIL'] = $this->adjust_timezone($event['recurrence']['UNTIL'])->format('c');
}
foreach ((array)$event['attachments'] as $k => $attachment) {
$event['attachments'][$k]['classname'] = rcmail_filetype2classname($attachment['mimetype'], $attachment['name']);
}
return array(
'start' => gmdate('c', $this->fromGMT($event['start'])), // client treats date strings as they were in users's timezone
'end' => gmdate('c', $this->fromGMT($event['end'])), // so shift timestamps to users's timezone and render a date string
'start' => $this->adjust_timezone($event['start'])->format('c'),
'end' => $this->adjust_timezone($event['end'])->format('c'),
'title' => strval($event['title']),
'description' => strval($event['description']),
'location' => strval($event['location']),
@ -1167,8 +1177,8 @@ class calendar extends rcube_plugin
foreach ($alarms as $alarm) {
$out[] = array(
'id' => $alarm['id'],
'start' => gmdate('c', $this->fromGMT($alarm['start'])),
'end' => gmdate('c', $this->fromGMT($alarm['end'])),
'start' => $this->adjust_timezone($alarm['start'])->format('c'),
'end' => $this->adjust_timezone($alarm['end'])->format('c'),
'allDay' => ($event['allday'] == 1)?true:false,
'title' => $alarm['title'],
'location' => $alarm['location'],
@ -1297,7 +1307,7 @@ class calendar extends rcube_plugin
}
$offset = $notify[0] * $mult;
$refdate = $mult > 0 ? $event['end'] : $event['start'];
$notify_at = $refdate + $offset;
$notify_at = $refdate->format('U') + $offset;
}
else { // absolute timestamp
$notify_at = $notify[0];
@ -1319,11 +1329,11 @@ class calendar extends rcube_plugin
$k = strtoupper($k);
switch ($k) {
case 'UNTIL':
$val = gmdate('Ymd\THis', $val);
$val = $val->format('Ymd\THis');
break;
case 'EXDATE':
foreach ((array)$val as $i => $ex)
$val[$i] = gmdate('Ymd\THis', $ex);
$val[$i] = $ex->format('Ymd\THis');
$val = join(',', (array)$val);
break;
}
@ -1432,8 +1442,8 @@ class calendar extends rcube_plugin
$this->driver->new_event(array(
'uid' => $this->generate_uid(),
'start' => $start,
'end' => $start + $duration,
'start' => new DateTime('@'.$start),
'end' => new DateTime('@'.($start + $duration)),
'allday' => $allday,
'title' => rtrim($title),
'free_busy' => $fb == 2 ? 'outofoffice' : ($fb ? 'busy' : 'free'),
@ -1683,6 +1693,13 @@ class calendar extends rcube_plugin
*/
private function prepare_event(&$event, $action)
{
// convert dates into DateTime objects in user's current timezone
$event['start'] = new DateTime($event['start'], $this->timezone);
$event['end'] = new DateTime($event['end'], $this->timezone);
if ($event['recurrence']['UNTIL'])
$event['recurrence']['UNTIL'] = new DateTime($event['recurrence']['UNTIL'], $this->timezone);
$attachments = array();
$eventid = 'cal:'.$event['id'];
if (is_array($_SESSION['event_session']) && $_SESSION['event_session']['id'] == $eventid) {
@ -1785,7 +1802,7 @@ class calendar extends rcube_plugin
public function event_date_text($event, $tzinfo = false)
{
$fromto = '';
$duration = $event['end'] - $event['start'];
$duration = $event['start']->diff($event['end'])->format('s');
$this->date_format_defaults();
$date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']));
@ -1796,7 +1813,7 @@ class calendar extends rcube_plugin
if (($todate = format_date($event['end'], $date_format)) != $fromto)
$fromto .= ' - ' . $todate;
}
else if ($duration < 86400 && gmdate('d', $event['start']) == gmdate('d', $event['end'])) {
else if ($duration < 86400 && $event['start']->format('d') == $event['end']->format('d')) {
$fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
' - ' . format_date($event['end'], $time_format);
}

View file

@ -6,7 +6,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as

View file

@ -186,6 +186,13 @@ function rcube_calendar_ui(settings)
return d;
};
// turn the given date into an ISO 8601 date string understandable by PHPs strtotime()
var date2servertime = function(date)
{
return date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate()
+ 'T'+date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();
}
// convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
var date2unixtime = function(date)
{
@ -545,7 +552,7 @@ function rcube_calendar_ui(settings)
recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change();
interval = $('select.edit-recurrence-interval').val(event.recurrence ? event.recurrence.INTERVAL : 1);
rrtimes = $('#edit-recurrence-repeat-times').val(event.recurrence ? event.recurrence.COUNT : 1);
rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(new Date(event.recurrence.UNTIL*1000), settings['date_format']) : '');
rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate($.fullCalendar.parseISO8601(event.recurrence.UNTIL), settings['date_format']) : '');
$('input.edit-recurrence-until:checked').prop('checked', false);
var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
@ -642,8 +649,8 @@ function rcube_calendar_ui(settings)
// post data to server
var data = {
calendar: event.calendar,
start: date2unixtime(start),
end: date2unixtime(end),
start: date2servertime(start),
end: date2servertime(end),
allday: allday.checked?1:0,
title: title.val(),
description: description.val(),
@ -702,7 +709,7 @@ function rcube_calendar_ui(settings)
if (until == 'count')
data.recurrence.COUNT = rrtimes.val();
else if (until == 'until')
data.recurrence.UNTIL = date2unixtime(parse_datetime(endtime.val(), rrenddate.val()));
data.recurrence.UNTIL = date2servertime(parse_datetime(endtime.val(), rrenddate.val()));
if (freq == 'WEEKLY') {
var byday = [];
@ -2233,9 +2240,9 @@ function rcube_calendar_ui(settings)
// initalize the fullCalendar plugin
var fc = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
right: 'prev,next today',
center: 'title',
right: 'agendaDay,agendaWeek,month,table'
left: 'agendaDay,agendaWeek,month,table'
},
aspectRatio: 1,
date: viewdate.getDate(),
@ -2375,8 +2382,8 @@ function rcube_calendar_ui(settings)
var data = {
id: event.id,
calendar: event.calendar,
start: date2unixtime(event.start),
end: date2unixtime(event.end),
start: date2servertime(event.start),
end: date2servertime(event.end),
allday: allDay?1:0
};
update_event_confirm('move', event, data);
@ -2393,8 +2400,8 @@ function rcube_calendar_ui(settings)
var data = {
id: event.id,
calendar: event.calendar,
start: date2unixtime(event.start),
end: date2unixtime(event.end)
start: date2servertime(event.start),
end: date2servertime(event.end)
};
update_event_confirm('resize', event, data);
},

View file

@ -8,7 +8,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -32,20 +32,20 @@
* 'id' => 'Event ID used for editing',
* 'uid' => 'Unique identifier of this event',
* 'calendar' => 'Calendar identifier to add event to or where the event is stored',
* 'start' => <unixtime>, // Event start date/time as unix timestamp
* 'end' => <unixtime>, // Event end date/time as unix timestamp
* 'start' => DateTime, // Event start date/time as DateTime object
* 'end' => DateTime, // Event end date/time as DateTime object
* 'allday' => true|false, // Boolean flag if this is an all-day event
* 'changed' => <unixtime>, // Last modification date of event
* 'changed' => DateTime, // Last modification date of event
* 'title' => 'Event title/summary',
* 'location' => 'Location string',
* 'description' => 'Event description',
* 'recurrence' => array( // Recurrence definition according to iCalendar (RFC 2445) specification as list of key-value pairs
* 'FREQ' => 'DAILY|WEEKLY|MONTHLY|YEARLY',
* 'INTERVAL' => 1...n,
* 'UNTIL' => <unixtime>,
* 'UNTIL' => DateTime,
* 'COUNT' => 1..n, // number of times
* // + more properties (see http://www.kanzaki.com/docs/ical/recur.html)
* 'EXDATE' => array(), // list of <unixtime>s of exception Dates/Times
* 'EXDATE' => array(), // list of DateTime objects of exception Dates/Times
* ),
* 'recurrence_id' => 'ID of the recurrence group', // usually the ID of the starting event
* 'categories' => 'Event category',
@ -157,8 +157,8 @@ abstract class calendar_driver
*
* @param array Hash array with event properties:
* id: Event identifier
* start: Event start date/time as unix timestamp
* end: Event end date/time as unix timestamp
* start: Event start date/time as DateTime object
* end: Event end date/time as DateTime object
* allday: Boolean flag if this is an all-day event
* @return boolean True on success, False on error
*/
@ -169,8 +169,8 @@ abstract class calendar_driver
*
* @param array Hash array with event properties:
* id: Event identifier
* start: Event start date/time as unix timestamp in user timezone
* end: Event end date/time as unix timestamp in user timezone
* start: Event start date/time as DateTime object with timezone
* end: Event end date/time as DateTime object with timezone
* @return boolean True on success, False on error
*/
abstract function resize_event($event);
@ -230,8 +230,8 @@ abstract class calendar_driver
* @return array A list of alarms, each encoded as hash array:
* id: Event identifier
* uid: Unique identifier of this event
* start: Event start date/time as unix timestamp
* end: Event end date/time as unix timestamp
* start: Event start date/time as DateTime object
* end: Event end date/time as DateTime object
* allday: Boolean flag if this is an all-day event
* title: Event title/summary
* location: Location string

View file

@ -8,7 +8,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -27,6 +27,8 @@
class database_driver extends calendar_driver
{
const DB_DATE_FORMAT = 'Y-m-d H:i:s';
// features this backend supports
public $alarms = true;
public $attendees = true;
@ -40,6 +42,7 @@ class database_driver extends calendar_driver
private $calendars = array();
private $calendar_ids = '';
private $free_busy_map = array('free' => 0, 'busy' => 1, 'out-of-office' => 2, 'outofoffice' => 2, 'tentative' => 3);
private $server_timezone;
private $db_events = 'events';
private $db_calendars = 'calendars';
@ -56,6 +59,7 @@ class database_driver extends calendar_driver
{
$this->cal = $cal;
$this->rc = $cal->rc;
$this->server_timezone = new DateTimeZone(date_default_timezone_get());
// load library classes
require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php');
@ -215,14 +219,14 @@ class database_driver extends calendar_driver
$query = $this->rc->db->query(sprintf(
"INSERT INTO " . $this->db_events . "
(calendar_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, attendees, alarms, notifyat)
VALUES (?, %s, %s, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
VALUES (?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
$this->rc->db->now(),
$this->rc->db->now(),
$this->rc->db->fromunixtime($event['start']),
$this->rc->db->fromunixtime($event['end'])
$this->rc->db->now()
),
$event['calendar'],
strval($event['uid']),
$event['start']->format(self::DB_DATE_FORMAT),
$event['end']->format(self::DB_DATE_FORMAT),
intval($event['all_day']),
$event['_recurrence'],
strval($event['title']),
@ -299,20 +303,23 @@ class database_driver extends calendar_driver
case 'future':
if ($master['id'] != $event['id']) {
// set until-date on master event, then save this instance as new recurring event
$master['recurrence']['UNTIL'] = $event['start'] - 86400;
$master['recurrence']['UNTIL'] = clone $event['start'];
$master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
unset($master['recurrence']['COUNT']);
$update_master = true;
// if recurrence COUNT, update value to the correct number of future occurences
if ($event['recurrence']['COUNT']) {
$fromdate = clone $master['start'];
$fromdate->setTimezone($this->server_timezone);
$sqlresult = $this->rc->db->query(sprintf(
"SELECT event_id FROM " . $this->db_events . "
WHERE calendar_id IN (%s)
AND start >= %s
AND start >= ?
AND recurrence_id=?",
$this->calendar_ids,
$this->rc->db->fromunixtime($event['start'])
$this->calendar_ids
),
$fromdate->format(self::DB_DATE_FORMAT),
$master['id']);
if ($count = $this->rc->db->num_rows($sqlresult))
$event['recurrence']['COUNT'] = $count;
@ -329,20 +336,21 @@ class database_driver extends calendar_driver
$event['recurrence_id'] = 0;
// use start date from master but try to be smart on time or duration changes
$old_start_date = date('Y-m-d', $old['start']);
$old_start_time = date('H:i', $old['start']);
$old_duration = $old['end'] - $old['start'];
$old_start_date = $old['start']->format('Y-m-d');
$old_start_time = $old['start']->format('H:i');
$old_duration = $old['end']->format('U') - $old['start']->format('U');
$new_start_date = date('Y-m-d', $event['start']);
$new_start_time = date('H:i', $event['start']);
$new_duration = $event['end'] - $event['start'];
$new_start_date = $event['start']->format('Y-m-d');
$new_start_time = $event['start']->format('H:i');
$new_duration = $event['end']->format('U') - $event['start']->format('U');
$diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration;
// shifted or resized
if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) {
$event['start'] = $master['start'] + ($event['start'] - $old['start']);
$event['end'] = $event['start'] + $new_duration;
$event['start'] = $master['start']->add($old['start']->diff($event['start']));
$event['end'] = clone $event['start'];
$event['end']->add(new DateInterval('PT'.$new_duration.'S'));
}
break;
}
@ -363,6 +371,12 @@ class database_driver extends calendar_driver
*/
private function _save_preprocess($event)
{
// shift dates to server's timezone
$event['start'] = clone $event['start'];
$event['start']->setTimezone($this->server_timezone);
$event['end'] = clone $event['end'];
$event['end']->setTimezone($this->server_timezone);
// compose vcalendar-style recurrencue rule from structured data
$rrule = $event['recurrence'] ? calendar::to_rrule($event['recurrence']) : '';
$event['_recurrence'] = rtrim($rrule, ';');
@ -396,7 +410,7 @@ class database_driver extends calendar_driver
*/
private function _get_notification($event)
{
if ($event['alarms'] && $event['start'] > time()) {
if ($event['alarms'] && $event['start'] > new DateTime()) {
$alarm = calendar::get_next_alarm($event);
if ($alarm['time'] && $alarm['action'] == 'DISPLAY')
@ -416,9 +430,11 @@ class database_driver extends calendar_driver
{
$event = $this->_save_preprocess($event);
$sql_set = array();
$set_cols = array('all_day', 'recurrence_id', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat');
$set_cols = array('start', 'end', 'all_day', 'recurrence_id', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat');
foreach ($set_cols as $col) {
if (isset($event[$col]))
if (is_object($event[$col]) && is_a($event[$col], 'DateTime'))
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]->format(self::DB_DATE_FORMAT));
else if (isset($event[$col]))
$sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]);
}
@ -430,12 +446,10 @@ class database_driver extends calendar_driver
$query = $this->rc->db->query(sprintf(
"UPDATE " . $this->db_events . "
SET changed=%s, start=%s, end=%s %s
SET changed=%s %s
WHERE event_id=?
AND calendar_id IN (" . $this->calendar_ids . ")",
$this->rc->db->now(),
$this->rc->db->fromunixtime($event['start']),
$this->rc->db->fromunixtime($event['end']),
($sql_set ? ', ' . join(', ', $sql_set) : '')
),
$event['id']
@ -489,21 +503,24 @@ class database_driver extends calendar_driver
require_once($this->cal->home . '/lib/calendar_recurrence.php');
$recurrence = new calendar_recurrence($this->cal, $event);
$duration = $event['end'] - $event['start'];
while ($next_ts = $recurrence->next_start()) {
$notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_ts, 'end' => $next_ts + $duration));
$duration = $event['start']->diff($event['end']);
while ($next_start = $recurrence->next_start()) {
$next_start->setTimezone($this->server_timezone);
$next_end = clone $next_start;
$next_end->add($duration);
$notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_start, 'end' => $next_end));
$query = $this->rc->db->query(sprintf(
"INSERT INTO " . $this->db_events . "
(calendar_id, recurrence_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, notifyat)
SELECT calendar_id, ?, %s, %s, uid, %s, %s, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, ?
SELECT calendar_id, ?, %s, %s, uid, ?, ?, all_day, recurrence, title, description, location, categories, free_busy, priority, sensitivity, alarms, ?
FROM " . $this->db_events . " WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")",
$this->rc->db->now(),
$this->rc->db->now(),
$this->rc->db->fromunixtime($next_ts),
$this->rc->db->fromunixtime($next_ts + $duration)
$this->rc->db->now()
),
$event['id'],
$next_start->format(self::DB_DATE_FORMAT),
$next_end->format(self::DB_DATE_FORMAT),
$notify_at,
$event['id']
);
@ -582,16 +599,20 @@ class database_driver extends calendar_driver
case 'future':
if ($master['id'] != $event['id']) {
// set until-date on master event
$master['recurrence']['UNTIL'] = $event['start'] - 86400;
$master['recurrence']['UNTIL'] = clone $event['start'];
$master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
unset($master['recurrence']['COUNT']);
$update_master = true;
// delete this and all future instances
$fromdate = clone $old['start'];
$fromdate->setTimezone($this->server_timezone);
$query = $this->rc->db->query(
"DELETE FROM " . $this->db_events . "
WHERE calendar_id IN (" . $this->calendar_ids . ")
AND start >= " . $this->rc->db->fromunixtime($old['start']) . "
AND start >= ?
AND recurrence_id=?",
$fromdate->format(self::DB_DATE_FORMAT),
$master['id']
);
break;
@ -704,8 +725,8 @@ class database_driver extends calendar_driver
$free_busy_map = array_flip($this->free_busy_map);
$event['id'] = $event['event_id'];
$event['start'] = strtotime($event['start']);
$event['end'] = strtotime($event['end']);
$event['start'] = new DateTime($event['start']);
$event['end'] = new DateTime($event['end']);
$event['allday'] = intval($event['all_day']);
$event['changed'] = strtotime($event['changed']);
$event['free_busy'] = $free_busy_map[$event['free_busy']];
@ -719,9 +740,9 @@ class database_driver extends calendar_driver
if (is_numeric($rr[2]))
$rr[2] = intval($rr[2]);
else if ($rr[1] == 'UNTIL')
$rr[2] = strtotime($rr[2]);
$rr[2] = date_create($rr[2]);
else if ($rr[1] == 'EXDATE')
$rr[2] = array_map('strtotime', explode(',', $rr[2]));
$rr[2] = array_map('date_create', explode(',', $rr[2]));
$event['recurrence'][$rr[1]] = $rr[2];
}
}
@ -734,7 +755,7 @@ class database_driver extends calendar_driver
$attendees = array();
foreach (explode("\n", $event['attendees']) as $line) {
$att = array();
foreach (rcube_explode_quoted_string(';', $line) as $prop) {
foreach (rcube_utils::explode_quoted_string(';', $line) as $prop) {
list($key, $value) = explode("=", $prop);
$att[strtolower($key)] = stripslashes(trim($value, '""'));
}
@ -793,7 +814,7 @@ class database_driver extends calendar_driver
public function dismiss_alarm($event_id, $snooze = 0)
{
// set new notifyat time or unset if not snoozed
$notify_at = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null;
$notify_at = $snooze > 0 ? date(self::DB_DATE_FORMAT, time() + $snooze) : null;
$query = $this->rc->db->query(sprintf(
"UPDATE " . $this->db_events . "

View file

@ -182,7 +182,9 @@ class kolab_calendar
$this->events[$master_id] = $this->_to_rcube_event($record);
if (($master = $this->events[$master_id]) && $master['recurrence']) {
$this->_get_recurring_events($master, $master['start'], $master['start'] + 86400 * 365 * 10, $id);
$limit = clone $master['start'];
$limit->add(new DateInterval('P10Y'));
$this->_get_recurring_events($record, $master['start'], $limit, $id);
}
}
@ -200,6 +202,10 @@ class kolab_calendar
*/
public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
{
// convert to DateTime for comparisons
$start = new DateTime('@'.$start);
$end = new DateTime('@'.$end);
// query Kolab storage
$query[] = array('dtstart', '<=', $end);
$query[] = array('dtend', '>=', $start);
@ -211,13 +217,11 @@ class kolab_calendar
}
}
$events = array();
foreach ((array)$this->storage->select($query) as $record) {
$event = $this->_to_rcube_event($record);
$this->events[$event['id']] = $event;
}
$events = array();
foreach ($this->events as $id => $event) {
// remember seen categories
if ($event['categories'])
$this->categories[$event['categories']]++;
@ -249,9 +253,8 @@ class kolab_calendar
}
// resolve recurring events
if ($event['recurrence'] && $virtual == 1) {
unset($event['_attendees']);
$events = array_merge($events, $this->_get_recurring_events($event, $start, $end));
if ($record['recurrence'] && $virtual == 1) {
$events = array_merge($events, $this->_get_recurring_events($record, $start, $end));
}
}
@ -374,30 +377,29 @@ class kolab_calendar
public function _get_recurring_events($event, $start, $end, $event_id = null)
{
$recurrence = new kolab_date_recurrence($event);
$events = array();
$duration = $event['end'] - $event['start'];
$i = 0;
while ($rec_start = $recurrence->next_start(true)) {
$rec_end = $rec_start + $duration;
$rec_id = $event['id'] . '-' . ++$i;
$events = array();
while ($next_event = $recurrence->next_instance()) {
$rec_start = $next_event['start']->format('U');
$rec_end = $next_event['end']->format('U');
$rec_id = $event['uid'] . '-' . ++$i;
// add to output if in range
if (($rec_start <= $end && $rec_end >= $start) || ($event_id && $rec_id == $event_id)) {
$rec_event = $event;
if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_id)) {
$rec_event = $this->_to_rcube_event($next_event);
$rec_event['id'] = $rec_id;
$rec_event['recurrence_id'] = $event['id'];
$rec_event['start'] = $rec_start;
$rec_event['end'] = $rec_end;
$rec_event['recurrence_id'] = $event['uid'];
$rec_event['_instance'] = $i;
unset($rec_event['_attendees']);
$events[] = $rec_event;
if ($rec_id == $event_id) {
$this->events[$rec_id] = $rec_event;
break;
}
}
else if ($rec_start > $end) // stop loop if out of range
else if ($next_event['start'] > $end) // stop loop if out of range
break;
}
@ -411,16 +413,18 @@ class kolab_calendar
{
$record['id'] = $record['uid'];
$record['calendar'] = $this->id;
/*
// convert from DateTime to unix timestamp
if (is_a($record['start'], 'DateTime'))
$record['start'] = $record['start']->format('U');
if (is_a($record['end'], 'DateTime'))
$record['end'] = $record['end']->format('U');
*/
// all-day events go from 12:00 - 13:00
if ($record['end'] <= $record['start'] && $record['allday'])
$record['end'] = $record['start'] + 3600;
if ($record['end'] <= $record['start'] && $record['allday']) {
$record['end'] = clone $record['start'];
$record['end']->add(new DateTimeInterval('PT1H'));
}
if (!empty($record['_attachments'])) {
foreach ($record['_attachments'] as $key => $attachment) {

View file

@ -380,7 +380,9 @@ class kolab_driver extends calendar_driver
// removing the first instance => just move to next occurence
if ($master['id'] == $event['id']) {
$recurring = reset($storage->_get_recurring_events($event, $event['start'], $event['end'] + 86400 * 370, $event['id'].'-1'));
$limit = clone $event['end'];
$limit->add(new DateInterval('P370D'));
$recurring = reset($storage->_get_recurring_events($event, $event['start'], $limit, $event['id'].'-1'));
$master['start'] = $recurring['start'];
$master['end'] = $recurring['end'];
if ($master['recurrence']['COUNT'])
@ -397,7 +399,8 @@ class kolab_driver extends calendar_driver
$_SESSION['calendar_restore_event_data'] = $master;
// set until-date on master event
$master['recurrence']['UNTIL'] = $event['start'] - 86400;
$master['recurrence']['UNTIL'] = clone $event['start'];
$master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
unset($master['recurrence']['COUNT']);
$success = $storage->update_event($master);
break;
@ -539,7 +542,8 @@ class kolab_driver extends calendar_driver
case 'future':
if ($master['id'] != $event['id']) {
// set until-date on master event
$master['recurrence']['UNTIL'] = $old['start'] - 86400;
$master['recurrence']['UNTIL'] = clone $old['start'];
$master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
unset($master['recurrence']['COUNT']);
$storage->update_event($master);
@ -555,7 +559,7 @@ class kolab_driver extends calendar_driver
// remove fixed weekday, will be re-set to the new weekday in kolab_calendar::insert_event()
if (strlen($event['recurrence']['BYDAY']) == 2)
unset($event['recurrence']['BYDAY']);
if ($master['recurrence']['BYMONTH'] == gmdate('n', $master['start']))
if ($master['recurrence']['BYMONTH'] == $master['start']->format('n'))
unset($event['recurrence']['BYMONTH']);
$success = $storage->insert_event($event);
@ -567,26 +571,27 @@ class kolab_driver extends calendar_driver
$event['uid'] = $master['uid'];
// use start date from master but try to be smart on time or duration changes
$old_start_date = date('Y-m-d', $old['start']);
$old_start_time = date('H:i', $old['start']);
$old_duration = $old['end'] - $old['start'];
$old_start_date = $old['start']->format('Y-m-d');
$old_start_time = $old['start']->format('H:i');
$old_duration = $old['end']->format('U') - $old['start']->format('U');
$new_start_date = date('Y-m-d', $event['start']);
$new_start_time = date('H:i', $event['start']);
$new_duration = $event['end'] - $event['start'];
$new_start_date = $event['start']->format('Y-m-d');
$new_start_time = $event['start']->format('H:i');
$new_duration = $event['end']->format('U') - $event['start']->format('U');
$diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration;
// shifted or resized
if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) {
$event['start'] = $master['start'] + ($event['start'] - $old['start']);
$event['end'] = $event['start'] + $new_duration;
$event['start'] = $master['start']->add($old['start']->diff($event['start']));
$event['end'] = clone $event['start'];
$event['end']->add(new DateInterval('PT'.$new_duration.'S'));
// remove fixed weekday, will be re-set to the new weekday in kolab_calendar::update_event()
if ($old_start_date != $new_start_date) {
if (strlen($event['recurrence']['BYDAY']) == 2)
unset($event['recurrence']['BYDAY']);
if ($old['recurrence']['BYMONTH'] == gmdate('n', $old['start']))
if ($old['recurrence']['BYMONTH'] == $old['start']->format('n'))
unset($event['recurrence']['BYMONTH']);
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -144,22 +144,23 @@ class calendar_ical
'uid' => $ve->getAttributeDefault('UID'),
'changed' => $ve->getAttributeDefault('DTSTAMP', 0),
'title' => $ve->getAttributeDefault('SUMMARY'),
'start' => $ve->getAttribute('DTSTART'),
'end' => $ve->getAttribute('DTEND'),
'start' => $this->_date2time($ve->getAttribute('DTSTART')),
'end' => $this->_date2time($ve->getAttribute('DTEND')),
// set defaults
'free_busy' => 'busy',
'priority' => 0,
);
// check for all-day dates
if (is_array($event['start'])) {
// create timestamp at 12:00 in user's timezone
$event['start'] = $this->_date2time($event['start']);
if (is_array($ve->getAttribute('DTSTART')))
$event['allday'] = true;
}
if (is_array($event['end'])) {
$event['end'] = $this->_date2time($event['end']) - 23 * 3600;
}
if ($event['allday'])
$event['end']->sub(new DateInterval('PT23H'));
// assign current timezone to event start/end
$event['start']->setTimezone($this->cal->timezone);
$event['end']->setTimezone($this->cal->timezone);
// map other attributes to internal fields
$_attendees = array();
@ -215,7 +216,7 @@ class calendar_ical
$params[$k] = $v;
}
if ($params['UNTIL'])
$params['UNTIL'] = $ve->_parseDateTime($params['UNTIL']);
$params['UNTIL'] = date_create($params['UNTIL']);
if (!$params['INTERVAL'])
$params['INTERVAL'] = 1;
@ -309,11 +310,11 @@ class calendar_ical
private function _date2time($prop)
{
// create timestamp at 12:00 in user's timezone
if (is_array($prop)) {
$date = new DateTime(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->timezone);
console($prop, $date->format('r'));
return $date->getTimestamp();
}
if (is_array($prop))
return date_create(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->timezone);
else if (is_numeric($prop))
return date_create('@'.$prop);
return $prop;
}
@ -353,15 +354,17 @@ class calendar_ical
foreach ($events as $event) {
$vevent = "BEGIN:VEVENT" . self::EOL;
$vevent .= "UID:" . self::escpape($event['uid']) . self::EOL;
$vevent .= "DTSTAMP:" . gmdate('Ymd\THis\Z', $event['changed'] ? $event['changed'] : time()) . self::EOL;
$vevent .= $this->format_datetime("DTSTAMP", $event['changed'] ?: new DateTime(), false, true) . self::EOL;
// correctly set all-day dates
if ($event['allday']) {
$vevent .= "DTSTART;VALUE=DATE:" . gmdate('Ymd', $event['start'] + $this->cal->gmt_offset) . self::EOL;
$vevent .= "DTEND;VALUE=DATE:" . gmdate('Ymd', $event['end'] + $this->cal->gmt_offset + 86400) . self::EOL; // ends the next day
$event['end'] = clone $event['end'];
$event['end']->add(new DateInterval('P1D')); // ends the next day
$vevent .= $this->format_datetime("DTSTART", $event['start'], true) . self::EOL;
$vevent .= $this->format_datetime("DTEND", $event['end'], true) . self::EOL;
}
else {
$vevent .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
$vevent .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
$vevent .= $this->format_datetime("DTSTART", $event['start'], false) . self::EOL;
$vevent .= $this->format_datetime("DTEND", $event['end'], false) . self::EOL;
}
$vevent .= "SUMMARY:" . self::escpape($event['title']) . self::EOL;
$vevent .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL;
@ -424,7 +427,26 @@ class calendar_ical
// fold lines to 75 chars
return rcube_vcard::rfc2425_fold($ical);
}
private function format_datetime($attr, $dt, $dateonly = false, $utc = false)
{
if (is_numeric($dt))
$dt = new DateTime('@'.$dt);
if ($utc)
$dt->setTimezone(new DateTimeZone('UTC'));
if ($dateonly) {
return $attr . ';VALUE=DATE:' . $dt->format('Ymd');
}
else {
// <ATTR>;TZID=Europe/Zurich:20120706T210000
$tz = $dt->getTimezone();
$tzid = $tz && $tz->getName() != 'UTC' ? ';TZID=' . $tz->getName() : '';
return $attr . $tzid . ':' . $dt->format('Ymd\THis' . ($tzid ? '' : 'Z'));
}
}
private function escpape($str)
{
return preg_replace('/(?<!\\\\)([\:\;\,\\n\\r])/', '\\\$1', $str);

View file

@ -9,7 +9,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @package @package_name@
*
* Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -28,9 +28,9 @@ class calendar_recurrence
{
private $cal;
private $event;
private $next;
private $engine;
private $tz_offset = 0;
private $dst_start = false;
private $duration;
private $hour = 0;
/**
@ -42,30 +42,30 @@ class calendar_recurrence
function __construct($cal, $event)
{
$this->cal = $cal;
$this->event = $event;
$this->next = new Horde_Date($event['start'], $cal->timezone->getName());
$this->hour = $this->next->hour;
if (is_object($event['start']) && is_object($event['end']))
$this->duration = $event['start']->diff($event['end']);
// use Horde classes to compute recurring instances
// TODO: replace with something that has less than 6'000 lines of code
require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php');
$this->event = $event;
$this->engine = new Horde_Date_Recurrence($event['start']);
$this->engine->fromRRule20(calendar::to_rrule($event['recurrence']));
if (is_array($event['recurrence']['EXDATE'])) {
foreach ($event['recurrence']['EXDATE'] as $exdate)
$this->engine->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate));
$this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
}
$this->tz_offset = $event['allday'] ? $this->cal->gmt_offset - date('Z') : 0;
$this->next = new Horde_Date($event['start'] + $this->tz_offset); # shift all-day times to server timezone because computation operates in local TZ
$this->dst_start = $this->next->format('I');
$this->hour = $this->next->hour;
}
/**
* Get timestamp of the next occurence of this event
* Get date/time of the next occurence of this event
*
* @return mixed Unix timestamp or False if recurrence ended
* @return mixed DateTime object or False if recurrence ended
*/
public function next_start()
{
@ -75,8 +75,8 @@ class calendar_recurrence
$next->hour = $this->hour; # fix time for all-day events
$next->min = 0;
}
# $dst_diff = ($this->dst_start - $next->format('I')) * 3600; # consider difference in daylight saving between base event and recurring instance
$time = $next->timestamp() - $this->tz_offset;
$time = $next->toDateTime();
$this->next = $next;
}

View file

@ -380,7 +380,7 @@ class calendar_ui
$this->cal->gettext(array('name' => $label, 'vars' => array('min' => $n % 60, 'hrs' => intval($n / 60))))));
}
return html::tag('ul', $attrib, join("\n", $items), html::$common_attrib);
return html::tag('ul', $attrib + array('class' => 'toolbarmenu'), join("\n", $items), html::$common_attrib);
}
/**

View file

@ -29,7 +29,9 @@ body.calendarmain #mainscreen {
}
#datepicker {
margin-top: 12px;
position: absolute;
top: 40px;
left: 0;
width: 100%;
min-height: 190px;
}
@ -89,7 +91,7 @@ body.calendarmain #mainscreen {
position: absolute;
left: 254px;
width: 8px;
top: 37px;
top: 40px;
bottom: 0;
background: url(images/toggle.gif) 0 48% no-repeat transparent;
cursor: pointer;
@ -241,17 +243,18 @@ pre {
#calendartoolbar {
position: absolute;
top: -6px;
right: 0;
left: 0;
height: 40px;
z-index: 200;
}
#calendartoolbar a {
padding-right: 10px;
}
#quicksearchbar {
right: 4px;
body.calendarmain #quicksearchbar {
top: -7px;
right: 2px;
z-index: 200;
}
body.calendarmain #searchmenulink {
@ -870,7 +873,6 @@ span.edit-alarm-set {
}
a.dropdown-link {
color: #CC0000;
font-size: 12px;
text-decoration: none;
}
@ -904,7 +906,6 @@ a.dropdown-link:after {
}
.alarm-item div.alarm-actions a {
color: #CC0000;
margin-right: 0.8em;
text-decoration: none;
}
@ -1019,7 +1020,7 @@ span.spacer {
.fc-content {
position: absolute !important;
top: 38px;
top: 40px;
left: 0;
right: 0;
bottom: 28px;
@ -1210,7 +1211,9 @@ div.fc-event-location {
background: -ms-linear-gradient(left, #e3f2f6 0, #d6eaf3 14px ,#d6eaf3 100%);
background: linear-gradient(left, #e3f2f6 0, #d6eaf3 14px, #d6eaf3 100%);
border: 0;
padding: 7px;
border-bottom: 1px solid #ccc;
height: 18px;
padding: 8px 7px 3px 7px;
}
.calendarmain .fc-view-table tr.fc-event td {

View file

@ -11,10 +11,11 @@
<div id="mainscreen">
<div id="calendarsidebar">
<div id="quicksearchbar">
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
<a id="searchmenulink" class="iconbutton searchoptions" > </a>
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
<div id="calendartoolbar" class="toolbar">
<roundcube:button command="addevent" type="link" class="button addevent disabled" classAct="button addevent" classSel="button addevent pressed" label="calendar.new_event" title="calendar.new_event" />
<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="calendar.print" title="calendar.printtitle" />
<roundcube:button command="export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="calendar.export" title="calendar.exporttitle" />
<roundcube:container name="toolbar" id="calendartoolbar" />
</div>
<div id="datepicker" class="uibox"></div>
@ -30,12 +31,11 @@
</div>
</div>
<div id="calendarsidebartoggle"></div>
<div id="calendartoolbar" class="toolbar">
<roundcube:button command="addevent" type="link" class="button addevent disabled" classAct="button addevent" classSel="button addevent pressed" label="calendar.new_event" title="calendar.new_event" />
<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="calendar.print" title="calendar.printtitle" />
<roundcube:button command="export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="calendar.export" title="calendar.exporttitle" />
<roundcube:container name="toolbar" id="calendartoolbar" />
<div id="quicksearchbar">
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
<a id="searchmenulink" class="iconbutton searchoptions" > </a>
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
</div>
<div id="calendar">

View file

@ -1198,7 +1198,10 @@ class Horde_Date_Recurrence
'%04d%02d%02d');
$this->setRecurEnd(new Horde_Date(array('year' => $year,
'month' => $month,
'mday' => $mday)));
'mday' => $mday,
'hour' => 23,
'min' => 59,
'sec' => 59)));
}
if (isset($rdata['COUNT'])) {
$this->setRecurCount($rdata['COUNT']);

View file

@ -55,7 +55,7 @@ class kolab_date_recurrence
$this->engine->fromRRule20($this->to_rrule($object['recurrence'])); // TODO: get that string directly from libkolabxml
foreach ((array)$object['recurrence']['EXDATE'] as $exdate)
$this->engine->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate));
$this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
$now = new DateTime('now', kolab_format::$timezone);
$this->tz_offset = $object['allday'] ? $now->getOffset() - date('Z') : 0;
@ -124,7 +124,7 @@ class kolab_date_recurrence
public function end($limit = 'now +1 year')
{
if ($this->object['recurrence']['UNTIL'])
return $this->object['recurrence']['UNTIL'];
return $this->object['recurrence']['UNTIL']->format('U');
$limit_time = strtotime($limit);
while ($next_start = $this->next_start(true)) {
@ -154,11 +154,11 @@ class kolab_date_recurrence
$k = strtoupper($k);
switch ($k) {
case 'UNTIL':
$val = gmdate('Ymd\THis', $val);
$val = $val->format('Ymd\THis');
break;
case 'EXDATE':
foreach ((array)$val as $i => $ex)
$val[$i] = gmdate('Ymd\THis', $ex);
$val[$i] = $ex->format('Ymd\THis');
$val = join(',', (array)$val);
break;
}

View file

@ -136,6 +136,7 @@ class kolab_format_contact extends kolab_format
$this->obj->setUid($object['uid']);
$object['changed'] = new DateTime('now', self::$timezone);
$this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC')));
// do the hard work of setting object values
$nc = new NameComponents;

View file

@ -50,6 +50,7 @@ class kolab_format_distributionlist extends kolab_format
$this->obj->setUid($object['uid']);
$object['changed'] = new DateTime('now', self::$timezone);
$this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC')));
$this->obj->setName($object['name']);

View file

@ -229,7 +229,7 @@ class kolab_format_event extends kolab_format_xcal
if ($recurrence['range-type'] == 'number')
$rrule['COUNT'] = intval($recurrence['range']);
else if ($recurrence['range-type'] == 'date')
$rrule['UNTIL'] = $recurrence['range'];
$rrule['UNTIL'] = date_create('@'.$recurrence['range']);
if ($recurrence['day']) {
$byday = array();
@ -251,7 +251,7 @@ class kolab_format_event extends kolab_format_xcal
if ($recurrence['exclusion']) {
foreach ((array)$recurrence['exclusion'] as $excl)
$rrule['EXDATE'][] = strtotime($excl . date(' H:i:s', $rec['start-date'])); // use time of event start
$rrule['EXDATE'][] = date_create($excl . date(' H:i:s', $rec['start-date'])); // use time of event start
}
}

View file

@ -50,6 +50,7 @@ class kolab_format_journal extends kolab_format
$this->obj->setUid($object['uid']);
$object['changed'] = new DateTime('now', self::$timezone);
$this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC')));
// TODO: set object propeties

View file

@ -50,6 +50,7 @@ class kolab_format_note extends kolab_format
$this->obj->setUid($object['uid']);
$object['changed'] = new DateTime('now', self::$timezone);
$this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC')));
// TODO: set object propeties

View file

@ -144,7 +144,7 @@ abstract class kolab_format_xcal extends kolab_format
}
else if ($until = self::php_datetime($rr->end())) {
$until->setTime($object['start']->format('G'), $object['start']->format('i'), 0);
$object['recurrence']['UNTIL'] = $until->format('U');
$object['recurrence']['UNTIL'] = $until;
}
if (($byday = $rr->byday()) && $byday->size()) {
@ -169,7 +169,7 @@ abstract class kolab_format_xcal extends kolab_format
if ($exceptions = $this->obj->exceptionDates()) {
for ($i=0; $i < $exceptions->size(); $i++) {
if ($exdate = self::php_datetime($exceptions->get($i)))
$object['recurrence']['EXDATE'][] = $exdate->format('U');
$object['recurrence']['EXDATE'][] = $exdate;
}
}
}
@ -223,6 +223,7 @@ abstract class kolab_format_xcal extends kolab_format
$this->obj->setUid($object['uid']);
$object['changed'] = new DateTime('now', self::$timezone);
$this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC')));
// increment sequence
$this->obj->setSequence($this->obj->sequence()+1);

View file

@ -515,7 +515,7 @@ class kolab_storage_cache
// extend date range for recurring events
if ($object['recurrence']) {
$recurrence = new kolab_date_recurrence($object);
$sql_data['dtend'] = date('Y-m-d H:i:s', $recurrence->end() ?: strtotime('now +1 year'));
$sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +1 year'));
}
}
else if ($objtype == 'task') {