Handle default timezones in getVCalendarRange

Also includes some PHPUnit-based tests for this function!
This commit is contained in:
Jamie McClymont 2019-01-03 13:24:50 +13:00
parent 0c006b5c7c
commit cf7de16e59
2 changed files with 129 additions and 10 deletions

View File

@ -1156,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
*/
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();
$recur = $component->GetProperty($property);
@ -1165,7 +1165,7 @@ function rrule_expand( $dtstart, $property, $component, $range_end, $is_date=nul
$this_start = $component->GetProperty('DTSTART');
if ( isset($this_start) ) {
$this_start = new RepeatRuleDateTime($this_start);
$this_start = RepeatRuleDateTime::withFallbackTzid($this_start, $fallback_tzid);
}
else {
$this_start = clone($dtstart);
@ -1418,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
* @return RepeatRuleDateRange
*/
function getComponentRange(vComponent $comp) {
function getComponentRange(vComponent $comp, string $fallback_tzid = null) {
$dtstart_prop = $comp->GetProperty('DTSTART');
$duration_prop = $comp->GetProperty('DURATION');
if ( isset($duration_prop) ) {
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->modify(new Rfc5545Duration($duration_prop->Value()));
}
@ -1447,17 +1447,17 @@ function getComponentRange(vComponent $comp) {
}
if ( isset($dtstart_prop) )
$dtstart = new RepeatRuleDateTime($dtstart_prop);
$dtstart = RepeatRuleDateTime::withFallbackTzid($dtstart_prop, $fallback_tzid);
else
$dtstart = null;
if ( isset($dtend_prop) )
$dtend = new RepeatRuleDateTime($dtend_prop);
$dtend = RepeatRuleDateTime::withFallbackTzid($dtend_prop, $fallback_tzid);
else
$dtend = null;
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($dtend) || (isset($dtend) && $completed > $dtend) ) $dtend = $completed;
}
@ -1473,7 +1473,7 @@ function getComponentRange(vComponent $comp) {
* @param object $vResource A vComponent which is a VCALENDAR containing components needing expansion
* @return RepeatRuleDateRange Representing the range of time covered by the event.
*/
function getVCalendarRange( $vResource ) {
function getVCalendarRange( $vResource, string $fallback_tzid = null ) {
$components = $vResource->GetComponents();
$dtstart = null;
@ -1483,7 +1483,7 @@ function getVCalendarRange( $vResource ) {
$has_repeats = false;
foreach( $components AS $k => $comp ) {
if ( $comp->GetType() == 'VTIMEZONE' ) continue;
$range = getComponentRange($comp);
$range = getComponentRange($comp, $fallback_tzid);
$dtstart = $range->from;
if ( !isset($dtstart) ) continue;
$duration = $range->getDuration();
@ -1502,7 +1502,7 @@ function getVCalendarRange( $vResource ) {
$range_end = new RepeatRuleDateTime();
$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);
foreach ( rdate_expand($dtstart, 'EXDATE', $comp, $range_end) AS $k => $v ) {
unset($instances[$k]);

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());
}
}