Branch 'roundcubemail-plugins-kolab-0.7' - 2 commits - plugins/calendar

Thomas Brüderli bruederli at kolabsys.com
Tue Oct 30 12:01:39 CET 2012


 plugins/calendar/calendar.php                     |   79 ++++++++++++++------
 plugins/calendar/calendar_ui.js                   |   30 +++++--
 plugins/calendar/drivers/calendar_driver.php      |    6 -
 plugins/calendar/drivers/kolab/kolab_calendar.php |   85 +++++++++++++---------
 plugins/calendar/drivers/kolab/kolab_driver.php   |   28 ++++---
 plugins/calendar/lib/calendar_ical.php            |   70 +++++++++++++-----
 plugins/calendar/lib/calendar_recurrence.php      |   23 +++--
 plugins/calendar/lib/calendar_ui.php              |    6 +
 8 files changed, 217 insertions(+), 110 deletions(-)

New commits:
commit e60cc461ee0c83117b88b09a9c7483712715d378
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Oct 30 12:00:36 2012 +0100

    More timestamp -> DateTime adaption and fixes

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index c9a61cd..812dcf4 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1075,6 +1075,8 @@ class calendar extends rcube_plugin
       $event['alarms_text'] = $this->_alarms_text($event['alarms']);
     if ($event['recurrence'])
       $event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
+    if ($event['recurrence']['UNTIL'])
+      $event['recurrence']['UNTIL'] = $this->toUserDateTime($event['recurrence']['UNTIL'])->format('c');
 
     return array(
       '_id'   => $event['calendar'] . ':' . $event['id'],  // unique identifier for fullcalendar
@@ -1322,8 +1324,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'),
@@ -1672,7 +1674,9 @@ class calendar extends rcube_plugin
   public function event_date_text($event, $tzinfo = false)
   {
     $fromto = '';
-    $duration = $event['end'] - $event['start'];
+    $event['start'] = $this->toUserDateTime($event['start']);
+    $event['end'] = $this->toUserDateTime($event['end']);
+    $duration = $event['end']->format('U') - $event['start']->format('U');
     
     $this->date_format_defaults();
     $date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']));
@@ -1706,7 +1710,7 @@ class calendar extends rcube_plugin
    */
   public static function format_date($date, $format=null)
   {
-    return format_date(is_a($date, 'DateTime') ? $date->format('c') : $date, $format);
+    return format_date(is_a($date, 'DateTime') ? $date->format('c') : $date, $format, false);
   }
 
   /**
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 75acc8f..c325c0a 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -557,7 +557,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,true), settings['date_format']) : '');
         $('input.edit-recurrence-until:checked').prop('checked', false);
       
         var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 4fe64d1..558b777 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -452,7 +452,8 @@ class kolab_calendar
     $duration = $event['end']->format('U') - $event['start']->format('U');
     $i = 0;
     while ($rec_start = $recurrence->next_start()) {
-      $rec_end = new DateTime('@' . ($rec_start->format('U') + $duration));
+      $rec_end = clone $rec_start;
+      $rec_end->modify('+'.$duration.' seconds');
       $rec_id = $event['id'] . '-' . ++$i;
 
       // add to output if in range
@@ -485,22 +486,21 @@ class kolab_calendar
     $start_time = date('H:i:s', $rec['start-date']);
     $allday = $rec['_is_all_day'] || ($start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date']));
     if ($allday) {  // in Roundcube all-day events only go from 12:00 to 13:00
-      $rec['start-date'] += 12 * 3600;
       $dtstart = new DateTime('@'.$rec['start-date']);
-      $dtstart->setTime(12, 0, 0);
+      $dtstart->modify('+12 hours');
       $dtstart->setTimezone($this->cal->user_timezone);
+      $dtstart->setTime(12, 0, 0);
 
-      $rec['end-date'] -= 11 * 3600;
       $dtend = new DateTime('@'.$rec['end-date']);
-      $dtend->setTime(13, 0, 0);
       $dtend->setTimezone($this->cal->user_timezone);
-
-      $rec['start-date'] = $dtstart;
-      $rec['end-date'] = $dtend;
+      $dtend->modify('-11 hours');
+      $dtend->setTime(13, 0, 0);
 
       // sanity check
-      if ($rec['end-date'] <= $rec['start-date'])
-        $dtend = new DateTime('@' . ($dtstart->format('U') + 90000));
+      if ($dtend <= $dtstart) {
+        $dtend = clone $dtstart;
+        $dtend->modify('+25 hours');
+      }
     }
     else {
       $dtstart = new DateTime('@'.$rec['start-date']);
@@ -531,8 +531,10 @@ class kolab_calendar
       
       if ($recurrence['range-type'] == 'number')
         $rrule['COUNT'] = intval($recurrence['range']);
-      else if ($recurrence['range-type'] == 'date')
+      else if ($recurrence['range-type'] == 'date') {
         $rrule['UNTIL'] = new DateTime('@'.$recurrence['range']);
+        $rrule['UNTIL']->setTimezone($this->cal->user_timezone);
+      }
       
       if ($recurrence['day']) {
         $byday = array();
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index de9f99e..77b12cf 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -659,7 +659,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']->modify('-1 day');
           unset($master['recurrence']['COUNT']);
           $storage->update_event($master);
           
@@ -675,7 +676,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);
@@ -687,26 +688,29 @@ 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;
+          $offset = $event['start']->format('U') - $old['start']->format('U');
+          $event['start'] = clone $master['start'];
+          $event['start']->modify(($offset > 0 ? '+' : '') . $offset . ' seconds');
+          $event['end'] = clone $event['start'];
+          $event['end']->modify('+'.$new_duration.' seconds');
           
           // 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']);
           }
         }
diff --git a/plugins/calendar/lib/calendar_recurrence.php b/plugins/calendar/lib/calendar_recurrence.php
index 78ba7ef..0a3c296 100644
--- a/plugins/calendar/lib/calendar_recurrence.php
+++ b/plugins/calendar/lib/calendar_recurrence.php
@@ -53,6 +53,12 @@ class calendar_recurrence
     // TODO: replace with something that has less than 6'000 lines of code
     require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php');
 
+    // shift until date by one day in order to trick the Horde_Date_Recurrence computation
+    if ($event['recurrence']['UNTIL']) {
+      $event['recurrence']['UNTIL'] = clone $event['recurrence']['UNTIL'];
+      $event['recurrence']['UNTIL']->modify('+1 day');
+    }
+
     $this->event = $event;
     $this->engine = new Horde_Date_Recurrence($dtstart->format('U'));
     $this->engine->fromRRule20(calendar::to_rrule($event['recurrence']));


commit 5c36cf87f39dde5d1867a9e8e0b7b4ab2dbbf239
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 29 23:16:02 2012 +0100

    Use DateTime objects instead of unix timestamps. Fixes various timezone and DST issues (including OTRS #1000068 and #1000069)

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index b3f0437..c9a61cd 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -45,6 +45,7 @@ class calendar extends rcube_plugin
   public $urlbase;
   public $timezone;
   public $gmt_offset;
+  public $user_timezone;
 
   public $ical;
   public $ui;
@@ -859,7 +860,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)))
@@ -1030,12 +1031,22 @@ class calendar extends rcube_plugin
   }
 
   /**
-   * Convert the given date string into a GMT-based time stamp
+   * Convert the given date value into a DateTime object in user's timezone
    */
-  function fromGMT($datetime)
+  function toUserDateTime($datetime)
   {
-    $ts = is_numeric($datetime) ? $datetime : strtotime($datetime);
-    return $ts + $this->gmt_offset;
+    if (is_a($datetime, 'DateTime')) {
+      $datetime->setTimezone($this->user_timezone);
+      return $datetime;
+    }
+    try {
+        $dt = new DateTime(is_numeric($datetime) ? '@'.$datetime : $datetime, new DateTimeZone('UTC'));
+        $dt->setTimezone($this->user_timezone);
+    }
+    catch (Exception $e) {
+        $dt = new DateTime('1970-01-01 00:00:00', $this->user_timezone);
+    }
+    return $dt;
   }
 
   /**
@@ -1067,8 +1078,8 @@ class calendar extends rcube_plugin
 
     return array(
       '_id'   => $event['calendar'] . ':' . $event['id'],  // unique identifier for fullcalendar
-      '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->toUserDateTime($event['start'])->format('c'), // client treats date strings as they were in users's timezone
+      'end'   => $this->toUserDateTime($event['end'])->format('c'),   // so shift timestamps to users's timezone and render a date string
       'description' => strval($event['description']),
       'location'    => strval($event['location']),
       'className'   => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'fc-event-cat-' . asciiwords(strtolower($event['categories']), true),
@@ -1086,8 +1097,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->toUserDateTime($alarm['start'])->format('c'),
+        'end'      => $this->toUserDateTime($alarm['end'])->format('c'),
         'allDay'   => ($alarm['allday'] == 1)?true:false,
         'title'    => $alarm['title'],
         'location' => $alarm['location'],
@@ -1116,7 +1127,7 @@ class calendar extends rcube_plugin
     }
     
     if (preg_match('/@(\d+)/', $trigger, $m)) {
-      $text .= ' ' . $this->gettext(array('name' => 'alarmat', 'vars' => array('datetime' => format_date($m[1]))));
+      $text .= ' ' . $this->gettext(array('name' => 'alarmat', 'vars' => array('datetime' => self::format_date($m[1]))));
     }
     else if ($val = self::parse_alaram_value($trigger)) {
       $text .= ' ' . intval($val[0]) . ' ' . $this->gettext('trigger' . $val[1]);
@@ -1156,7 +1167,7 @@ class calendar extends rcube_plugin
     if ($rrule['COUNT'])
       $until =  $this->gettext(array('name' => 'forntimes', 'vars' => array('nr' => $rrule['COUNT'])));
     else if ($rrule['UNTIL'])
-      $until = $this->gettext('recurrencend') . ' ' . format_date($rrule['UNTIL'], self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])));
+      $until = $this->gettext('recurrencend') . ' ' . self::format_date($rrule['UNTIL'], self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])));
     else
       $until = $this->gettext('forever');
     
@@ -1198,11 +1209,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(',', $val);
           break;
       }
@@ -1298,7 +1309,7 @@ class calendar extends rcube_plugin
         $start -= 3600;
       
       if ($allday) {
-        $start = strtotime(date('Y-m-d 00:00:00', $start));
+        $start = date_create(date('Y-m-d 00:00:00', $start));
         $duration = 86399;
       }
       
@@ -1552,6 +1563,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->user_timezone);
+    $event['end'] = new DateTime($event['end'], $this->user_timezone);
+
+    if ($event['recurrence']['UNTIL'])
+      $event['recurrence']['UNTIL'] = new DateTime($event['recurrence']['UNTIL'], $this->user_timezone);
+
     $attachments = array();
     $eventid = 'cal:'.$event['id'];
     if (is_array($_SESSION['event_session']) && $_SESSION['event_session']['id'] == $eventid) {
@@ -1661,17 +1679,17 @@ class calendar extends rcube_plugin
     $time_format = self::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']));
     
     if ($event['allday']) {
-      $fromto = format_date($event['start'], $date_format);
-      if (($todate = format_date($event['end'], $date_format)) != $fromto)
+      $fromto = self::format_date($event['start'], $date_format);
+      if (($todate = self::format_date($event['end'], $date_format)) != $fromto)
         $fromto .= ' - ' . $todate;
     }
-    else if ($duration < 86400 && gmdate('d', $event['start']) == gmdate('d', $event['end'])) {
-      $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
-        ' - ' . format_date($event['end'], $time_format);
+    else if ($duration < 86400 && $event['start']->format('d') == $event['end']->format('d')) {
+      $fromto = self::format_date($event['start'], $date_format) . ' ' . self::format_date($event['start'], $time_format) .
+        ' - ' . self::format_date($event['end'], $time_format);
     }
     else {
-      $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
-        ' - ' . format_date($event['end'], $date_format) . ' ' . format_date($event['end'], $time_format);
+      $fromto = self::format_date($event['start'], $date_format) . ' ' . self::format_date($event['start'], $time_format) .
+        ' - ' . self::format_date($event['end'], $date_format) . ' ' . self::format_date($event['end'], $time_format);
     }
     
     // add timezone information
@@ -1683,6 +1701,15 @@ class calendar extends rcube_plugin
   }
 
   /**
+   * Wrapper for Roundcube's internal format_date() function
+   * accepting DateTime objects as argument
+   */
+  public static function format_date($date, $format=null)
+  {
+    return format_date(is_a($date, 'DateTime') ? $date->format('c') : $date, $format);
+  }
+
+  /**
    * Echo simple free/busy status text for the given user and time range
    */
   public function freebusy_status()
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index a8eb2b8..75acc8f 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -208,7 +208,19 @@ function rcube_calendar_ui(settings)
         date.setTime((ts + 3600) * 1000);
       return date;
     };
-    
+
+    // turn the given date into an ISO 8601 date string understandable by PHPs strtotime()
+    var date2servertime = function(date)
+    {
+      return date.getFullYear()+'-'+zeropad(date.getMonth()+1)+'-'+zeropad(date.getDate())
+          + 'T'+zeropad(date.getHours())+':'+zeropad(date.getMinutes())+':'+zeropad(date.getSeconds());
+    }
+
+    var zeropad = function(num)
+    {
+        return (num < 10 ? '0' : '') + num;
+    }
+
     // determine whether the given date is on a weekend
     var is_weekend = function(date)
     {
@@ -642,8 +654,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 +714,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 = [];
@@ -2355,8 +2367,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);
@@ -2373,8 +2385,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);
       },
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index babecd7..1729277 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -37,8 +37,8 @@
  *            '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 unix timestamp
+ *           'end' => DateTime,  // Event end date/time as unix timestamp
  *        'allday' => true|false,  // Boolean flag if this is an all-day event
  *       'changed' => <unixtime>, // Last modification date of event
  *         'title' => 'Event title/summary',
@@ -47,7 +47,7 @@
  *    '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
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 0475ba1..4fe64d1 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -218,6 +218,10 @@ class kolab_calendar
    */
   public function list_events($start, $end, $search = null, $virtual = 1)
   {
+    // convert to DateTime for comparisons
+    $start = new DateTime('@'.$start);
+    $end = new DateTime('@'.$end);
+    
     $this->_fetch_events();
     
     if (!empty($search))
@@ -443,14 +447,14 @@ class kolab_calendar
     require_once($this->cal->home . '/lib/calendar_recurrence.php');
     
     $recurrence = new calendar_recurrence($this->cal, $event);
-    
+
     $events = array();
-    $duration = $event['end'] - $event['start'];
+    $duration = $event['end']->format('U') - $event['start']->format('U');
     $i = 0;
     while ($rec_start = $recurrence->next_start()) {
-      $rec_end = $rec_start + $duration;
+      $rec_end = new DateTime('@' . ($rec_start->format('U') + $duration));
       $rec_id = $event['id'] . '-' . ++$i;
-      
+
       // add to output if in range
       if (($rec_start <= $end && $rec_end >= $start) || ($event_id && $rec_id == $event_id)) {
         $rec_event = $event;
@@ -482,14 +486,27 @@ class kolab_calendar
     $allday = $rec['_is_all_day'] || ($start_time == '00:00:00' && $start_time == date('H:i:s', $rec['end-date']));
     if ($allday) {  // in Roundcube all-day events only go from 12:00 to 13:00
       $rec['start-date'] += 12 * 3600;
-      $rec['end-date']   -= 11 * 3600;
-      $rec['end-date']   -= $this->cal->gmt_offset - date('Z', $rec['end-date']);    // shift times from server's timezone to user's timezone
-      $rec['start-date'] -= $this->cal->gmt_offset - date('Z', $rec['start-date']);  // because generated with mktime() in Horde_Kolab_Format_Date::decodeDate()
+      $dtstart = new DateTime('@'.$rec['start-date']);
+      $dtstart->setTime(12, 0, 0);
+      $dtstart->setTimezone($this->cal->user_timezone);
+
+      $rec['end-date'] -= 11 * 3600;
+      $dtend = new DateTime('@'.$rec['end-date']);
+      $dtend->setTime(13, 0, 0);
+      $dtend->setTimezone($this->cal->user_timezone);
+
+      $rec['start-date'] = $dtstart;
+      $rec['end-date'] = $dtend;
+
       // sanity check
       if ($rec['end-date'] <= $rec['start-date'])
-        $rec['end-date'] += 86400;
+        $dtend = new DateTime('@' . ($dtstart->format('U') + 90000));
     }
-    
+    else {
+      $dtstart = new DateTime('@'.$rec['start-date']);
+      $dtend = new DateTime('@'.$rec['end-date']);
+    }
+
     // convert alarm time into internal format
     if ($rec['alarm']) {
       $alarm_value = $rec['alarm'];
@@ -515,7 +532,7 @@ class kolab_calendar
       if ($recurrence['range-type'] == 'number')
         $rrule['COUNT'] = intval($recurrence['range']);
       else if ($recurrence['range-type'] == 'date')
-        $rrule['UNTIL'] = $recurrence['range'];
+        $rrule['UNTIL'] = new DateTime('@'.$recurrence['range']);
       
       if ($recurrence['day']) {
         $byday = array();
@@ -537,7 +554,7 @@ class kolab_calendar
       
       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'][] = new DateTime($excl . $dtstart->format(' H:i:s'), $this->cal->user_timezone);  // use time of event start
       }
     }
 
@@ -586,8 +603,8 @@ class kolab_calendar
       'title' => $rec['summary'],
       'location' => $rec['location'],
       'description' => $rec['body'],
-      'start' => $rec['start-date'],
-      'end' => $rec['end-date'],
+      'start' => $dtstart,
+      'end' => $dtend,
       'allday' => $allday,
       'recurrence' => $rrule,
       'alarms' => $alarm_value . $alarm_unit,
@@ -611,7 +628,6 @@ class kolab_calendar
   private function _from_rcube_event($event)
   {
     $priority_map = $this->priority_map;
-    $tz_offset = $this->cal->gmt_offset;
 
     $object = array(
     // kolab         => roundcube
@@ -620,8 +636,8 @@ class kolab_calendar
       'location'     => $event['location'],
       'body'         => $event['description'],
       'categories'   => $event['categories'],
-      'start-date'   => $event['start'],
-      'end-date'     => $event['end'],
+      'start-date'   => $event['start']->format('U'),
+      'end-date'     => $event['end']->format('U'),
       'sensitivity'  =>$this->sensitivity_map[$event['sensitivity']],
       'show-time-as' => $event['free_busy'],
       'priority'     => $event['priority'],
@@ -655,7 +671,7 @@ class kolab_calendar
       //Range Type
       if ($ra['UNTIL']) {
         $object['recurrence']['range-type'] = 'date';
-        $object['recurrence']['range'] = $ra['UNTIL'];
+        $object['recurrence']['range'] = $ra['UNTIL']->format('U');
       }
       if ($ra['COUNT']) {
         $object['recurrence']['range-type'] = 'number';
@@ -670,7 +686,7 @@ class kolab_calendar
         }
         else {
           // use weekday of start date if empty
-          $object['recurrence']['day'][] = strtolower(gmdate('l', $event['start'] + $tz_offset));
+          $object['recurrence']['day'][] = strtolower($event['start']->format('l'));
         }
       }
       
@@ -683,7 +699,7 @@ class kolab_calendar
           $object['recurrence']['type']  = 'weekday';
         }
         else {
-          $object['recurrence']['daynumber'] = date('j', $event['start']);
+          $object['recurrence']['daynumber'] = $event['start']->format('j');
           $object['recurrence']['cycle'] = 'monthly';
           $object['recurrence']['type']  = 'daynumber';
         }
@@ -692,7 +708,7 @@ class kolab_calendar
       //yearly
       if ($ra['FREQ'] == 'YEARLY') {
         if (!$ra['BYMONTH'])
-          $ra['BYMONTH'] = gmdate('n', $event['start'] + $tz_offset);
+          $ra['BYMONTH'] = $event['start']->format('n');
         
         $object['recurrence']['cycle'] = 'yearly';
         $object['recurrence']['month'] = $this->month_map[intval($ra['BYMONTH'])];
@@ -704,29 +720,34 @@ class kolab_calendar
         }
         else {
           $object['recurrence']['type'] = 'monthday';
-          $object['recurrence']['daynumber'] = gmdate('j', $event['start'] + $tz_offset);
+          $object['recurrence']['daynumber'] = $event['start']->format('j');
         }
       }
       
       //exclusions
       foreach ((array)$ra['EXDATE'] as $excl) {
-        $object['recurrence']['exclusion'][] = gmdate('Y-m-d', $excl + $tz_offset);
+        $object['recurrence']['exclusion'][] = $excl->format('Y-m-d');
       }
     }
     
     // whole day event
     if ($event['allday']) {
-      $object['end-date'] += 12 * 3600;  // end is at 13:00 => jump to the next day
-      $object['end-date'] += $tz_offset - date('Z');   // shift 00 times from user's timezone to server's timezone 
-      $object['start-date'] += $tz_offset - date('Z');  // because Horde_Kolab_Format_Date::encodeDate() uses strftime()
+      // shift times from user's timezone to server's timezone
+      // because Horde_Kolab_Format_Date::encodeDate() uses strftime()
+      $server_tz = new DateTimeZone(date_default_timezone_get());
+      $event['start']->setTimezone($server_tz);
+      $event['end']->setTimezone($server_tz);
+      
+      $event['start']->setTime(0,0,0);
+      $event['end']->setTime(0,0,0);
       
       // create timestamps at exactly 00:00. This is also needed for proper re-interpretation in _to_rcube_event() after updating an event
-      $object['start-date'] = mktime(0,0,0, date('n', $object['start-date']), date('j', $object['start-date']), date('Y', $object['start-date']));
-      $object['end-date']   = mktime(0,0,0, date('n', $object['end-date']),   date('j', $object['end-date']),   date('Y', $object['end-date']));
+      $object['start-date'] = mktime(0,0,0, $event['start']->format('n'), $event['start']->format('j'), $event['start']->format('Y'));
+      $object['end-date']   = mktime(0,0,0, $event['end']->format('n'),   $event['end']->format('j'),   $event['end']->format('Y')) + 86400;
       
       // sanity check: end date is same or smaller than start
       if (date('Y-m-d', $object['end-date']) <= date('Y-m-d', $object['start-date']))
-        $object['end-date'] = mktime(13,0,0, date('n', $object['start-date']), date('j', $object['start-date']), date('Y', $object['start-date'])) + 86400;
+        $object['end-date'] = mktime(13,0,0, $event['start']->format('n'), $event['start']->format('j'), $event['start']->format('Y')) + 86400;
       
       $object['_is_all_day'] = 1;
     }
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 83fd69d..de9f99e 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -794,7 +794,7 @@ class kolab_driver extends calendar_driver
 
       foreach ($calendar->list_events($time, $time + 86400 * 365) as $e) {
         // add to list if alarm is set
-        if ($e['_alarm'] && ($notifyat = $e['start'] - $e['_alarm'] * 60) <= $time) {
+        if ($e['_alarm'] && ($notifyat = $e['start']->format('U') - $e['_alarm'] * 60) <= $time) {
           $id = $e['id'];
           $events[$id] = $e;
           $events[$id]['notifyat'] = $notifyat;
diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php
index d2ec2c9..b9b9889 100644
--- a/plugins/calendar/lib/calendar_ical.php
+++ b/plugins/calendar/lib/calendar_ical.php
@@ -153,8 +153,8 @@ 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,
@@ -162,15 +162,16 @@ class calendar_ical
     );
     
     // 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'] = new DateTime('@'.($event['end']->format('U') - 23*3600));
+    
+    // assign current timezone to event start/end
+    $event['start']->setTimezone($this->cal->user_timezone);
+    $event['end']->setTimezone($this->cal->user_timezone);
+    
     // map other attributes to internal fields
     $_attendees = array();
     foreach ($ve->getAllAttributes() as $attr) {
@@ -225,9 +226,15 @@ 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;
+          if ($params['EXDATE']) {
+            $exdates = array();
+            foreach (explode(',', $params['EXDATE']) as $excl)
+              $exdates[] = new DateTime($params['EXDATE'], $this->cal->user_timezone);
+            $params['EXDATE'] = $exdates;
+          }
           
           $event['recurrence'] = $params;
           break;
@@ -319,10 +326,14 @@ class calendar_ical
   private function _date2time($prop)
   {
     // create timestamp at 12:00 in user's timezone
-    return is_array($prop) ? strtotime(sprintf('%04d%02d%02dT120000%s', $prop['year'], $prop['month'], $prop['mday'], $this->timezone)) : $prop;
+    if (is_array($prop))
+      return date_create(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->user_timezone);
+    else if (is_numeric($prop))
+      return date_create('@'.$prop);
+    
+    return $prop;
   }
 
-
   /**
    * Free resources by clearing member vars
    */
@@ -358,15 +369,16 @@ 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'] = new DateTime('@'.($event['end']->format('U') + 86400));  // 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;
@@ -429,7 +441,27 @@ 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();
+      $tzname = $tz ? $tz->getName() : 'UTC';
+      $tzid = $tzname != 'UTC' && $tzname != '+00:00' ? ';TZID=' . $tzname : '';
+      return $attr . $tzid . ':' . $dt->format('Ymd\THis' . ($tzid ? '' : '\Z'));
+    }
+  }
+
   private function escpape($str)
   {
     return preg_replace('/(?<!\\\\)([\:\;\,\\n\\r])/', '\\\$1', $str);
diff --git a/plugins/calendar/lib/calendar_recurrence.php b/plugins/calendar/lib/calendar_recurrence.php
index fac87fb..78ba7ef 100644
--- a/plugins/calendar/lib/calendar_recurrence.php
+++ b/plugins/calendar/lib/calendar_recurrence.php
@@ -35,7 +35,6 @@ class calendar_recurrence
   private $event;
   private $engine;
   private $tz_offset = 0;
-  private $dst_start = false;
   private $hour = 0;
 
   /**
@@ -47,44 +46,42 @@ class calendar_recurrence
   function __construct($cal, $event)
   {
     $this->cal = $cal;
+    $dtstart = clone $event['start'];
+    $dtstart->setTimezone($cal->user_timezone);
 
     // 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 = new Horde_Date_Recurrence($dtstart->format('U'));
     $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->next = new Horde_Date($dtstart->format('U'));
     $this->hour = $this->next->hour;
   }
 
   /**
    * Get timestamp of the next occurence of this event
    *
-   * @return mixed Unix timestamp or False if recurrence ended
+   * @return mixed DateTime or False if recurrence ended
    */
   public function next_start()
   {
     $time = false;
     if ($this->next && ($next = $this->engine->nextActiveRecurrence(array('year' => $this->next->year, 'month' => $this->next->month, 'mday' => $this->next->mday + 1, 'hour' => $this->next->hour, 'min' => $this->next->min, 'sec' => $this->next->sec)))) {
-      # consider difference in daylight saving between base event and recurring instance
-      $dst_diff = ($this->dst_start - $next->format('I')) * 3600;
       # fix time for all-day events
       if ($this->event['allday']) {
         $next->hour = $this->hour;
         $next->min = 0;
-        $dst_diff = 0;
       }
-      $time = $next->timestamp() - $this->tz_offset - $dst_diff;
+      $time = new DateTime($next->iso8601DateTime(), $this->cal->user_timezone);
       $this->next = $next;
     }
 
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 477d1e3..f381e48 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -337,9 +337,11 @@ class calendar_ui
     $input_time = new html_inputfield(array('name' => 'alarmtime[]', 'class' => 'edit-alarm-time', 'size' => 6));
     
     $select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
-    foreach (array('-M','-H','-D','+M','+H','+D','@') as $trigger)
+    foreach (array('-M','-H','-D','+M','+H','+D') as $trigger)
       $select_offset->add($this->cal->gettext('trigger' . $trigger), $trigger);
-     
+    if (!is_a($this->cal->driver, 'kolab_driver'))
+      $select_offset->add($this->cal->gettext('trigger@'), '@');
+    
     // pre-set with default values from user settings
     $preset = calendar::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
     $hidden = array('style' => 'display:none');





More information about the commits mailing list