From b15fcb3f77325a981e67a69916e50d0c192038e2 Mon Sep 17 00:00:00 2001 From: Andrew McMillan Date: Sun, 26 Dec 2010 17:41:52 +1300 Subject: [PATCH] Refactoring of RRule-v2, which incidentally fixes a bug! VALUE=DATE:YYYYMMDD dates should now work better, and all date parsing is much more robust. Signed-off-by: Andrew McMillan --- inc/RRule-v2.php | 108 +++++++++++++----- inc/freebusy-functions.php | 9 +- .../0889-POST-freebusy.result | 2 - 3 files changed, 84 insertions(+), 35 deletions(-) diff --git a/inc/RRule-v2.php b/inc/RRule-v2.php index 0dd9b03e..b42605eb 100644 --- a/inc/RRule-v2.php +++ b/inc/RRule-v2.php @@ -30,8 +30,8 @@ $rrule_expand_limit = array( $rrule_day_numbers = array( 'SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6 ); -$GLOBALS['debug_rrule'] = false; -// $GLOBALS['debug_rrule'] = true; +define( 'DEBUG_RRULE', false ); +// define( 'DEBUG_RRULE', true); /** * Wrap the DateTimeZone class to allow parsing some iCalendar TZID strangenesses @@ -87,10 +87,15 @@ class RepeatRuleDateTime extends DateTime { public function __construct($date = null, $dtz = null) { if ( !isset(self::$UTCzone) ) self::$UTCzone = new RepeatRuleTimeZone('UTC'); $this->is_date = false; - if ( !isset($date) ) return; + if ( !isset($date) ) { + $date = date('Ymd\THis'); + // Floating + $dtz = self::$UTCzone; + $this->tzid = null; + } - if ( is_object($date) && (gettype($date) == 'vProperty' || gettype($date) == 'iCalProp' ) ) { - $tzid = $date->GetProperty('TZID'); + if ( is_object($date) && method_exists($date,'GetParameterValue') ) { + $tzid = $date->GetParameterValue('TZID'); $actual_date = $date->Value(); if ( isset($tzid) ) { $dtz = new RepeatRuleTimeZone($tzid); @@ -98,7 +103,7 @@ class RepeatRuleDateTime extends DateTime { } else { $dtz = self::$UTCzone; - if ( substr($actual_date,strlen($actual_date),1) == 'Z' ) { + if ( substr($actual_date,-1) == 'Z' ) { $this->tzid = 'UTC'; $actual_date = substr($actual_date, 0, strlen($actual_date) - 1); } @@ -108,12 +113,14 @@ class RepeatRuleDateTime extends DateTime { $this->is_date; } $date = $actual_date; -// printf( "Date%s value with timezone: %s in %s\n", ($this->is_date?"":"Time"), $date, $this->tzid ); + if ( DEBUG_RRULE ) printf( "Date%s property%s: %s%s\n", ($this->is_date ? "" : "Time"), + (isset($this->tzid) ? ' with timezone' : ''), $date, + (isset($this->tzid) ? ' in '.$this->tzid : '') ); } - elseif (preg_match('/;?(?:TZID=([^:;]+).*):(\d{8}(T\d{6})?)(Z)?/', $date, $matches) ) { + elseif (preg_match('/;TZID= ([^:;]+) (?: ;.* )? : ( \d{8} (?:T\d{6})? ) (Z)?/x', $date, $matches) ) { $date = $matches[2]; $this->is_date = (strlen($date) == 8); - if ( isset($matches[4]) && $matches[4] == 'Z' ) { + if ( isset($matches[3]) && $matches[3] == 'Z' ) { $dtz = self::$UTCzone; $this->tzid = 'UTC'; } @@ -125,16 +132,18 @@ class RepeatRuleDateTime extends DateTime { $dtz = self::$UTCzone; $this->tzid = null; } -// printf( "Date%s value with timezone: %s in %s\n", ($this->is_date?"":"Time"), $date, $this->tzid ); + if ( DEBUG_RRULE ) printf( "Date%s property%s: %s%s\n", ($this->is_date ? "" : "Time"), + (isset($this->tzid) ? ' with timezone' : ''), $date, + (isset($this->tzid) ? ' in '.$this->tzid : '') ); } elseif ( ( $dtz === null || $dtz == '' ) - && preg_match('{;?VALUE=DATE (?:;[^:]+) : ((?:[12]\d{3}) (?:0[1-9]|1[012]) (?:0[1-9]|[12]\d|3[01]Z?) )$}x', $date, $matches) ) { + && preg_match('{;VALUE=DATE (?:;[^:]+) : ((?:[12]\d{3}) (?:0[1-9]|1[012]) (?:0[1-9]|[12]\d|3[01]Z?) )$}x', $date, $matches) ) { $this->is_date = true; $date = $matches[1]; // Floating $dtz = self::$UTCzone; $this->tzid = null; -// printf( "Date value: %s\n", $date ); + if ( DEBUG_RRULE ) printf( "Floating Date value: %s\n", $date ); } elseif ( $dtz === null || $dtz == '' ) { $dtz = self::$UTCzone; @@ -143,14 +152,18 @@ class RepeatRuleDateTime extends DateTime { $this->tzid = ( $matches[3] == 'Z' ? 'UTC' : null ); } $this->is_date = (strlen($date) == 8 ); -// printf( "Date%s value with timezone: %s in %s\n", ($this->is_date?"":"Time"), $date, $this->tzid ); + if ( DEBUG_RRULE ) printf( "Date%s value with timezone: %s in %s\n", ($this->is_date?"":"Time"), $date, $this->tzid ); } elseif ( is_string($dtz) ) { $dtz = new RepeatRuleTimeZone($dtz); $this->tzid = $dtz->tzid(); + $type = gettype($date); + if ( DEBUG_RRULE ) printf( "Date%s $type with timezone: %s in %s\n", ($this->is_date?"":"Time"), $date, $this->tzid ); } else { $this->tzid = $dtz->getName(); + $type = gettype($date); + if ( DEBUG_RRULE ) printf( "Date%s $type with timezone: %s in %s\n", ($this->is_date?"":"Time"), $date, $this->tzid ); } parent::__construct($date, $dtz); @@ -321,7 +334,7 @@ class RepeatRule { public function __construct( $basedate, $rrule ) { $this->base = ( is_object($basedate) ? $basedate : new RepeatRuleDateTime($basedate) ); - if ( $GLOBALS['debug_rrule'] ) { + if ( DEBUG_RRULE ) { printf( "Constructing RRULE based on: '%s', rrule: '%s'\n", $basedate, $rrule ); } @@ -356,7 +369,7 @@ class RepeatRule { /** need to handle the error, but FREQ is mandatory so unlikely */ } $this->frequency_string = sprintf('+%d %s', $this->interval, $this->freq_name ); - if ( $GLOBALS['debug_rrule'] ) printf( "Frequency modify string is: '%s', base is: '%s'\n", $this->frequency_string, $this->base->format('c') ); + if ( DEBUG_RRULE ) printf( "Frequency modify string is: '%s', base is: '%s'\n", $this->frequency_string, $this->base->format('c') ); $this->Start(); } @@ -389,7 +402,7 @@ class RepeatRule { if ( !$this->valid() ) return null; if ( !isset($this->instances[$this->position]) ) $this->GetMoreInstances(); if ( !$this->valid() ) return null; - if ( $GLOBALS['debug_rrule'] ) printf( "Returning date from position %d: %s (%s)\n", $this->position, $this->instances[$this->position]->format('c'), $this->instances[$this->position]->UTC() ); + if ( DEBUG_RRULE ) printf( "Returning date from position %d: %s (%s)\n", $this->position, $this->instances[$this->position]->format('c'), $this->instances[$this->position]->UTC() ); return $this->instances[$this->position]; } @@ -424,7 +437,7 @@ class RepeatRule { else { $this->current_base->modify( $this->frequency_string ); } - if ( $GLOBALS['debug_rrule'] ) printf( "Getting more instances from: '%s' - %d\n", $this->current_base->format('c'), count($this->instances) ); + if ( DEBUG_RRULE ) printf( "Getting more instances from: '%s' - %d\n", $this->current_base->format('c'), count($this->instances) ); $this->current_set = array( clone($this->current_base) ); foreach( $rrule_expand_limit[$this->freq] AS $bytype => $action ) { if ( isset($this->{$bytype}) ) $this->{$action.'_'.$bytype}(); @@ -434,7 +447,7 @@ class RepeatRule { if ( isset($this->bysetpos) ) $this->limit_bysetpos(); $position = count($this->instances) - 1; - if ( $GLOBALS['debug_rrule'] ) printf( "Inserting %d from current_set into position %d\n", count($this->current_set), $position + 1 ); + if ( DEBUG_RRULE ) printf( "Inserting %d from current_set into position %d\n", count($this->current_set), $position + 1 ); foreach( $this->current_set AS $k => $instance ) { if ( $instance < $this->base ) continue; if ( isset($this->until) && $instance > $this->until ) { @@ -445,7 +458,7 @@ class RepeatRule { $got_more = true; $position++; $this->instances[$position] = $instance; - if ( $GLOBALS['debug_rrule'] ) printf( "Added date %s into position %d in current set\n", $instance->format('c'), $position ); + if ( DEBUG_RRULE ) printf( "Added date %s into position %d in current set\n", $instance->format('c'), $position ); if ( isset($this->count) && ($position + 1) >= $this->count ) $this->finished = true; } } @@ -479,7 +492,7 @@ class RepeatRule { foreach( $instances AS $k => $instance ) { foreach( $this->bymonth AS $k => $month ) { $expanded = $this->date_mask( clone($instance), null, $month, null, null, null, null); - if ( $GLOBALS['debug_rrule'] ) printf( "Expanded BYMONTH $month into date %s\n", $expanded->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanded BYMONTH $month into date %s\n", $expanded->format('c') ); $this->current_set[] = $expanded; } } @@ -512,7 +525,7 @@ class RepeatRule { $expanded = clone($day_in_week); $expanded->modify( sprintf('+%d day', $offset) ); $this->current_set[] = $expanded; - if ( $GLOBALS['debug_rrule'] ) printf( "Expanded BYDAY(W) $weekday into date %s\n", $expanded->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanded BYDAY(W) $weekday into date %s\n", $expanded->format('c') ); } } @@ -528,7 +541,7 @@ class RepeatRule { $dow = $rrule_day_numbers[$matches[3]]; $first_dom = 1 + $dow - $dow_of_first; if ( $first_dom < 1 ) $first_dom +=7; // e.g. 1st=WE, dow=MO => 1+1-3=-1 => MO is 6th, etc. $whichweek = intval($matches[2]); - if ( $GLOBALS['debug_rrule'] ) printf( "Expanding BYDAY(M) $weekday in month of %s\n", $instance->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanding BYDAY(M) $weekday in month of %s\n", $first_of_month->format('c') ); if ( $whichweek > 0 ) { $whichweek--; $monthday = $first_dom; @@ -542,14 +555,14 @@ class RepeatRule { } if ( $monthday > 0 && $monthday <= $days_in_month ) { $expanded = $this->date_mask( clone($day_in_month), null, null, $monthday, null, null, null); - if ( $GLOBALS['debug_rrule'] ) printf( "Expanded BYDAY(M) $weekday now $monthday into date %s\n", $expanded->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanded BYDAY(M) $weekday now $monthday into date %s\n", $expanded->format('c') ); $this->current_set[] = $expanded; } } else { for( $monthday = $first_dom; $monthday <= $days_in_month; $monthday += 7 ) { $expanded = $this->date_mask( clone($day_in_month), null, null, $monthday, null, null, null); - if ( $GLOBALS['debug_rrule'] ) printf( "Expanded BYDAY(M) $weekday now $monthday into date %s\n", $expanded->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanded BYDAY(M) $weekday now $monthday into date %s\n", $expanded->format('c') ); $this->current_set[] = $expanded; } } @@ -570,7 +583,7 @@ class RepeatRule { $dow = $rrule_day_numbers[$matches[3]]; $first_doy = 1 + $dow - $dow_of_first; if ( $first_doy < 1 ) $first_doy +=7; // e.g. 1st=WE, dow=MO => 1+1-3=-1 => MO is 6th, etc. $whichweek = intval($matches[2]); - if ( $GLOBALS['debug_rrule'] ) printf( "Expanding BYDAY(Y) $weekday from date %s\n", $instance->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanding BYDAY(Y) $weekday from date %s\n", $instance->format('c') ); if ( $whichweek > 0 ) { $whichweek--; $yearday = $first_doy; @@ -584,14 +597,14 @@ class RepeatRule { } if ( $yearday > 0 && $yearday <= $days_in_year ) { $expanded->modify(sprintf('+%d day', $yearday - 1)); - if ( $GLOBALS['debug_rrule'] ) printf( "Expanded BYDAY(Y) $weekday now $yearday into date %s\n", $expanded->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanded BYDAY(Y) $weekday now $yearday into date %s\n", $expanded->format('c') ); $this->current_set[] = $expanded; } } else { $expanded->modify(sprintf('+%d day', $first_doy - 1)); for( $yearday = $first_doy; $yearday <= $days_in_year; $yearday += 7 ) { - if ( $GLOBALS['debug_rrule'] ) printf( "Expanded BYDAY(Y) $weekday now $yearday into date %s\n", $expanded->format('c') ); + if ( DEBUG_RRULE ) printf( "Expanded BYDAY(Y) $weekday now $yearday into date %s\n", $expanded->format('c') ); $this->current_set[] = clone($expanded); $expanded->modify('+1 week'); } @@ -669,7 +682,7 @@ class RepeatRule { $this->current_set = array(); foreach( $instances AS $k => $instance ) { foreach( $this->{$element_name} AS $k => $element_value ) { - if ( $GLOBALS['debug_rrule'] ) printf( "Limiting '$fmt_char' on '%s' => '%s' ?=? '%s' ? %s\n", $instance->format('c'), $instance->format($fmt_char), $element_value, ($instance->format($fmt_char) == $element_value ? 'Yes' : 'No') ); + if ( DEBUG_RRULE ) printf( "Limiting '$fmt_char' on '%s' => '%s' ?=? '%s' ? %s\n", $instance->format('c'), $instance->format($fmt_char), $element_value, ($instance->format($fmt_char) == $element_value ? 'Yes' : 'No') ); if ( $instance->format($fmt_char) == $element_value ) $this->current_set[] = $instance; } } @@ -684,7 +697,7 @@ class RepeatRule { foreach( $this->byday AS $k => $weekday ) { $dow = $rrule_day_numbers[$weekday]; foreach( $instances AS $k => $instance ) { - if ( $GLOBALS['debug_rrule'] ) printf( "Limiting '$fmt_char' on '%s' => '%s' ?=? '%s' (%d) ? %s\n", $instance->format('c'), $instance->format($fmt_char), $weekday, $dow, ($instance->format($fmt_char) == $dow ? 'Yes' : 'No') ); + if ( DEBUG_RRULE ) printf( "Limiting '$fmt_char' on '%s' => '%s' ?=? '%s' (%d) ? %s\n", $instance->format('c'), $instance->format($fmt_char), $weekday, $dow, ($instance->format($fmt_char) == $dow ? 'Yes' : 'No') ); if ( $instance->format($fmt_char) == $dow ) $this->current_set[] = $instance; } } @@ -703,7 +716,7 @@ class RepeatRule { $count = count($instances); $this->current_set = array(); foreach( $this->bysetpos AS $k => $element_value ) { - if ( $GLOBALS['debug_rrule'] ) printf( "Limiting bysetpos %s of %d instances\n", $element_value, $count ); + if ( DEBUG_RRULE ) printf( "Limiting bysetpos %s of %d instances\n", $element_value, $count ); if ( $element_value > 0 ) { $this->current_set[] = $instances[$element_value - 1]; } @@ -825,6 +838,7 @@ function expand_event_instances( $vResource, $range_start = null, $range_end = n $recur_utc = $recur_utc->UTC(); if ( isset($range) && $range == 'THISANDFUTURE' ) { foreach( $instances AS $k => $v ) { + if ( DEBUG_RRULE ) printf( "Removing overridden instance at: $k\n" ); if ( $k >= $recur_utc ) unset($instances[$k]); } } @@ -832,11 +846,45 @@ function expand_event_instances( $vResource, $range_start = null, $range_end = n unset($instances[$recur_utc]); } } + else if ( DEBUG_RRULE ) { + $p = $comp->GetProperty('SUMMARY'); + $summary = ( isset($p) ? $p->Value() : 'not set'); + $p = $comp->GetProperty('UID'); + $uid = ( isset($p) ? $p->Value() : 'not set'); + printf( "Processing event '%s' with UID '%s' starting on %s\n", + $summary, $uid, $dtstart->UTC() ); + print( "Instances at start"); + foreach( $instances AS $k => $v ) { + print ' : '.$k; + } + print "\n"; + } $instances = array_merge( $instances, rrule_expand($dtstart, 'RRULE', $comp, $range_end) ); + if ( DEBUG_RRULE ) { + print( "After rrule_expand"); + foreach( $instances AS $k => $v ) { + print ' : '.$k; + } + print "\n"; + } $instances = array_merge( $instances, rdate_expand($dtstart, 'RDATE', $comp, $range_end) ); + if ( DEBUG_RRULE ) { + print( "After rdate_expand"); + foreach( $instances AS $k => $v ) { + print ' : '.$k; + } + print "\n"; + } foreach ( rdate_expand($dtstart, 'EXDATE', $comp, $range_end) AS $k => $v ) { unset($instances[$k]); } + if ( DEBUG_RRULE ) { + print( "After exdate_expand"); + foreach( $instances AS $k => $v ) { + print ' : '.$k; + } + print "\n"; + } } $last_duration = null; diff --git a/inc/freebusy-functions.php b/inc/freebusy-functions.php index d2a67ebd..b7d788a7 100644 --- a/inc/freebusy-functions.php +++ b/inc/freebusy-functions.php @@ -33,12 +33,12 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null $where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } - +// $debugging = true; $fbtimes = array(); $sql = 'SELECT caldav_data.caldav_data, calendar_item.rrule, calendar_item.transp, calendar_item.status, '; $sql .= "to_char(calendar_item.dtstart at time zone 'GMT',".iCalendar::SqlUTCFormat().') AS start, '; $sql .= "to_char(calendar_item.dtend at time zone 'GMT',".iCalendar::SqlUTCFormat().') AS finish, '; - $sql .= "calendar_item.class "; + $sql .= "calendar_item.class, calendar_item.dav_id "; $sql .= 'FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name,collection_id) '; $sql .= 'INNER JOIN collection USING(collection_id)'; $sql .= $where; @@ -50,6 +50,9 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null if ( $calendar_object->status == 'TENTATIVE' ) { $extra = ';BUSY-TENTATIVE'; } +// else if ( $debugging ) { +// $extra = ';'.$calendar_object->dav_id; +// } dbg_error_log( "REPORT", " FreeBusy: Not transparent, tentative or cancelled: %s, %s, %s", $calendar_object->start, $calendar_object->finish, $calendar_object->class ); $ics = new vComponent($calendar_object->caldav_data); $expanded = expand_event_instances($ics, $range_start, $range_end); @@ -61,7 +64,7 @@ function get_freebusy( $path_match, $range_start, $range_end, $bin_privs = null // echo "-----------------------------------------------------\n"; $start_date = $v->GetProperty('DTSTART'); if ( !isset($start_date) ) continue; - $start_date = new RepeatRuleDateTime($start_date->Value()); + $start_date = new RepeatRuleDateTime($start_date); $duration = $v->GetProperty('DURATION'); $duration = ( !isset($duration) ? 'P1D' : $duration->Value()); $end_date = clone($start_date); diff --git a/testing/tests/regression-suite/0889-POST-freebusy.result b/testing/tests/regression-suite/0889-POST-freebusy.result index d8510527..b9663c7f 100644 --- a/testing/tests/regression-suite/0889-POST-freebusy.result +++ b/testing/tests/regression-suite/0889-POST-freebusy.result @@ -3613,7 +3613,6 @@ FREEBUSY:20040618T160000Z/20040618T210000Z FREEBUSY:20040628T063000Z/20040628T110000Z FREEBUSY:20040802T100000Z/20040802T130000Z FREEBUSY:20040803T000000Z/20040804T000000Z -FREEBUSY:20040804T000000Z/20040805T000000Z FREEBUSY:20040806T070000Z/20040806T100000Z FREEBUSY:20040819T070000Z/20040819T100000Z FREEBUSY:20040823T060000Z/20040823T070000Z @@ -3621,7 +3620,6 @@ FREEBUSY:20040904T080000Z/20040904T170000Z FREEBUSY:20040909T170000Z/20040909T200000Z FREEBUSY:20040915T120000Z/20040915T140000Z FREEBUSY:20040916T000000Z/20040917T000000Z -FREEBUSY:20040917T000000Z/20040918T000000Z FREEBUSY:20040921T093000Z/20040921T100000Z FREEBUSY:20041001T140000Z/20041001T150000Z FREEBUSY:20041111T130000Z/20041111T150000Z