Merge branch 'JJJollyjim/davical-refactor-freebusy'

This commit is contained in:
Andrew Ruthven 2019-01-04 22:37:25 +13:00
commit bcdf59ae2e
18 changed files with 224 additions and 35 deletions

View File

@ -29,6 +29,7 @@ test:
artifacts: artifacts:
paths: paths:
- testing/report.xml - testing/report.xml
- apache2_log/*
reports: reports:
junit: testing/report.xml junit: testing/report.xml
when: when:
@ -68,4 +69,5 @@ test:
- apache2ctl start - apache2ctl start
- useradd testrunner - useradd testrunner
- cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling" ./run_regressions.sh all x' - cd testing && su testrunner -c 'IS_CI=yes ALLSUITES="regression-suite binding carddav scheduling" ./run_regressions.sh all x'
after_script:
- cp -r /var/log/apache2 apache2_log

View File

@ -167,8 +167,8 @@ CREATE TABLE calendar_item (
completed TIMESTAMP WITH TIME ZONE, completed TIMESTAMP WITH TIME ZONE,
dav_id INT8 UNIQUE, dav_id INT8 UNIQUE,
collection_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE, collection_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE,
first_instance_start TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL, first_instance_start TIMESTAMP WITH TIME ZONE DEFAULT NULL,
last_instance_end TIMESTAMP WITHOUT TIME ZONE DEFAULT NULL, last_instance_end TIMESTAMP WITH TIME ZONE DEFAULT NULL,
-- Cascade updates / deletes from the caldav_data table -- Cascade updates / deletes from the caldav_data table
CONSTRAINT caldav_exists FOREIGN KEY ( user_no, dav_name ) CONSTRAINT caldav_exists FOREIGN KEY ( user_no, dav_name )
@ -489,4 +489,4 @@ CREATE SEQUENCE metrics_count_delticket;
CREATE SEQUENCE metrics_count_bind; CREATE SEQUENCE metrics_count_bind;
CREATE SEQUENCE metrics_count_unknown; CREATE SEQUENCE metrics_count_unknown;
SELECT new_db_revision(1,3,2, 'Luty' ); SELECT new_db_revision(1,3,3, 'Marzec' );

23
dba/patches/1.3.3.sql Normal file
View File

@ -0,0 +1,23 @@
-- Notable enhancement: change first_instance_start and last_instance_end to be timezone-aware
BEGIN;
SELECT check_db_revision(1,3,2);
ALTER TABLE calendar_item
ALTER COLUMN first_instance_start
TYPE TIMESTAMP WITH TIME ZONE
-- I don't believe the column has ever been populated, but if it has, we want
-- to throw away that data anyway because we don't know its timezone
USING NULL::timestamptz;
ALTER TABLE calendar_item
ALTER COLUMN last_instance_end
TYPE TIMESTAMP WITH TIME ZONE
USING NULL::timestamptz; -- As above
-- http://blogs.transparent.com/polish/names-of-the-months-and-their-meaning/
SELECT new_db_revision(1,3,3, 'Marzec' );
COMMIT;
ROLLBACK;

View File

@ -1295,6 +1295,15 @@ EOQRY;
} }
/**
* Returns the name of the timezone for this collection, or the collection containing this resource
*/
function timezone_name() {
if ( !isset($this->collection) ) $this->FetchCollection();
return $this->collection->timezone;
}
/** /**
* Returns the database row for this resource * Returns the database row for this resource
*/ */

View File

@ -305,6 +305,18 @@ class RepeatRuleDateTime extends DateTime {
return $this; return $this;
} }
public static function withFallbackTzid( $date, $fallback_tzid ) {
// Floating times or dates (either VALUE=DATE or with no TZID) can default to the collection's tzid, if one is set
if ($date->GetParameterValue('VALUE') == 'DATE' && isset($fallback_tzid)) {
return new RepeatRuleDateTime($date->Value()."T000000", new RepeatRuleTimeZone($fallback_tzid));
} else if ($date->GetParameterValue('TZID') === null && isset($fallback_tzid)) {
return new RepeatRuleDateTime($date->Value(), new RepeatRuleTimeZone($fallback_tzid));
} else {
return new RepeatRuleDateTime($date);
}
}
public function __toString() { public function __toString() {
return (string)parent::format(self::$Format) . ' ' . parent::getTimeZone()->getName(); return (string)parent::format(self::$Format) . ' ' . parent::getTimeZone()->getName();
@ -1144,7 +1156,7 @@ function rdate_expand( $dtstart, $property, $component, $range_end = null, $is_d
* *
* @return array An array keyed on the UTC dates, referring to the component * @return array An array keyed on the UTC dates, referring to the component
*/ */
function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=null, $return_floating_times=false ) { function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=null, $return_floating_times=false, $fallback_tzid=null ) {
$expansion = array(); $expansion = array();
$recur = $component->GetProperty($property); $recur = $component->GetProperty($property);
@ -1153,7 +1165,7 @@ function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=nul
$this_start = $component->GetProperty('DTSTART'); $this_start = $component->GetProperty('DTSTART');
if ( isset($this_start) ) { if ( isset($this_start) ) {
$this_start = new RepeatRuleDateTime($this_start); $this_start = RepeatRuleDateTime::withFallbackTzid($this_start, $fallback_tzid);
} }
else { else {
$this_start = clone($dtstart); $this_start = clone($dtstart);
@ -1406,12 +1418,12 @@ function expand_event_instances( vComponent $vResource, $range_start = null, $ra
* @throws Exception (1) When DTSTART is not present but the RFC says MUST and (2) when we get an unsupported component * @throws Exception (1) When DTSTART is not present but the RFC says MUST and (2) when we get an unsupported component
* @return RepeatRuleDateRange * @return RepeatRuleDateRange
*/ */
function getComponentRange(vComponent $comp) { function getComponentRange(vComponent $comp, string $fallback_tzid = null) {
$dtstart_prop = $comp->GetProperty('DTSTART'); $dtstart_prop = $comp->GetProperty('DTSTART');
$duration_prop = $comp->GetProperty('DURATION'); $duration_prop = $comp->GetProperty('DURATION');
if ( isset($duration_prop) ) { if ( isset($duration_prop) ) {
if ( !isset($dtstart_prop) ) throw new Exception('Invalid '.$comp->GetType().' containing DURATION without DTSTART', 0); if ( !isset($dtstart_prop) ) throw new Exception('Invalid '.$comp->GetType().' containing DURATION without DTSTART', 0);
$dtstart = new RepeatRuleDateTime($dtstart_prop); $dtstart = RepeatRuleDateTime::withFallbackTzid($dtstart_prop, $fallback_tzid);
$dtend = clone($dtstart); $dtend = clone($dtstart);
$dtend->modify(new Rfc5545Duration($duration_prop->Value())); $dtend->modify(new Rfc5545Duration($duration_prop->Value()));
} }
@ -1435,17 +1447,17 @@ function getComponentRange(vComponent $comp) {
} }
if ( isset($dtstart_prop) ) if ( isset($dtstart_prop) )
$dtstart = new RepeatRuleDateTime($dtstart_prop); $dtstart = RepeatRuleDateTime::withFallbackTzid($dtstart_prop, $fallback_tzid);
else else
$dtstart = null; $dtstart = null;
if ( isset($dtend_prop) ) if ( isset($dtend_prop) )
$dtend = new RepeatRuleDateTime($dtend_prop); $dtend = RepeatRuleDateTime::withFallbackTzid($dtend_prop, $fallback_tzid);
else else
$dtend = null; $dtend = null;
if ( isset($completed_prop) ) { if ( isset($completed_prop) ) {
$completed = new RepeatRuleDateTime($completed_prop); $completed = RepeatRuleDateTime::withFallbackTzid($completed_prop, $fallback_tzid);
if ( !isset($dtstart) || (isset($dtstart) && $completed < $dtstart) ) $dtstart = $completed; if ( !isset($dtstart) || (isset($dtstart) && $completed < $dtstart) ) $dtstart = $completed;
if ( !isset($dtend) || (isset($dtend) && $completed > $dtend) ) $dtend = $completed; if ( !isset($dtend) || (isset($dtend) && $completed > $dtend) ) $dtend = $completed;
} }
@ -1461,7 +1473,7 @@ function getComponentRange(vComponent $comp) {
* @param object $vResource A vComponent which is a VCALENDAR containing components needing expansion * @param object $vResource A vComponent which is a VCALENDAR containing components needing expansion
* @return RepeatRuleDateRange Representing the range of time covered by the event. * @return RepeatRuleDateRange Representing the range of time covered by the event.
*/ */
function getVCalendarRange( $vResource ) { function getVCalendarRange( $vResource, string $fallback_tzid = null ) {
$components = $vResource->GetComponents(); $components = $vResource->GetComponents();
$dtstart = null; $dtstart = null;
@ -1471,7 +1483,7 @@ function getVCalendarRange( $vResource ) {
$has_repeats = false; $has_repeats = false;
foreach( $components AS $k => $comp ) { foreach( $components AS $k => $comp ) {
if ( $comp->GetType() == 'VTIMEZONE' ) continue; if ( $comp->GetType() == 'VTIMEZONE' ) continue;
$range = getComponentRange($comp); $range = getComponentRange($comp, $fallback_tzid);
$dtstart = $range->from; $dtstart = $range->from;
if ( !isset($dtstart) ) continue; if ( !isset($dtstart) ) continue;
$duration = $range->getDuration(); $duration = $range->getDuration();
@ -1490,7 +1502,7 @@ function getVCalendarRange( $vResource ) {
$range_end = new RepeatRuleDateTime(); $range_end = new RepeatRuleDateTime();
$range_end->modify('+150 years'); $range_end->modify('+150 years');
} }
$instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end); $instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end, null, false, $fallback_tzid);
$instances += rdate_expand($dtstart, 'RDATE', $comp, $range_end); $instances += rdate_expand($dtstart, 'RDATE', $comp, $range_end);
foreach ( rdate_expand($dtstart, 'EXDATE', $comp, $range_end) AS $k => $v ) { foreach ( rdate_expand($dtstart, 'EXDATE', $comp, $range_end) AS $k => $v ) {
unset($instances[$k]); unset($instances[$k]);

View File

@ -219,16 +219,23 @@ class WritableCollection extends DAVResource {
$calitem_params[':due'] = $first->GetPValue('DUE'); $calitem_params[':due'] = $first->GetPValue('DUE');
$calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
$calitem_params[':status'] = $first->GetPValue('STATUS'); $calitem_params[':status'] = $first->GetPValue('STATUS');
$range = getVCalendarRange($vcal, $this->timezone_name());
$calitem_params[':first_instance_start'] = isset($range->from) ? $range->from->UTC() : null;
$calitem_params[':last_instance_end'] = isset($range->until) ? $range->until->UTC() : null;
if ( $create_resource ) { if ( $create_resource ) {
$sql = <<<EOSQL $sql = <<<EOSQL
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
dtstart, dtend, summary, location, class, transp, dtstart, dtend, summary, location, class, transp,
description, rrule, tz_id, last_modified, url, priority, description, rrule, tz_id, last_modified, url, priority,
created, due, percent_complete, status, collection_id ) created, due, percent_complete, status, collection_id,
first_instance_start, last_instance_end )
VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp, VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp,
:dtstart, $dtend, :summary, :location, :class, :transp, :dtstart, $dtend, :summary, :location, :class, :transp,
:description, :rrule, :tzid, :modified, :url, :priority, :description, :rrule, :tzid, :modified, :url, :priority,
:created, :due, :percent_complete, :status, $collection_id ) :created, :due, :percent_complete, :status, $collection_id,
:first_instance_start, :last_instance_end)
EOSQL; EOSQL;
$sync_change = 201; $sync_change = 201;
} }
@ -237,7 +244,8 @@ EOSQL;
UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp, UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location, class=:class, transp=:transp, dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location, class=:class, transp=:transp,
description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority, description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
created=:created, due=:due, percent_complete=:percent_complete, status=:status created=:created, due=:due, percent_complete=:percent_complete, status=:status,
first_instance_start=:first_instance_start, last_instance_end=:last_instance_end
WHERE user_no=:user_no AND dav_name=:dav_name WHERE user_no=:user_no AND dav_name=:dav_name
EOSQL; EOSQL;
$sync_change = 200; $sync_change = 200;

View File

@ -1215,6 +1215,10 @@ EOSQL;
$calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
$calitem_params[':status'] = $first->GetPValue('STATUS'); $calitem_params[':status'] = $first->GetPValue('STATUS');
// Intentionally not populating first_instance_start and last_instance_end
// here, As they may depend on the default tzid of the collection, which as
// I understand it hasn't yet been set
if ( $inserting ) { if ( $inserting ) {
$created = $first->GetPValue('CREATED'); $created = $first->GetPValue('CREATED');
if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z'; if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z';
@ -1596,7 +1600,11 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
$calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
$calitem_params[':status'] = $first->GetPValue('STATUS'); $calitem_params[':status'] = $first->GetPValue('STATUS');
// force re-render the object (to get the same representation for all attendiees) $range = getVCalendarRange($vcal, $resource->timezone_name());
$calitem_params[':first_instance_start'] = isset($range->from) ? $range->from->UTC() : null;
$calitem_params[':last_instance_end'] = isset($range->until) ? $range->until->UTC() : null;
// force re-render the object (to get the same representation for all attendees)
$vcal->Render(null, true); $vcal->Render(null, true);
if ( !$collection->IsSchedulingCollection() ) { if ( !$collection->IsSchedulingCollection() ) {
@ -1632,11 +1640,13 @@ function write_resource( DAVResource $resource, $caldav_data, DAVResource $colle
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
dtstart, dtend, summary, location, class, transp, dtstart, dtend, summary, location, class, transp,
description, rrule, tz_id, last_modified, url, priority, description, rrule, tz_id, last_modified, url, priority,
created, due, percent_complete, status, collection_id ) created, due, percent_complete, status, collection_id,
first_instance_start, last_instance_end )
VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp, VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp,
:dtstart, $dtend, :summary, :location, :class, :transp, :dtstart, $dtend, :summary, :location, :class, :transp,
:description, :rrule, :tzid, :modified, :url, :priority, :description, :rrule, :tzid, :modified, :url, :priority,
:created, :due, :percent_complete, :status, :collection_id ) :created, :due, :percent_complete, :status, $collection_id,
:first_instance_start, :last_instance_end)
EOSQL; EOSQL;
$sync_change = 201; $sync_change = 201;
$calitem_params[':collection_id'] = $collection_id; $calitem_params[':collection_id'] = $collection_id;
@ -1650,7 +1660,8 @@ UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location, dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location,
class=:class, transp=:transp, description=:description, rrule=:rrule, class=:class, transp=:transp, description=:description, rrule=:rrule,
tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
due=:due, percent_complete=:percent_complete, status=:status due=:due, percent_complete=:percent_complete, status=:status,
first_instance_start=:first_instance_start, last_instance_end=:last_instance_end
WHERE dav_id=:dav_id WHERE dav_id=:dav_id
EOSQL; EOSQL;
$sync_change = 200; $sync_change = 200;

View File

@ -20,7 +20,10 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null
} }
$params = array( ':path_match' => $path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC() ); $params = array( ':path_match' => $path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC() );
$where = ' WHERE caldav_data.dav_name ~ :path_match '; $where = ' WHERE caldav_data.dav_name ~ :path_match ';
$where .= 'AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end) '; $where .= "AND (";
$where .= " (calendar_item.first_instance_start <= :end AND (:start <= calendar_item.last_instance_end OR calendar_item.last_instance_end IS NULL)) ";
$where .= " OR (calendar_item.first_instance_start IS NULL AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end)) ";
$where .= ") ";
$where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VTODO' ) "; $where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VTODO' ) ";
$where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) "; $where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) ";
$where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) "; $where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) ";
@ -66,13 +69,7 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null
} }
// Floating dtstarts (either VALUE=DATE or with no TZID) can default to the collection's tzid, if one is set // Floating dtstarts (either VALUE=DATE or with no TZID) can default to the collection's tzid, if one is set
if ($start_date->GetParameterValue('VALUE') == 'DATE' && isset($collection_tzid)) { $start_date = RepeatRuleDateTime::withFallbackTzid( $start_date, $collection_tzid );
$start_date = new RepeatRuleDateTime($start_date->Value()."T000000", new RepeatRuleTimeZone($collection_tzid));
} else if ($start_date->GetParameterValue('TZID') === null && isset($collection_tzid)) {
$start_date = new RepeatRuleDateTime($start_date->Value(), new RepeatRuleTimeZone($collection_tzid));
} else {
$start_date = new RepeatRuleDateTime($start_date);
}
$duration = $v->GetProperty('DURATION'); $duration = $v->GetProperty('DURATION');
$duration = ( !isset($duration) ? 'P1D' : $duration->Value()); $duration = ( !isset($duration) ? 'P1D' : $duration->Value());

View File

@ -0,0 +1,119 @@
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '/usr/share/awl/inc' . PATH_SEPARATOR . 'inc');
require_once('RRule.php');
require_once('vCalendar.php');
use PHPUnit\Framework\TestCase;
final class RangeTest extends TestCase
{
public function testGetVCalendarRange() {
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Asia/Baku
BEGIN:STANDARD
TZOFFSETFROM:+0400
TZOFFSETTO:+0400
TZNAME:+04
DTSTART:19700101T000000
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
CREATED:20181218T045608Z
LAST-MODIFIED:20181218T045951Z
DTSTAMP:20181218T045951Z
UID:8eeee169-420f-4645-9546-9ea8293a1c6d
SUMMARY:Blep
RRULE:FREQ=DAILY;UNTIL=20190102T050000Z;BYDAY=MO,TU,WE,TH,FR
DTSTART;TZID=Asia/Baku:20181226T090000
DTEND;TZID=Asia/Baku:20181226T112000
TRANSP:OPAQUE
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal);
self::assertEquals("20181226T050000Z", (string) $range->from->UTC());
self::assertEquals("20190102T072000Z", (string) $range->until->UTC());
// TZ is specified in the event, so this should be unaffected by passing in a fallback timezone:
$range = getVCalendarRange($cal, "Asia/Baku");
self::assertEquals("20181226T050000Z", (string) $range->from->UTC());
self::assertEquals("20190102T072000Z", (string) $range->until->UTC());
}
public function testGetVCalendarRangeTwoDayAllDay() {
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20181218T052603Z
LAST-MODIFIED:20181218T052734Z
DTSTAMP:20181218T052734Z
UID:7e76efc0-b1ed-4b68-b0ed-9e34889c30bd
SUMMARY:New Event
RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=TU
DTSTART;VALUE=DATE:20181225
DTEND;VALUE=DATE:20181227
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal, "Asia/Baku");
self::assertEquals("20181224T200000Z", (string) $range->from->UTC());
self::assertEquals("20190109T200000Z", (string) $range->until->UTC());
}
public function testGetVCalendarRangeFloating() {
// When interpreted as being in Greece, this event crosses the daylight savings boundary!
// TODO deal with how that affects all-day events...
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20181218T052603Z
LAST-MODIFIED:20181218T052734Z
DTSTAMP:20181218T052734Z
UID:7e76efc0-b1ed-4b68-b0ed-9e34889c30bd
SUMMARY:New Event
RRULE:FREQ=MONTHLY;COUNT=4;INTERVAL=2;BYMONTHDAY=10
DTSTART:20180411T140000
DTEND:20180411T150000
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal, "Europe/Athens");
self::assertEquals("20180411T110000Z", (string) $range->from->UTC());
self::assertEquals("20181210T130000Z", (string) $range->until->UTC());
}
public function testGetVCalendarRangeAllDayAcrossDST() {
// When interpreted as being in Greece, this event crosses the daylight savings boundary!
$cal = new vCalendar("BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VEVENT
CREATED:20181218T052603Z
LAST-MODIFIED:20181218T052734Z
DTSTAMP:20181218T052734Z
UID:7e76efc0-b1ed-4b68-b0ed-9e34889c30bd
SUMMARY:New Event
RRULE:FREQ=MONTHLY;COUNT=5;INTERVAL=2;BYMONTHDAY=10
DTSTART;VALUE=DATE:20180410
DTEND;VALUE=DATE:20180411
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR");
$range = getVCalendarRange($cal, "Europe/Athens");
self::assertEquals("20180409T210000Z", (string) $range->from->UTC());
// This is correctly at a different UTC time-of-day to the original, since DST has changed
self::assertEquals("20181210T220000Z", (string) $range->until->UTC());
}
}

View File

@ -1,6 +1,6 @@
HTTP/1.1 200 OK HTTP/1.1 200 OK
Date: Dow, 01 Jan 2000 00:00:00 GMT Date: Dow, 01 Jan 2000 00:00:00 GMT
Content-Length: 14720 Content-Length: 14764
Content-Type: text/calendar;charset=UTF-8 Content-Type: text/calendar;charset=UTF-8
BEGIN:VCALENDAR BEGIN:VCALENDAR
@ -11,6 +11,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:20060930T110000Z DTSTART:20060930T110000Z
DTEND:correct DTEND:correct
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20061001T210000Z/20061001T220000Z FREEBUSY:20061001T210000Z/20061001T220000Z
FREEBUSY:20061002T210000Z/20061002T220000Z FREEBUSY:20061002T210000Z/20061002T220000Z
FREEBUSY:20061003T210000Z/20061003T220000Z FREEBUSY:20061003T210000Z/20061003T220000Z

View File

@ -1,6 +1,6 @@
HTTP/1.1 200 OK HTTP/1.1 200 OK
Date: Dow, 01 Jan 2000 00:00:00 GMT Date: Dow, 01 Jan 2000 00:00:00 GMT
Content-Length: 14720 Content-Length: 14764
Content-Type: text/calendar;charset=UTF-8 Content-Type: text/calendar;charset=UTF-8
BEGIN:VCALENDAR BEGIN:VCALENDAR
@ -11,6 +11,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:20060930T110000Z DTSTART:20060930T110000Z
DTEND:20070630T115959Z DTEND:20070630T115959Z
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20061001T210000Z/20061001T220000Z FREEBUSY:20061001T210000Z/20061001T220000Z
FREEBUSY:20061002T210000Z/20061002T220000Z FREEBUSY:20061002T210000Z/20061002T220000Z
FREEBUSY:20061003T210000Z/20061003T220000Z FREEBUSY:20061003T210000Z/20061003T220000Z

View File

@ -1,6 +1,6 @@
HTTP/1.1 200 OK HTTP/1.1 200 OK
Date: Dow, 01 Jan 2000 00:00:00 GMT Date: Dow, 01 Jan 2000 00:00:00 GMT
Content-Length: 14720 Content-Length: 14764
Content-Type: text/calendar;charset=UTF-8 Content-Type: text/calendar;charset=UTF-8
BEGIN:VCALENDAR BEGIN:VCALENDAR
@ -11,6 +11,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:20060930T110000Z DTSTART:20060930T110000Z
DTEND:20070630T115959Z DTEND:20070630T115959Z
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20061001T210000Z/20061001T220000Z FREEBUSY:20061001T210000Z/20061001T220000Z
FREEBUSY:20061002T210000Z/20061002T220000Z FREEBUSY:20061002T210000Z/20061002T220000Z
FREEBUSY:20061003T210000Z/20061003T220000Z FREEBUSY:20061003T210000Z/20061003T220000Z

View File

@ -14,6 +14,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:20081021T110000Z DTSTART:20081021T110000Z
DTEND:20081106T110000Z DTEND:20081106T110000Z
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20081021T180000Z/20081022T040000Z FREEBUSY:20081021T180000Z/20081022T040000Z
FREEBUSY:20081022T180000Z/20081023T040000Z FREEBUSY:20081022T180000Z/20081023T040000Z
FREEBUSY:20081022T184500Z/20081022T193000Z FREEBUSY:20081022T184500Z/20081022T193000Z

View File

@ -1,6 +1,6 @@
HTTP/1.1 200 OK HTTP/1.1 200 OK
Date: Dow, 01 Jan 2000 00:00:00 GMT Date: Dow, 01 Jan 2000 00:00:00 GMT
Content-Length: 14720 Content-Length: 14764
Content-Type: text/calendar;charset=UTF-8 Content-Type: text/calendar;charset=UTF-8
BEGIN:VCALENDAR BEGIN:VCALENDAR
@ -11,6 +11,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:20060930T110000Z DTSTART:20060930T110000Z
DTEND:20070630T115959Z DTEND:20070630T115959Z
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20061001T210000Z/20061001T220000Z FREEBUSY:20061001T210000Z/20061001T220000Z
FREEBUSY:20061002T210000Z/20061002T220000Z FREEBUSY:20061002T210000Z/20061002T220000Z
FREEBUSY:20061003T210000Z/20061003T220000Z FREEBUSY:20061003T210000Z/20061003T220000Z

View File

@ -14,6 +14,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:correct DTSTART:correct
DTEND:correct DTEND:correct
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20060930T210000Z/20060930T220000Z FREEBUSY:20060930T210000Z/20060930T220000Z
FREEBUSY:20061001T210000Z/20061001T220000Z FREEBUSY:20061001T210000Z/20061001T220000Z
FREEBUSY:20061002T210000Z/20061002T220000Z FREEBUSY:20061002T210000Z/20061002T220000Z

View File

@ -1,6 +1,6 @@
HTTP/1.1 200 OK HTTP/1.1 200 OK
Date: Dow, 01 Jan 2000 00:00:00 GMT Date: Dow, 01 Jan 2000 00:00:00 GMT
Content-Length: 14720 Content-Length: 14764
Content-Type: text/calendar;charset=UTF-8 Content-Type: text/calendar;charset=UTF-8
BEGIN:VCALENDAR BEGIN:VCALENDAR
@ -11,6 +11,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:20060930T110000Z DTSTART:20060930T110000Z
DTEND:20070630T115959Z DTEND:20070630T115959Z
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20061001T210000Z/20061001T220000Z FREEBUSY:20061001T210000Z/20061001T220000Z
FREEBUSY:20061002T210000Z/20061002T220000Z FREEBUSY:20061002T210000Z/20061002T220000Z
FREEBUSY:20061003T210000Z/20061003T220000Z FREEBUSY:20061003T210000Z/20061003T220000Z

View File

@ -14,6 +14,7 @@ BEGIN:VFREEBUSY
DTSTAMP:yyyymmddThhmmssZ DTSTAMP:yyyymmddThhmmssZ
DTSTART:20080730T110000Z DTSTART:20080730T110000Z
DTEND:20080803T110000Z DTEND:20080803T110000Z
FREEBUSY:20031231T230000Z/20100217T000000Z
FREEBUSY:20080730T190000Z/20080731T050000Z FREEBUSY:20080730T190000Z/20080731T050000Z
FREEBUSY:20080730T194500Z/20080730T203000Z FREEBUSY:20080730T194500Z/20080730T203000Z
FREEBUSY:20080730T210000Z/20080730T220000Z FREEBUSY:20080730T210000Z/20080730T220000Z

View File

@ -1,4 +1,4 @@
The database is version XX currently at revision 1.3.2. The database is version XX currently at revision 1.3.3.
No patches were applied. No patches were applied.
Supported locales updated. Supported locales updated.
Updated view: dav_principal.sql applied. Updated view: dav_principal.sql applied.