Branch 'roundcubemail-plugins-kolab-format2-horde5' - plugins/calendar plugins/libcalendaring

Thomas Brüderli bruederli at kolabsys.com
Wed Mar 6 10:58:41 CET 2013


 plugins/calendar/calendar.php            |   45 +++++++++++++-
 plugins/calendar/calendar_ui.js          |   94 ++++++++++++++++++++-----------
 plugins/libcalendaring/libcalendaring.js |    6 -
 3 files changed, 107 insertions(+), 38 deletions(-)

New commits:
commit 3475aa416fe0e90acbe8181ca8f5e462d50dffe2
Author: Thomas Bruederli <thomas at roundcube.net>
Date:   Wed Mar 6 10:57:56 2013 +0100

    Make free/busy finder DST aware (#1676)

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index f777cb3..7259c99 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1365,6 +1365,16 @@ class calendar extends rcube_plugin
     $start = get_input_value('start', RCUBE_INPUT_GPC);
     $end = get_input_value('end', RCUBE_INPUT_GPC);
     
+    // convert dates into unix timestamps
+    if (!empty($start) && !is_numeric($start)) {
+      $dts = new DateTime($start, $this->timezone);
+      $start = $dts->format('U');
+    }
+    if (!empty($end) && !is_numeric($end)) {
+      $dte = new DateTime($end, $this->timezone);
+      $end = $dte->format('U');
+    }
+    
     if (!$start) $start = time();
     if (!$end) $end = $start + 3600;
     
@@ -1402,11 +1412,27 @@ class calendar extends rcube_plugin
     $start = get_input_value('start', RCUBE_INPUT_GPC);
     $end = get_input_value('end', RCUBE_INPUT_GPC);
     $interval = intval(get_input_value('interval', RCUBE_INPUT_GPC));
-    
+    $strformat = $interval > 60 ? 'Ymd' : 'YmdHis';
+
+    // convert dates into unix timestamps
+    if (!empty($start) && !is_numeric($start)) {
+      $dts = new DateTime($start, $this->timezone);
+      $start = $dts->format('U');
+    }
+    if (!empty($end) && !is_numeric($end)) {
+      $dte = new DateTime($end, $this->timezone);
+      $end = $dte->format('U');
+    }
+
     if (!$start) $start = time();
     if (!$end)   $end = $start + 86400 * 30;
     if (!$interval) $interval = 60;  // 1 hour
     
+    if (!$dte) {
+      $dts = new DateTime('@'.$start);
+      $dts->setTimezone($this->timezone);
+    }
+    
     $fblist = $this->driver->get_freebusy_list($email, $start, $end);
     $slots = array();
     
@@ -1414,7 +1440,9 @@ class calendar extends rcube_plugin
     for ($s = 0, $t = $start; $t <= $end; $s++) {
       $status = self::FREEBUSY_UNKNOWN;
       $t_end = $t + $interval * 60;
-        
+      $dt = new DateTime('@'.$t);
+      $dt->setTimezone($this->timezone);
+
       // determine attendee's status
       if (is_array($fblist)) {
         $status = self::FREEBUSY_FREE;
@@ -1429,13 +1457,24 @@ class calendar extends rcube_plugin
       }
       
       $slots[$s] = $status;
+      $times[$s] = intval($dt->format($strformat));
       $t = $t_end;
     }
     
+    $dte = new DateTime('@'.$t_end);
+    $dte->setTimezone($this->timezone);
+    
     // let this information be cached for 5min
     send_future_expire_header(300);
     
-    echo json_encode(array('email' => $email, 'start' => intval($start), 'end' => intval($t_end), 'interval' => $interval, 'slots' => $slots));
+    echo json_encode(array(
+      'email' => $email,
+      'start' => $dts->format('c'),
+      'end'   => $dte->format('c'),
+      'interval' => $interval,
+      'slots' => $slots,
+      'times' => $times,
+    ));
     exit;
   }
   
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 1efc160..5342884 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -74,6 +74,7 @@ function rcube_calendar_ui(settings)
     var parse_datetime = this.parse_datetime;
     var date2unixtime = this.date2unixtime;
     var fromunixtime = this.fromunixtime;
+    var parseISO8601 = this.parseISO8601;
     var init_alarms_edit = this.init_alarms_edit;
 
 
@@ -122,6 +123,15 @@ function rcube_calendar_ui(settings)
       return d;
     };
 
+    // fix date if jumped over a DST change
+    var fix_date = function(date)
+    {
+      if (date.getHours() == 23)
+        date.setTime(date.getTime() + HOUR_MS);
+      else if (date.getHours() > 0)
+        date.setHours(0);
+    };
+
     // turn the given date into an ISO 8601 date string understandable by PHPs strtotime()
     var date2servertime = function(date)
     {
@@ -129,6 +139,11 @@ function rcube_calendar_ui(settings)
           + 'T'+zeropad(date.getHours())+':'+zeropad(date.getMinutes())+':'+zeropad(date.getSeconds());
     }
 
+    var date2timestring = function(date, dateonly)
+    {
+      return date2servertime(date).replace(/[^0-9]/g, '').substr(0, (dateonly ? 8 : 14));
+    }
+
     var zeropad = function(num)
     {
         return (num < 10 ? '0' : '') + num;
@@ -463,7 +478,7 @@ function rcube_calendar_ui(settings)
         recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change();
         interval = $('#eventedit 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($.fullCalendar.parseISO8601(event.recurrence.UNTIL), settings['date_format']) : '');
+        rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(parseISO8601(event.recurrence.UNTIL), settings['date_format']) : '');
         $('#eventedit input.edit-recurrence-until:checked').prop('checked', false);
       
         var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
@@ -876,10 +891,13 @@ function rcube_calendar_ui(settings)
     {
       if (delta) {
         freebusy_ui.start.setTime(freebusy_ui.start.getTime() + DAY_MS * delta);
+        fix_date(freebusy_ui.start);
+        
         // skip weekends if in workinhoursonly-mode
         if (Math.abs(delta) == 1 && freebusy_ui.workinhoursonly) {
           while (is_weekend(freebusy_ui.start))
             freebusy_ui.start.setTime(freebusy_ui.start.getTime() + DAY_MS * delta);
+          fix_date(freebusy_ui.start);
         }
         
         freebusy_ui.end = new Date(freebusy_ui.start.getTime() + DAY_MS * freebusy_ui.numdays);
@@ -948,7 +966,7 @@ function rcube_calendar_ui(settings)
       
       // if we have loaded free-busy data, show it
       if (!freebusy_ui.loading) {
-        if (date2unixtime(freebusy_ui.start) < freebusy_data.start || date2unixtime(freebusy_ui.end) > freebusy_data.end || freebusy_ui.interval != freebusy_data.interval) {
+        if (freebusy_ui.start < freebusy_data.start || freebusy_ui.end > freebusy_data.end || freebusy_ui.interval != freebusy_data.interval) {
           load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
         }
         else {
@@ -1052,12 +1070,13 @@ function rcube_calendar_ui(settings)
     // fetch free-busy information for each attendee from server
     var load_freebusy_data = function(from, interval)
     {
-      var start = new Date(from.getTime() - DAY_MS * 2);  // start 1 days before event
+      var start = new Date(from.getTime() - DAY_MS * 2);  // start 2 days before event
+      fix_date(start);
       var end = new Date(start.getTime() + DAY_MS * Math.max(14, freebusy_ui.numdays + 7));   // load min. 14 days
       freebusy_ui.numrequired = 0;
       freebusy_data.all = [];
       freebusy_data.required = [];
-      
+
       // load free-busy information for every attendee
       var domid, email;
       for (var i=0; i < event_attendees.length; i++) {
@@ -1070,7 +1089,7 @@ function rcube_calendar_ui(settings)
             type: 'GET',
             dataType: 'json',
             url: rcmail.url('freebusy-times'),
-            data: { email:email, start:date2unixtime(clone_date(start, 1)), end:date2unixtime(clone_date(end, 2)), interval:interval, _remote:1 },
+            data: { email:email, start:date2servertime(clone_date(start, 1)), end:date2servertime(clone_date(end, 2)), interval:interval, _remote:1 },
             success: function(data) {
               freebusy_ui.loading--;
               
@@ -1084,11 +1103,11 @@ function rcube_calendar_ui(settings)
               }
               
               // copy data to member var
-              var req = attendee.role != 'OPT-PARTICIPANT';
-              var ts = data.start - 0;
-              freebusy_data.start = ts;
+              var ts, req = attendee.role != 'OPT-PARTICIPANT';
+              freebusy_data.start = parseISO8601(data.start);
               freebusy_data[data.email] = {};
               for (var i=0; i < data.slots.length; i++) {
+                ts = data.times[i] + '';
                 freebusy_data[data.email][ts] = data.slots[i];
                 
                 // set totals
@@ -1100,10 +1119,8 @@ function rcube_calendar_ui(settings)
                 if (!freebusy_data.all[ts])
                   freebusy_data.all[ts] = [0,0,0,0];
                 freebusy_data.all[ts][data.slots[i]]++;
-                
-                ts += data.interval * 60;
               }
-              freebusy_data.end = ts;
+              freebusy_data.end = parseISO8601(data.end);
               freebusy_data.interval = data.interval;
 
               // hide loading indicator
@@ -1160,13 +1177,18 @@ function rcube_calendar_ui(settings)
       var domid = String(email).replace(rcmail.identifier_expr, '');
       var row = $('#fbrow' + domid);
       var rowall = $('#fbrowall').children();
-      var ts = date2unixtime(freebusy_ui.start);
-      var fbdata = freebusy_data[email];
-      
+      var dateonly = freebusy_ui.interval > 60,
+        t, ts = date2timestring(freebusy_ui.start, dateonly),
+        curdate = new Date(),
+        fbdata = freebusy_data[email];
+
       if (fbdata && fbdata[ts] !== undefined && row.length) {
+        t = freebusy_ui.start.getTime();
         row.children().each(function(i, cell){
+          curdate.setTime(t);
+          ts = date2timestring(curdate, dateonly);
           cell.className = cell.className.replace('unknown', fbdata[ts] ? status_classes[fbdata[ts]] : 'unknown');
-          
+
           // also update total row if all data was loaded
           if (freebusy_ui.loading == 0 && freebusy_data.all[ts] && (cell = rowall.get(i))) {
             var workinghours = cell.className.indexOf('workinghours') >= 0;
@@ -1182,7 +1204,7 @@ function rcube_calendar_ui(settings)
             cell.className = (workinghours ? 'workinghours ' : 'offhours ') + req_status +  ' all-' + all_status;
           }
           
-          ts += freebusy_ui.interval * 60;
+          t += freebusy_ui.interval * 60000;
         });
       }
     };
@@ -1190,9 +1212,12 @@ function rcube_calendar_ui(settings)
     // write changed event date/times back to form fields
     var update_freebusy_dates = function(start, end)
     {
+      // fix all-day evebt times
       if (me.selected_event.allDay) {
         start.setHours(12);
         start.setMinutes(0);
+        if (end.getHours() == 0)
+          end.setHours(-1);
         end.setHours(13);
         end.setMinutes(0);
       }
@@ -1209,13 +1234,13 @@ function rcube_calendar_ui(settings)
     var freebusy_find_slot = function(dir)
     {
       var event = me.selected_event,
-        eventstart = date2unixtime(event.start),  // calculate with unixtimes
-        eventend = date2unixtime(event.end),
+        eventstart = event.start.getTime(),  // calculate with integers
+        eventend = event.end.getTime(),
         duration = eventend - eventstart,
-        sinterval = freebusy_data.interval * 60,
+        sinterval = freebusy_data.interval * 60000,
         intvlslots = 1,
         numslots = Math.ceil(duration / sinterval),
-        checkdate, slotend, email, curdate;
+        checkdate, slotend, email, ts, slot, slotdate = new Date(), slotenddate = new Date();
 
       // shift event times to next possible slot
       eventstart += sinterval * intvlslots * dir;
@@ -1223,27 +1248,32 @@ function rcube_calendar_ui(settings)
 
       // iterate through free-busy slots and find candidates
       var candidatecount = 0, candidatestart = candidateend = success = false;
-      for (var slot = dir > 0 ? freebusy_data.start : freebusy_data.end - sinterval; (dir > 0 && slot < freebusy_data.end) || (dir < 0 && slot >= freebusy_data.start); slot += sinterval * dir) {
+      for (slot = dir > 0 ? freebusy_data.start.getTime() : freebusy_data.end.getTime() - sinterval;
+            (dir > 0 && slot < freebusy_data.end.getTime()) || (dir < 0 && slot >= freebusy_data.start.getTime());
+            slot += sinterval * dir) {
         slotend = slot + sinterval;
+        slotdate.setTime(slot);
+        slotenddate.setTime(slotend);
+
         if ((dir > 0 && slotend <= eventstart) || (dir < 0 && slot >= eventend))  // skip
           continue;
-        
+
         // respect workingours setting
         if (freebusy_ui.workinhoursonly) {
-          curdate = fromunixtime(dir > 0 || !candidateend ? slot : (candidateend - duration));
-          if (is_weekend(curdate) || (freebusy_data.interval <= 60 && !is_workinghour(curdate))) {  // skip off-hours
+          if (is_weekend(slotdate) || (freebusy_data.interval <= 60 && !is_workinghour(slotdate))) {  // skip off-hours
             candidatestart = candidateend = false;
             candidatecount = 0;
             continue;
           }
         }
-        
+
         if (!candidatestart)
           candidatestart = slot;
-        
+
         // check freebusy data for all attendees
+        ts = date2timestring(slotdate, freebusy_data.interval > 60);
         for (var i=0; i < event_attendees.length; i++) {
-          if (freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT' && (email = freebusy_ui.attendees[i].email) && freebusy_data[email] && freebusy_data[email][slot] > 1) {
+          if (freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT' && (email = freebusy_ui.attendees[i].email) && freebusy_data[email] && freebusy_data[email][ts] > 1) {
             candidatestart = candidateend = false;
             break;
           }
@@ -1264,12 +1294,12 @@ function rcube_calendar_ui(settings)
         // if candidate is big enough, this is it!
         if (candidatecount == numslots) {
           if (dir > 0) {
-            event.start = fromunixtime(candidatestart);
-            event.end = fromunixtime(candidatestart + duration);
+            event.start.setTime(candidatestart);
+            event.end.setTime(candidatestart + duration);
           }
           else {
-            event.end = fromunixtime(candidateend);
-            event.start = fromunixtime(candidateend - duration);
+            event.end.setTime(candidateend);
+            event.start.setTime(candidateend - duration);
           }
           success = true;
           break;
@@ -1432,7 +1462,7 @@ function rcube_calendar_ui(settings)
         type: 'GET',
         dataType: 'html',
         url: rcmail.url('freebusy-status'),
-        data: { email:email, start:date2unixtime(clone_date(event.start, event.allDay?1:0)), end:date2unixtime(clone_date(event.end, event.allDay?2:0)), _remote: 1 },
+        data: { email:email, start:date2servertime(clone_date(event.start, event.allDay?1:0)), end:date2servertime(clone_date(event.end, event.allDay?2:0)), _remote: 1 },
         success: function(status){
           icon.removeClass('loading').addClass(String(status).toLowerCase());
         },
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index 35897d7..b2ae597 100644
--- a/plugins/libcalendaring/libcalendaring.js
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -110,7 +110,7 @@ function rcube_libcalendaring(settings)
      * Convert an ISO 8601 formatted date string from the server into a Date object.
      * Timezone information will be ignored, the server already provides dates in user's timezone.
      */
-    function parseISO8601(s)
+    this.parseISO8601 = function(s)
     {
         // force d to be on check's YMD, for daylight savings purposes
         var fixDate = function(d, check) {
@@ -319,8 +319,8 @@ function rcube_libcalendaring(settings)
         var actions, adismiss, asnooze, alarm, html, event_ids = [];
         for (var i=0; i < alarms.length; i++) {
             alarm = alarms[i];
-            alarm.start = parseISO8601(alarm.start);
-            alarm.end = parseISO8601(alarm.end);
+            alarm.start = this.parseISO8601(alarm.start);
+            alarm.end = this.parseISO8601(alarm.end);
             event_ids.push(alarm.id);
 
             html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';





More information about the commits mailing list