Make free/busy finder DST aware (#1676)

This commit is contained in:
Thomas Bruederli 2013-03-06 10:57:56 +01:00 committed by Thomas Bruederli
parent cddf6e0494
commit 7e4b581b4e
3 changed files with 107 additions and 38 deletions

View file

@ -1383,6 +1383,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;
@ -1420,11 +1430,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();
@ -1432,6 +1458,8 @@ 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)) {
@ -1447,13 +1475,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;
}

View file

@ -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;
@ -469,7 +484,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'];
@ -894,10 +909,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);
@ -966,7 +984,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 {
@ -1070,7 +1088,8 @@ 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 = [];
@ -1088,7 +1107,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--;
@ -1102,11 +1121,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
@ -1118,10 +1137,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
@ -1178,11 +1195,16 @@ 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
@ -1200,7 +1222,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;
});
}
};
@ -1208,9 +1230,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);
}
@ -1227,13 +1252,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;
@ -1241,15 +1266,19 @@ 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;
@ -1260,8 +1289,9 @@ function rcube_calendar_ui(settings)
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;
}
@ -1282,12 +1312,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;
@ -1457,7 +1487,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());
},

View file

@ -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>';