From 4b3e73889faeb1e3cd31366f7f84674665e43b4a Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 30 Jun 2011 10:02:02 +0200 Subject: [PATCH 1/3] Properly support AM/PM time format --- plugins/calendar/calendar_ui.js | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index eff093b7..e98bbc0f 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -70,10 +70,19 @@ function rcube_calendar_ui(settings) var parse_datetime = function(time, date) { // we use the utility function from datepicker to parse dates - var date = $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings); - var time_arr = time.split(/[:.]/); - if (!isNaN(time_arr[0])) date.setHours(time_arr[0]); - if (!isNaN(time_arr[1])) date.setMinutes(time_arr[1]); + var date = date ? $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings) : new Date(); + + var time_arr = time.replace(/\s*[ap]m?/i, '').replace(/0([0-9])/g, '$1').split(/[:.]/); + if (!isNaN(time_arr[0])) { + date.setHours(time_arr[0]); + if (time.match(/pm?/i) && date.getHours() < 12) + date.setHours(parseInt(time_arr[0]) + 12); + else if (date.getHours() == 12) + date.setHours(0); + } + if (!isNaN(time_arr[1])) + date.setMinutes(time_arr[1]); + return date; }; @@ -947,7 +956,10 @@ function rcube_calendar_ui(settings) // format time string var formattime = function(hour, minutes) { - return ((hour < 10) ? "0" : "") + hour + ((minutes < 10) ? ":0" : ":") + minutes; + var d = new Date(); + d.setHours(hour); + d.setMinutes(minutes); + return $.fullCalendar.formatDate(d, settings['time_format']) }; // if start date is changed, shift end date according to initial duration @@ -974,7 +986,7 @@ function rcube_calendar_ui(settings) var result = []; var now = new Date(); var full = p.term - 1 > 0 || p.term.length > 1; - var hours = full? p.term - 0 : now.getHours(); + var hours = (full ? parse_datetime(p.term, '') : now).getHours(); var step = 15; var minutes = hours * 60 + (full ? 0 : now.getMinutes()); var min = Math.ceil(minutes / step) * step % 60; @@ -982,7 +994,7 @@ function rcube_calendar_ui(settings) // list hours from 0:00 till now for (var h = 0; h < hours; h++) result.push(formattime(h, 0)); - // list 15min steps for the next two hours + // list 15min steps for the next two hours for (; h < hour + 2; h++) { while (min < 60) { result.push(formattime(h, min)); @@ -999,11 +1011,12 @@ function rcube_calendar_ui(settings) // scroll to current time var widget = $(this).autocomplete('widget'); var menu = $(this).data('autocomplete').menu; - var val = $(this).val(); + var val = $(this).val().replace(/^(.+)(am?)/i, '0:$1').replace(/^(.+)(pm?)/i, '1:$1'); var li, html, offset = 0; + widget.css('width', '7em'); widget.children().each(function(){ li = $(this); - html = li.children().first().html(); + html = li.children().first().html().replace(/^(.+)(am?)/i, '0:$1').replace(/^(.+)(pm?)/i, '1:$1'); if (html < val) offset += li.height(); if (html == val) From de45b996b42ede2b50f3fff2beaab6a7062727c7 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 30 Jun 2011 10:11:27 +0200 Subject: [PATCH 2/3] Move defaults to public member for general access --- plugins/calendar/calendar.php | 49 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index bfab2ad2..0b4b00ef 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -33,6 +33,18 @@ class calendar extends rcube_plugin public $ical; public $ui; + + public $defaults = array( + 'calendar_default_view' => "agendaWeek", + 'calendar_date_format' => "yyyy-MM-dd", + 'calendar_date_short' => "M-d", + 'calendar_date_long' => "MMM d yyyy", + 'calendar_date_agenda' => "ddd MM-dd", + 'calendar_time_format' => "HH:mm", + 'calendar_timeslots' => 2, + 'calendar_first_day' => 1, + 'calendar_first_hour' => 6, + ); private $default_categories = array( 'Personal' => 'c0c0c0', @@ -56,6 +68,14 @@ class calendar extends rcube_plugin // load localizations $this->add_texts('localization/', $this->rc->task == 'calendar' && !$this->rc->action); + // set user's timezone + if ($this->rc->config->get('timezone') === 'auto') + $this->timezone = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z'); + else + $this->timezone = ($this->rc->config->get('timezone') + intval($this->rc->config->get('dst_active'))); + + $this->gmt_offset = $this->timezone * 3600; + require($this->home . '/lib/calendar_ui.php'); $this->ui = new calendar_ui($this); @@ -99,14 +119,6 @@ class calendar extends rcube_plugin // add hook to display alarms $this->add_hook('keep_alive', array($this, 'keep_alive')); - - // set user's timezone - if ($this->rc->config->get('timezone') === 'auto') - $this->timezone = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z'); - else - $this->timezone = ($this->rc->config->get('timezone') + intval($this->rc->config->get('dst_active'))); - - $this->gmt_offset = $this->timezone * 3600; } /** @@ -158,7 +170,6 @@ class calendar extends rcube_plugin $this->register_handler('plugin.edit_recurring_warning', array($this->ui, 'recurring_event_warning')); $this->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template - $this->rc->output->set_env('calendar_settings', $this->load_settings()); $this->rc->output->add_label('low','normal','high'); $this->rc->output->send("calendar.calendar"); @@ -533,15 +544,15 @@ class calendar extends rcube_plugin // configuration $settings['default_calendar'] = $this->rc->config->get('calendar_default_calendar'); - $settings['default_view'] = (string)$this->rc->config->get('calendar_default_view', "agendaWeek"); - $settings['date_format'] = (string)$this->rc->config->get('calendar_date_format', "yyyy/MM/dd"); - $settings['date_short'] = (string)$this->rc->config->get('calendar_date_short', "M/d"); - $settings['date_long'] = (string)$this->rc->config->get('calendar_date_long', "M d yyyy"); - $settings['date_agenda'] = (string)$this->rc->config->get('calendar_date_agenda', "ddd M d"); - $settings['time_format'] = (string)$this->rc->config->get('calendar_time_format', "HH:mm"); - $settings['timeslots'] = (int)$this->rc->config->get('calendar_timeslots', 2); - $settings['first_day'] = (int)$this->rc->config->get('calendar_first_day', 1); - $settings['first_hour'] = (int)$this->rc->config->get('calendar_first_hour', 6); + $settings['default_view'] = (string)$this->rc->config->get('calendar_default_view', $this->defaults['calendar_default_view']); + $settings['date_format'] = (string)$this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']); + $settings['date_short'] = (string)$this->rc->config->get('calendar_date_short', $this->defaults['calendar_date_short']); + $settings['date_long'] = (string)$this->rc->config->get('calendar_date_long', $this->defaults['calendar_date_long']); + $settings['date_agenda'] = (string)$this->rc->config->get('calendar_date_agenda', $this->defaults['calendar_date_agenda']); + $settings['time_format'] = (string)$this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']); + $settings['timeslots'] = (int)$this->rc->config->get('calendar_timeslots', $this->defaults['calendar_timeslots']); + $settings['first_day'] = (int)$this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']); + $settings['first_hour'] = (int)$this->rc->config->get('calendar_first_hour', $this->defaults['calendar_first_hour']); $settings['timezone'] = $this->timezone; // localization @@ -698,7 +709,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'))); + $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']))); else $until = $this->gettext('forever'); From 04f649c6fd258319d88a17cfc50f2df79cd45aed Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 30 Jun 2011 22:49:20 +0200 Subject: [PATCH 3/3] Add support for 'every last friday (in june)' recurrence rules --- .../calendar/lib/Horde_Date_Recurrence.php | 145 +++++++++++++++--- 1 file changed, 123 insertions(+), 22 deletions(-) diff --git a/plugins/calendar/lib/Horde_Date_Recurrence.php b/plugins/calendar/lib/Horde_Date_Recurrence.php index 166224b1..51a0bad2 100644 --- a/plugins/calendar/lib/Horde_Date_Recurrence.php +++ b/plugins/calendar/lib/Horde_Date_Recurrence.php @@ -5233,6 +5233,20 @@ class Horde_Date_Recurrence { */ var $recurData = null; + /** + * BYDAY recurrence number + * + * @var integer + */ + var $recurNthDay = 0; + + /** + * BYMONTH recurrence data + * + * @var array + */ + var $recurMonths = array(); + /** * All the exceptions from recurrence for this event. * @@ -5292,6 +5306,44 @@ class Horde_Date_Recurrence { return $this->recurData; } + /** + * Specifies the months for yearly (weekday) recurrence + * + * @param array $months List of months (integers) this event recurs on. + */ + function setRecurByMonth($months) + { + $this->recurMonths = $months; + } + + /** + * Returns a list of months this yearly event recurs on + * + * @return array List of months (integers) this event recurs on. + */ + function getRecurByMonth() + { + return $this->recurMonths; + } + + /** + * + * @param integer $nthDay The nth weekday of month to repeat events on + */ + function setRecurNthWeekday($nthDay) + { + $this->recurNthDay = $nthDay; + } + + /** + * + * @return integer The nth weekday of month to repeat events. + */ + function getRecurNthWeekday() + { + return $this->recurNthDay; + } + /** * Returns whether this event has a specific recurrence type. * @@ -5657,8 +5709,13 @@ class Horde_Date_Recurrence { $estart = new Horde_Date($this->start); // What day of the week, and week of the month, do we recur on? - $nth = ceil($this->start->mday / 7); - $weekday = $estart->dayOfWeek(); + if ($this->recurNthDay != 0) { + $nth = $this->recurNthDay < 0 ? 'last' : $this->recurNthDay; + $weekday = log($this->recurData, 2); + } else { + $nth = ceil($this->start->mday / 7); + $weekday = $estart->dayOfWeek(); + } // Adjust $estart to be the first candidate. $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12; @@ -5678,7 +5735,11 @@ class Horde_Date_Recurrence { $estart->correct(); $next = new Horde_Date($estart); - $next->setNthWeekday($weekday, $nth); + if ($this->recurNthDay) { + list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::nWeekdayOfMonth($nth, $weekday, $estart->month, $estart->year, '%e/%m/%Y')); + } else { + $next->setNthWeekday($weekday, $nth); + } if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. @@ -5773,8 +5834,18 @@ class Horde_Date_Recurrence { $estart = new Horde_Date($this->start); // What day of the week, and week of the month, do we recur on? - $nth = ceil($this->start->mday / 7); - $weekday = $estart->dayOfWeek(); + if ($this->recurNthDay != 0) { + $nth = $this->recurNthDay < 0 ? 'last' : $this->recurNthDay; + $weekday = log($this->recurData, 2); + } else { + $nth = ceil($this->start->mday / 7); + $weekday = $estart->dayOfWeek(); + } + + // set month from recurrence rule (FEXME: support more than one month) + if ($this->recurMonths) { + $estart->month = $this->recurMonths[0]; + } // Adjust $estart to be the first candidate. $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; @@ -5793,7 +5864,11 @@ class Horde_Date_Recurrence { $estart->correct(); $next = new Horde_Date($estart); - $next->setNthWeekday($weekday, $nth); + if ($this->recurNthDay) { + list($next->mday, $next->month, $next->year) = explode('/', Date_Calc::nWeekdayOfMonth($nth, $weekday, $estart->month, $estart->year, '%e/%m/%Y')); + } else { + $next->setNthWeekday($weekday, $nth); + } if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. @@ -6159,6 +6234,14 @@ class Horde_Date_Recurrence { // Always default the recurInterval to 1. $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1); + $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, + 'MO' => HORDE_DATE_MASK_MONDAY, + 'TU' => HORDE_DATE_MASK_TUESDAY, + 'WE' => HORDE_DATE_MASK_WEDNESDAY, + 'TH' => HORDE_DATE_MASK_THURSDAY, + 'FR' => HORDE_DATE_MASK_FRIDAY, + 'SA' => HORDE_DATE_MASK_SATURDAY); + switch (strtoupper($rdata['FREQ'])) { case 'DAILY': $this->setRecurType(HORDE_DATE_RECUR_DAILY); @@ -6167,13 +6250,6 @@ class Horde_Date_Recurrence { case 'WEEKLY': $this->setRecurType(HORDE_DATE_RECUR_WEEKLY); if (isset($rdata['BYDAY'])) { - $maskdays = array('SU' => HORDE_DATE_MASK_SUNDAY, - 'MO' => HORDE_DATE_MASK_MONDAY, - 'TU' => HORDE_DATE_MASK_TUESDAY, - 'WE' => HORDE_DATE_MASK_WEDNESDAY, - 'TH' => HORDE_DATE_MASK_THURSDAY, - 'FR' => HORDE_DATE_MASK_FRIDAY, - 'SA' => HORDE_DATE_MASK_SATURDAY); $days = explode(',', $rdata['BYDAY']); $mask = 0; foreach ($days as $day) { @@ -6198,6 +6274,10 @@ class Horde_Date_Recurrence { case 'MONTHLY': if (isset($rdata['BYDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_WEEKDAY); + if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { + $this->setRecurOnDay($maskdays[$m[2]]); + $this->setRecurNthWeekday($m[1]); + } } else { $this->setRecurType(HORDE_DATE_RECUR_MONTHLY_DATE); } @@ -6208,6 +6288,14 @@ class Horde_Date_Recurrence { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DAY); } elseif (isset($rdata['BYDAY'])) { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_WEEKDAY); + if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) { + $this->setRecurOnDay($maskdays[$m[2]]); + $this->setRecurNthWeekday($m[1]); + } + if ($rdata['BYMONTH']) { + $months = explode(',', $rdata['BYMONTH']); + $this->setRecurByMonth($months); + } } else { $this->setRecurType(HORDE_DATE_RECUR_YEARLY_DATE); } @@ -6271,13 +6359,19 @@ class Horde_Date_Recurrence { break; case HORDE_DATE_RECUR_MONTHLY_WEEKDAY: - $nth_weekday = (int)($this->start->mday / 7); - if (($this->start->mday % 7) > 0) { - $nth_weekday++; + if ($this->recurNthDay != 0) { + $nth_weekday = $this->recurNthDay; + $day_of_week = log($this->recurData, 2); + } else { + $day_of_week = $this->start->dayOfWeek(); + $nth_weekday = (int)($this->start->mday / 7); + if (($this->start->mday % 7) > 0) { + $nth_weekday++; + } } $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval - . ';BYDAY=' . $nth_weekday . $vcaldays[$this->start->dayOfWeek()]; + . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week]; break; case HORDE_DATE_RECUR_YEARLY_DATE: @@ -6290,16 +6384,23 @@ class Horde_Date_Recurrence { break; case HORDE_DATE_RECUR_YEARLY_WEEKDAY: - $nth_weekday = (int)($this->start->mday / 7); - if (($this->start->mday % 7) > 0) { - $nth_weekday++; + if ($this->recurNthDay != 0) { + $nth_weekday = $this->recurNthDay; + $day_of_week = log($this->recurData, 2); + } else { + $day_of_week = $this->start->dayOfWeek(); + $nth_weekday = (int)($this->start->mday / 7); + if (($this->start->mday % 7) > 0) { + $nth_weekday++; + } } + $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month; $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval . ';BYDAY=' . $nth_weekday - . $vcaldays[$this->start->dayOfWeek()] - . ';BYMONTH=' . $this->start->month; + . $vcaldays[$day_of_week] + . ';BYMONTH=' . $months; break; }