Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F118607965
D536.1775940095.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
25 KB
Referenced Files
None
Subscribers
None
D536.1775940095.diff
View Options
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -857,8 +857,8 @@
if ($success && $reload)
$this->rc->output->command('plugin.reload_view');
}
-
-
+
+
/**
* Dispatcher for event actions initiated by the client
*/
@@ -867,7 +867,7 @@
$action = rcube_utils::get_input_value('action', rcube_utils::INPUT_GPC);
$event = rcube_utils::get_input_value('e', rcube_utils::INPUT_POST, true);
$success = $reload = $got_msg = false;
-
+
// force notify if hidden + active
if ((int)$this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']) === 1)
$event['_notify'] = 1;
@@ -887,8 +887,10 @@
case "new":
// create UID for new event
$event['uid'] = $this->generate_uid();
- $this->write_preprocess($event, $action);
- if ($success = $this->driver->new_event($event)) {
+ if (!$this->write_preprocess($event, $action)) {
+ $got_msg = true;
+ }
+ else if ($success = $this->driver->new_event($event)) {
$event['id'] = $event['uid'];
$event['_savemode'] = 'all';
$this->cleanup_event($event);
@@ -898,8 +900,10 @@
break;
case "edit":
- $this->write_preprocess($event, $action);
- if ($success = $this->driver->edit_event($event)) {
+ if (!$this->write_preprocess($event, $action)) {
+ $got_msg = true;
+ }
+ else if ($success = $this->driver->edit_event($event)) {
$this->cleanup_event($event);
$this->event_save_success($event, $old, $action, $success);
}
@@ -907,19 +911,23 @@
break;
case "resize":
- $this->write_preprocess($event, $action);
- if ($success = $this->driver->resize_event($event)) {
+ if (!$this->write_preprocess($event, $action)) {
+ $got_msg = true;
+ }
+ else if ($success = $this->driver->resize_event($event)) {
$this->event_save_success($event, $old, $action, $success);
}
$reload = $event['_savemode'] ? 2 : 1;
break;
case "move":
- $this->write_preprocess($event, $action);
- if ($success = $this->driver->move_event($event)) {
+ if (!$this->write_preprocess($event, $action)) {
+ $got_msg = true;
+ }
+ else if ($success = $this->driver->move_event($event)) {
$this->event_save_success($event, $old, $action, $success);
}
- $reload = $success && $event['_savemode'] ? 2 : 1;
+ $reload = $success && $event['_savemode'] ? 2 : 1;
break;
case "remove":
@@ -1184,7 +1192,7 @@
// unlock client
$this->rc->output->command('plugin.unlock_saving');
- // update event object on the client or trigger a complete refretch if too complicated
+ // update event object on the client or trigger a complete refresh if too complicated
if ($reload) {
$args = array('source' => $event['calendar']);
if ($reload > 1)
@@ -1993,12 +2001,31 @@
// start/end is all we need for 'move' action (#1480)
if ($action == 'move') {
- return;
+ return true;
}
// convert the submitted recurrence settings
if (is_array($event['recurrence'])) {
$event['recurrence'] = $this->lib->from_client_recurrence($event['recurrence'], $event['start']);
+
+ // align start date with the first occurrence
+ if (!empty($event['recurrence']) && !empty($event['syncstart'])
+ && (empty($event['_savemode']) || $event['_savemode'] == 'all')
+ ) {
+ $next = $this->find_first_occurrence($event);
+
+ if (!$next) {
+ $this->rc->output->show_message('calendar.recurrenceerror', 'error');
+ return false;
+ }
+ else if ($event['start'] != $next) {
+ $diff = $event['start']->diff($event['end'], true);
+
+ $event['start'] = $next;
+ $event['end'] = clone $next;
+ $event['end']->add($diff);
+ }
+ }
}
// convert the submitted alarm values
@@ -2075,6 +2102,8 @@
$event['url'] = $event['vurl'];
unset($event['vurl']);
}
+
+ return true;
}
/**
@@ -3448,6 +3477,35 @@
}
/**
+ * Find first occurrence of a recurring event excluding start date
+ *
+ * @param array $event Event data (with 'start' and 'recurrence')
+ *
+ * @return DateTime Date of the first occurrence
+ */
+ public function find_first_occurrence($event)
+ {
+ // Make sure libkolab plugin is loaded in case of Kolab driver
+ $this->load_driver();
+
+ // Use libkolab to compute recurring events (and libkolab plugin)
+ // Horde-based fallback has many bugs
+ if (class_exists('kolabformat') && class_exists('kolabcalendaring') && class_exists('kolab_date_recurrence')) {
+ $object = kolab_format::factory('event', 3.0);
+ $object->set($event);
+
+ $recurrence = new kolab_date_recurrence($object);
+ }
+ else {
+ // fallback to libcalendaring (Horde-based) recurrence implementation
+ require_once(__DIR__ . '/lib/calendar_recurrence.php');
+ $recurrence = new calendar_recurrence($this, $event);
+ }
+
+ return $recurrence->first_occurrence();
+ }
+
+ /**
* Magic getter for public access to protected members
*/
public function __get($name)
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -678,7 +678,7 @@
var freebusy = $('#edit-free-busy').val(event.free_busy);
var priority = $('#edit-priority').val(event.priority);
var sensitivity = $('#edit-sensitivity').val(event.sensitivity);
-
+ var syncstart = $('#edit-recurrence-syncstart input');
var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
var startdate = $('#edit-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
var starttime = $('#edit-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
@@ -898,6 +898,9 @@
data._fromcalendar = event.calendar;
}
+ if (data.recurrence && syncstart.is(':checked'))
+ data.syncstart = 1;
+
update_event(action, data);
$dialog.dialog("close");
} // end click:
@@ -3974,8 +3977,15 @@
$('#edit-attendees-form .attendees-invitebox').show();
}
}
+
// reset autocompletion on tab change (#3389)
rcmail.ksearch_blur();
+
+ // display recurrence warning in recurrence tab only
+ if (tab == 'recurrence')
+ $('#edit-recurrence-frequency').change();
+ else
+ $('#edit-recurrence-syncstart').hide();
}
});
$('#edit-enddate').datepicker(datepicker_settings);
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -659,14 +659,7 @@
}
// use libkolab to compute recurring events
- if (class_exists('kolabcalendaring')) {
- $recurrence = new kolab_date_recurrence($object);
- }
- else {
- // fallback to local recurrence implementation
- require_once($this->cal->home . '/lib/calendar_recurrence.php');
- $recurrence = new calendar_recurrence($this->cal, $event);
- }
+ $recurrence = new kolab_date_recurrence($object);
$i = 0;
while ($next_event = $recurrence->next_instance()) {
@@ -717,7 +710,7 @@
if (++$i > 100000)
break;
}
-
+
return $events;
}
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -1784,16 +1784,16 @@
*/
private function get_recurrence_count($event, $dtstart)
{
- // use libkolab to compute recurring events
- if (class_exists('kolabcalendaring') && $event['_formatobj']) {
- $recurrence = new kolab_date_recurrence($event['_formatobj']);
- }
- else {
- // fallback to local recurrence implementation
- require_once($this->cal->home . '/lib/calendar_recurrence.php');
- $recurrence = new calendar_recurrence($this->cal, $event);
+ // load the given event data into a libkolabxml container
+ if (!$event['_formatobj']) {
+ $event_xml = new kolab_format_event();
+ $event_xml->set($event);
+ $event['_formatobj'] = $event_xml;
}
+ // use libkolab to compute recurring events
+ $recurrence = new kolab_date_recurrence($event['_formatobj']);
+
$count = 0;
while (($next_event = $recurrence->next_instance()) && $next_event['start'] <= $dtstart && $count < 1000) {
$count++;
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -92,6 +92,7 @@
$this->cal->register_handler('plugin.resource_calendar', array($this, 'resource_calendar'));
$this->cal->register_handler('plugin.attendees_freebusy_table', array($this, 'attendees_freebusy_table'));
$this->cal->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify'));
+ $this->cal->register_handler('plugin.edit_recurrence_sync', array($this, 'edit_recurrence_sync'));
$this->cal->register_handler('plugin.edit_recurring_warning', array($this, 'recurring_event_warning'));
$this->cal->register_handler('plugin.event_rsvp_buttons', array($this, 'event_rsvp_buttons'));
$this->cal->register_handler('plugin.angenda_options', array($this, 'angenda_options'));
@@ -473,7 +474,7 @@
}
/**
- *
+ * Render HTML for attendee notification warning
*/
function edit_attendees_notify($attrib = array())
{
@@ -482,6 +483,15 @@
}
/**
+ * Render HTML for recurrence option to align start date with the recurrence rule
+ */
+ function edit_recurrence_sync($attrib = array())
+ {
+ $checkbox = new html_checkbox(array('name' => '_start_sync', 'value' => 1));
+ return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->cal->gettext('eventstartsync')));
+ }
+
+ /**
* Generate the form for recurrence settings
*/
function recurring_event_warning($attrib = array())
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -125,6 +125,7 @@
$labels['invitationsdeclined'] = 'Declined invitations';
$labels['changepartstat'] = 'Change participant status';
$labels['rsvpcomment'] = 'Invitation text';
+$labels['eventstartsync'] = 'Move the event start date to the first occurrence';
// agenda view
$labels['listrange'] = 'Range to display:';
@@ -267,6 +268,7 @@
$labels['futurevents'] = 'Future';
$labels['allevents'] = 'All';
$labels['saveasnew'] = 'Save as new';
+$labels['recurrenceerror'] = 'Unable to resolve recurrence rule for specified start date.';
// birthdays calendar
$labels['birthdays'] = 'Birthdays';
diff --git a/plugins/calendar/skins/larry/templates/eventedit.html b/plugins/calendar/skins/larry/templates/eventedit.html
--- a/plugins/calendar/skins/larry/templates/eventedit.html
+++ b/plugins/calendar/skins/larry/templates/eventedit.html
@@ -127,7 +127,8 @@
</div>
</form>
+ <roundcube:object name="plugin.edit_recurrence_sync" id="edit-recurrence-syncstart" class="event-dialog-message" style="display:none" />
<roundcube:object name="plugin.edit_attendees_notify" id="edit-attendees-notify" class="event-dialog-message" style="display:none" />
<roundcube:object name="plugin.edit_recurring_warning" class="event-dialog-message edit-recurring-warning" style="display:none" />
<div id="edit-localchanges-warning" class="event-dialog-message" style="display:none"><roundcube:label name="calendar.localchangeswarning" /></div>
</div>
\ No newline at end of file
diff --git a/plugins/libcalendaring/lib/libcalendaring_recurrence.php b/plugins/libcalendaring/lib/libcalendaring_recurrence.php
--- a/plugins/libcalendaring/lib/libcalendaring_recurrence.php
+++ b/plugins/libcalendaring/lib/libcalendaring_recurrence.php
@@ -152,4 +152,83 @@
return $last;
}
+ /**
+ * Find date/time of the first occurrence (excluding start date)
+ */
+ public function first_occurrence()
+ {
+ $start = clone $this->start;
+ $orig_start = clone $this->start;
+ $r = $this->recurrence;
+ $interval = intval($r['INTERVAL'] ?: 1);
+
+ switch ($this->recurrence['FREQ']) {
+ case 'WEEKLY':
+ if (empty($this->recurrence['BYDAY'])) {
+ return $start;
+ }
+
+ $start->sub(new DateInterval("P{$interval}W"));
+ break;
+
+ case 'MONTHLY':
+ if (empty($this->recurrence['BYDAY']) && empty($this->recurrence['BYMONTHDAY'])) {
+ return $start;
+ }
+
+ $start->sub(new DateInterval("P{$interval}M"));
+ break;
+
+ case 'YEARLY':
+ if (empty($this->recurrence['BYDAY']) && empty($this->recurrence['BYMONTH'])) {
+ return $start;
+ }
+
+ $start->sub(new DateInterval("P{$interval}Y"));
+ break;
+
+ default:
+ return $start;
+ }
+
+ $r = $this->recurrence;
+ $r['INTERVAL'] = $interval;
+ if ($r['COUNT']) {
+ // Increase count so we do not stop the loop to early
+ $r['COUNT'] += 100;
+ }
+
+ // Create recurrence that starts in the past
+ $recurrence = new self($this->lib);
+ $recurrence->init($r, $start);
+
+ // find the first occurrence
+ $found = false;
+ while ($next = $recurrence->next()) {
+ $start = $next;
+ if ($next >= $orig_start) {
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ rcube::raise_error(array(
+ 'file' => __FILE__,
+ 'line' => __LINE__,
+ 'message' => sprintf("Failed to find a first occurrence. Start: %s, Recurrence: %s",
+ $orig_start->format(DateTime::ISO8601), json_encode($r)),
+ ), true);
+
+ return null;
+ }
+
+ if ($start Instanceof Horde_Date) {
+ $start = $start->toDateTime();
+ }
+
+ $start->_dateonly = $this->dateonly;
+
+ return $start;
+ }
}
diff --git a/plugins/libkolab/lib/kolab_date_recurrence.php b/plugins/libkolab/lib/kolab_date_recurrence.php
--- a/plugins/libkolab/lib/kolab_date_recurrence.php
+++ b/plugins/libkolab/lib/kolab_date_recurrence.php
@@ -138,4 +138,89 @@
return false;
}
+
+ /**
+ * Find date/time of the first occurrence (excluding start date)
+ */
+ public function first_occurrence()
+ {
+ $event = $this->object->to_array();
+ $start = clone $this->start;
+ $orig_start = clone $this->start;
+ $interval = intval($event['recurrence']['INTERVAL'] ?: 1);
+
+ switch ($event['recurrence']['FREQ']) {
+ case 'WEEKLY':
+ if (empty($event['recurrence']['BYDAY'])) {
+ return $orig_start;
+ }
+
+ $start->sub(new DateInterval("P{$interval}W"));
+ break;
+
+ case 'MONTHLY':
+ if (empty($event['recurrence']['BYDAY']) && empty($event['recurrence']['BYMONTHDAY'])) {
+ return $orig_start;
+ }
+
+ $start->sub(new DateInterval("P{$interval}M"));
+ break;
+
+ case 'YEARLY':
+ if (empty($event['recurrence']['BYDAY']) && empty($event['recurrence']['BYMONTH'])) {
+ return $orig_start;
+ }
+
+ $start->sub(new DateInterval("P{$interval}Y"));
+ break;
+
+ case 'DAILY':
+ if (!empty($event['recurrence']['BYMONTH'])) {
+ break;
+ }
+
+ default:
+ return $orig_start;
+ }
+
+ $event['start'] = $start;
+ $event['recurrence']['INTERVAL'] = $interval;
+ if ($event['recurrence']['COUNT']) {
+ // Increase count so we do not stop the loop to early
+ $event['recurrence']['COUNT'] += 100;
+ }
+
+ // Create recurrence that starts in the past
+ $object_type = $this->object instanceof kolab_format_task ? 'task' : 'event';
+ $object = kolab_format::factory($object_type, 3.0);
+ $object->set($event);
+ $recurrence = new self($object);
+
+ // find the first occurrence
+ $found = false;
+ while ($next = $recurrence->next_start()) {
+ $start = $next;
+ if ($next >= $orig_start) {
+ $found = true;
+ break;
+ }
+ }
+
+ if (!$found) {
+ rcube::raise_error(array(
+ 'file' => __FILE__,
+ 'line' => __LINE__,
+ 'message' => sprintf("Failed to find a first occurrence. Start: %s, Recurrence: %s",
+ $orig_start->format(DateTime::ISO8601), json_encode($event['recurrence'])),
+ ), true);
+
+ return null;
+ }
+
+ if ($orig_start->_dateonly) {
+ $start->_dateonly = true;
+ }
+
+ return $start;
+ }
}
diff --git a/plugins/libkolab/tests/kolab_date_recurrence.php b/plugins/libkolab/tests/kolab_date_recurrence.php
new file mode 100644
--- /dev/null
+++ b/plugins/libkolab/tests/kolab_date_recurrence.php
@@ -0,0 +1,213 @@
+<?php
+
+/**
+ * kolab_date_recurrence tests
+ *
+ * @author Aleksander Machniak <machniak@kolabsys.com>
+ *
+ * Copyright (C) 2017, 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_date_recurrence_test extends PHPUnit_Framework_TestCase
+{
+ function setUp()
+ {
+ $rcube = rcmail::get_instance();
+ $rcube->plugins->load_plugin('libkolab', true, true);
+ }
+
+ /**
+ * kolab_date_recurrence::first_occurrence()
+ *
+ * @dataProvider data_first_occurrence
+ */
+ function test_first_occurrence($recurrence_data, $start, $expected)
+ {
+ $start = new DateTime($start);
+ if (!empty($recurrence_data['UNTIL'])) {
+ $recurrence_data['UNTIL'] = new DateTime($recurrence_data['UNTIL']);
+ }
+
+ $event = array('start' => $start, 'recurrence' => $recurrence_data);
+ $object = kolab_format::factory('event', 3.0);
+ $object->set($event);
+
+ $recurrence = new kolab_date_recurrence($object);
+ $first = $recurrence->first_occurrence();
+
+ $this->assertEquals($expected, $first ? $first->format('Y-m-d H:i:s') : '');
+ }
+
+ /**
+ * Data for test_first_occurrence()
+ */
+ function data_first_occurrence()
+ {
+ // TODO: BYYEARDAY, BYWEEKNO, BYSETPOS, WKST
+
+ return array(
+ // non-recurring
+ array(
+ array(), // recurrence data
+ '2017-08-31 11:00:00', // start date
+ '2017-08-31 11:00:00', // expected result
+ ),
+ // daily
+ array(
+ array('FREQ' => 'DAILY', 'INTERVAL' => '1'), // recurrence data
+ '2017-08-31 11:00:00', // start date
+ '2017-08-31 11:00:00', // expected result
+ ),
+ // TODO: this one is not supported by the Calendar UI
+ array(
+ array('FREQ' => 'DAILY', 'INTERVAL' => '1', 'BYMONTH' => 1),
+ '2017-08-31 11:00:00',
+ '2018-01-01 11:00:00',
+ ),
+ // weekly
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '1'),
+ '2017-08-31 11:00:00', // Thursday
+ '2017-08-31 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '1', 'BYDAY' => 'WE'),
+ '2017-08-31 11:00:00', // Thursday
+ '2017-09-06 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '1', 'BYDAY' => 'TH'),
+ '2017-08-31 11:00:00', // Thursday
+ '2017-08-31 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '1', 'BYDAY' => 'FR'),
+ '2017-08-31 11:00:00', // Thursday
+ '2017-09-01 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '2'),
+ '2017-08-31 11:00:00', // Thursday
+ '2017-08-31 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '3', 'BYDAY' => 'WE'),
+ '2017-08-31 11:00:00', // Thursday
+ '2017-09-20 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '1', 'BYDAY' => 'WE', 'COUNT' => 1),
+ '2017-08-31 11:00:00', // Thursday
+ '2017-09-06 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'WEEKLY', 'INTERVAL' => '1', 'BYDAY' => 'WE', 'UNTIL' => '2017-09-01'),
+ '2017-08-31 11:00:00', // Thursday
+ '',
+ ),
+ // monthly
+ array(
+ array('FREQ' => 'MONTHLY', 'INTERVAL' => '1'),
+ '2017-09-08 11:00:00',
+ '2017-09-08 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'MONTHLY', 'INTERVAL' => '1', 'BYMONTHDAY' => '8,9'),
+ '2017-08-31 11:00:00',
+ '2017-09-08 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'MONTHLY', 'INTERVAL' => '1', 'BYMONTHDAY' => '8,9'),
+ '2017-09-08 11:00:00',
+ '2017-09-08 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'MONTHLY', 'INTERVAL' => '1', 'BYDAY' => '1WE'),
+ '2017-08-16 11:00:00',
+ '2017-09-06 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'MONTHLY', 'INTERVAL' => '1', 'BYDAY' => '-1WE'),
+ '2017-08-16 11:00:00',
+ '2017-08-30 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'MONTHLY', 'INTERVAL' => '2'),
+ '2017-09-08 11:00:00',
+ '2017-09-08 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'MONTHLY', 'INTERVAL' => '2', 'BYMONTHDAY' => '8'),
+ '2017-08-31 11:00:00',
+ '2017-09-08 11:00:00', // ??????
+ ),
+ // yearly
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '1'),
+ '2017-08-16 11:00:00',
+ '2017-08-16 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '1', 'BYMONTH' => '8'),
+ '2017-08-16 11:00:00',
+ '2017-08-16 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '1', 'BYDAY' => '-1MO'),
+ '2017-08-16 11:00:00',
+ '2017-12-25 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '1', 'BYMONTH' => '8', 'BYDAY' => '-1MO'),
+ '2017-08-16 11:00:00',
+ '2017-08-28 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '1', 'BYMONTH' => '1', 'BYDAY' => '1MO'),
+ '2017-08-16 11:00:00',
+ '2018-01-01 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '1', 'BYMONTH' => '1,9', 'BYDAY' => '1MO'),
+ '2017-08-16 11:00:00',
+ '2017-09-04 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '2'),
+ '2017-08-16 11:00:00',
+ '2017-08-16 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '2', 'BYMONTH' => '8'),
+ '2017-08-16 11:00:00',
+ '2017-08-16 11:00:00',
+ ),
+ array(
+ array('FREQ' => 'YEARLY', 'INTERVAL' => '2', 'BYDAY' => '-1MO'),
+ '2017-08-16 11:00:00',
+ '2017-12-25 11:00:00',
+ ),
+ // on dates (FIXME: do we really expect the first occurrence to be on the start date?)
+ array(
+ array('RDATE' => array (new DateTime('2017-08-10 11:00:00 Europe/Warsaw'))),
+ '2017-08-01 11:00:00',
+ '2017-08-01 11:00:00',
+ ),
+ );
+ }
+
+}
+
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 11, 8:41 PM (12 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18861321
Default Alt Text
D536.1775940095.diff (25 KB)
Attached To
Mode
D536: Align event start date with the first occurrence
Attached
Detach File
Event Timeline