diff --git a/inc/RRule.php b/inc/RRule.php index 92d965df..4a41c182 100644 --- a/inc/RRule.php +++ b/inc/RRule.php @@ -9,7 +9,7 @@ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 */ -$ical_weekdays = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ); +$ical_weekdays = array( 'SU' => 0, 'MO' => 1, 'TU' => 2, 'WE' => 3, 'TH' => 4, 'FR' => 5, 'SA' => 6 ); /** * A Class for handling dates in iCalendar format. We do make the simplifying assumption @@ -145,6 +145,7 @@ class iCalDate { * Set the day of week used for calculation of week starts */ function SetWeekStart() { + global $ical_weekdays; $this->_wkst = $ical_weekdays[$weekstart]; } @@ -391,7 +392,10 @@ class iCalDate { $set = array(); $first_dow = (date('w',$this->_epoch) - $this->_dd + 36) % 7; foreach( $dayrules AS $k => $v ) { - $set[$this->MonthDay($first_dow,$days_in_month,$v)] = 1; + $days = $this->MonthDays($first_dow,$days_in_month,$v); + foreach( $days AS $k2 => $v2 ) { + $set[$v2] = 1; + } } return $set; } @@ -422,9 +426,55 @@ class iCalDate { function GreaterThan($lesser) { return ( $this->_text > $lesser ); // These sorts of dates are designed that way... } + + + /** + * Given a MonthDays string like "1MO", "-2WE" return an integer day of the month. + * + * @param string $dow_first The day of week of the first of the month. + * @param string $days_in_month The number of days in the month. + * @param string $dayspec The specification for a month day (or days) which we parse. + * + * @return array An array of the day numbers for the month which meet the rule. + */ + function &MonthDays($dow_first, $days_in_month, $dayspec) { + global $ical_weekdays; + dbg_error_log( "RRule", " Getting days for '%s'. %d days starting on a %d", $dayspec, $days_in_month, $dow_first ); + $set = array(); + preg_match( '/([0-9-]*)(MO|TU|WE|TH|FR|SA|SU)/', $dayspec, $matches); + $numeric = intval($matches[1]); + $dow = $ical_weekdays[$matches[2]]; + + $first_matching_day = 1 + ($dow - $dow_first); + if ( $first_matching_day < 0 ) $first_matching_day += 7; + + dbg_error_log( "RRule", " Looking at %d for first match on (%s/%s), %d for numeric", $first_matching_day, $matches[1], $matches[2], $numeric ); + + while( $first_matching_day <= $days_in_month ) { + $set[] = $first_matching_day; + $first_matching_day += 7; + } + + if ( $numeric != 0 ) { + if ( $numeric < 0 ) { + $numeric += count($set); + } + else { + $numeric--; + } + $answer = $set[$numeric]; + $set = array( $answer ); + } + + dbg_log_array( "RRule", 'MonthDays', $set, false ); + + return $set; + } + } + /** * A Class for handling Events on a calendar which repeat * @@ -546,6 +596,9 @@ class RRule { /** An array of all the dates so far */ var $_dates; + /** Whether we have calculated any of the dates */ + var $_started; + /** Whether we have calculated all of the dates */ var $_finished; @@ -564,6 +617,7 @@ class RRule { function RRule( $start, $rrule ) { $this->_first = new iCalDate($start); $this->_finished = false; + $this->_started = false; $this->_dates = array( $this->_first ); $this->_current = -1; @@ -634,7 +688,12 @@ class RRule { $limit--; do { $limit--; - $next->AddMonths($this->_part['INTERVAL']); + if ( $this->_started ) { + $next->AddMonths($this->_part['INTERVAL']); + } + else { + $this->_started = true; + } } while ( $limit && ! $next->TestByMonth($this->_part['BYMONTH']) ); @@ -656,8 +715,18 @@ class RRule { } else if ( $this->_part['FREQ'] == "DAILY" ) { - $next->AddDays($this->_part['INTERVAL']); - $days[$next->_dd] = 1; + $limit = 100; + do { + $limit--; + if ( $this->_started ) { + $next->AddDays($this->_part['INTERVAL']); + } + else { + $this->_started = true; + } + $days[$next->_dd] = 1; + } + while( $limit && count($days) < 1 ); } $i = 0; diff --git a/inc/test-RRULE.php b/inc/test-RRULE.php index 4692c1db..f13ef4fa 100644 --- a/inc/test-RRULE.php +++ b/inc/test-RRULE.php @@ -13,7 +13,8 @@ $tests = array( "20061103T073000" => "RRULE:FREQ=DAILY;COUNT=7" , "20061102T100000" => "RRULE:FREQ=WEEKLY;COUNT=26;INTERVAL=1;BYDAY=TH" , "20061103T160000" => "RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20071222T235900" - , "20061103T073000" => "RRULE:FREQ=MONTHLY" + , "20061104T073000" => "RRULE:FREQ=MONTHLY" + , "20061117T073000" => "RRULE:FREQ=MONTHLY;BYDAY=1MO,2WE,3FR,-1SU" ); foreach( $tests AS $start => $rrule ) {