From e64f92ff86dee3bb834f744ef477740bc488c5cf Mon Sep 17 00:00:00 2001 From: Andrew McMillan Date: Wed, 28 Sep 2011 05:21:31 +0800 Subject: [PATCH] Fix handling of BYMONTHDAY=-N in repeat rules. It seems PHP's date::setDate function doesn't do what we want when you hand it a negative integer so we need to override a little more of it's behaviour. We have to make sure that date::modify is not called with a days greater than the month we might land in when we add a number of months to it. --- inc/RRule-v2.php | 33 ++++++++++++++++++- testing/test-RRULE-v2.php | 3 +- .../regression-suite/0831-Spec-RRULE-1.result | 32 +++++++++++++++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/inc/RRule-v2.php b/inc/RRule-v2.php index 361b2d81..77b55209 100644 --- a/inc/RRule-v2.php +++ b/inc/RRule-v2.php @@ -343,6 +343,9 @@ class RepeatRuleDateTime extends DateTime { // printf( "Modify '%s' by: >>%s<<\n", $this->__toString(), $interval ); // print_r($this); if ( !isset($interval) || $interval == '' ) $interval = '1 day'; + if ( parent::format('d') > 28 && strstr($interval,'month') !== false ) { + $this->setDate(null,null,28); + } parent::modify($interval); return $this->__toString(); } @@ -431,10 +434,36 @@ class RepeatRuleDateTime extends DateTime { } + /** + * Returns a 1 if this year is a leap year, otherwise a 0 + * @param int $year The year we are quizzical about. + * @return 1 if this is a leap year, 0 otherwise + */ + public static function hasLeapDay($year) { + if ( ($year % 4) == 0 && (($year % 100) != 0 || ($year % 400) == 0) ) return 1; + return 0; + } + + /** + * Returns the number of days in a year/month pair + * @param int $year + * @param int $month + * @return int the number of days in the month + */ + public static function daysInMonth( $year, $month ) { + if ($month == 4 || $month == 6 || $month == 9 || $month == 11) return 30; + else if ($month != 2) return 31; + return 28 + RepeatRuleDateTime::hasLeapDay($year); + } + + function setDate( $year=null, $month=null, $day=null ) { if ( !isset($year) ) $year = parent::format('Y'); if ( !isset($month) ) $month = parent::format('m'); if ( !isset($day) ) $day = parent::format('d'); + if ( $day < 0 ) { + $day += RepeatRuleDateTime::daysInMonth($year, $month) + 1; + } parent::setDate( $year , $month , $day ); return $this; } @@ -749,7 +778,9 @@ class RepeatRule { $this->current_set = array(); foreach( $instances AS $k => $instance ) { foreach( $this->bymonthday AS $k => $monthday ) { - $this->current_set[] = $this->date_mask( clone($instance), null, null, $monthday, null, null, null); + $expanded = $this->date_mask( clone($instance), null, null, $monthday, null, null, null); + if ( DEBUG_RRULE ) printf( "Expanded BYMONTHDAY $monthday into date %s from %s\n", $expanded->format('c'), $instance->format('c') ); + $this->current_set[] = $expanded; } } } diff --git a/testing/test-RRULE-v2.php b/testing/test-RRULE-v2.php index 4d9edcb9..c9c68d57 100755 --- a/testing/test-RRULE-v2.php +++ b/testing/test-RRULE-v2.php @@ -80,7 +80,8 @@ $tests = array( , new RRuleTest( "The last working day of each month", "20061107T113000", "RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1;COUNT=30" ) , new RRuleTest( "Every working day", "20081020T103000", "RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;COUNT=30" ) , new RRuleTest( "Every working day", "20081020T110000", "RRULE:FREQ=DAILY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR;COUNT=30" ) -//**SQL is wrong , new RRuleTest( "1st Tuesday, 2nd Wednesday, 3rd Thursday & 4th Friday, every March, June, September, October and December", "20081001T133000", "RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU,2WE,3TH,4FR;BYMONTH=3,6,9,10,12" ) + , new RRuleTest( "The last day of each month", "20110831", "RRULE:FREQ=MONTHLY;BYMONTHDAY=-1" ) + , new RRuleTest( "1st Tuesday, 2nd Wednesday, 3rd Thursday & 4th Friday, every March, June, September, October and December (SQL is wrong)", "20081001T133000", "RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU,2WE,3TH,4FR;BYMONTH=3,6,9,10,12" ) , new RRuleTest( "Every tuesday and friday", "20081017T084500", "RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=TU,FR;COUNT=30" ) , new RRuleTest( "Every tuesday and friday", "20081017T084500", "RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,FR;COUNT=30" ) , new RRuleTest( "Every tuesday and friday", "20081017T084500", "RRULE:FREQ=DAILY;INTERVAL=1;BYDAY=TU,FR;COUNT=30" ) diff --git a/testing/tests/regression-suite/0831-Spec-RRULE-1.result b/testing/tests/regression-suite/0831-Spec-RRULE-1.result index 8d9952d3..7e05bade 100644 --- a/testing/tests/regression-suite/0831-Spec-RRULE-1.result +++ b/testing/tests/regression-suite/0831-Spec-RRULE-1.result @@ -2,7 +2,7 @@ HTTP/1.1 200 OK Date: Dow, 01 Jan 2000 00:00:00 GMT DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule DAV: extended-mkcol, calendar-proxy, bind, addressbook, calendar-auto-schedule -Content-Length: 5323 +Content-Length: 7224 Content-Type: text/plain #!/usr/bin/php @@ -51,6 +51,36 @@ PHP & SQL results are identical (-: 20081020T110000 - RRULE:FREQ=DAILY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR;COUNT=30 Every working day PHP & SQL results are identical (-: +=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= +20110831 - RRULE:FREQ=MONTHLY;BYMONTHDAY=-1 +The last day of each month +PHP & SQL results are identical (-: +=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= +20081001T133000 - RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU,2WE,3TH,4FR;BYMONTH=3,6,9,10,12 +1st Tuesday, 2nd Wednesday, 3rd Thursday & 4th Friday, every March, June, September, October and December (SQL is wrong) +PHP & SQL results differ :-( +PHP Result: + + 2008-10-07 13:30:00 2008-10-08 13:30:00 2008-10-16 13:30:00 2008-10-24 13:30:00 + 2008-12-02 13:30:00 2008-12-10 13:30:00 2008-12-18 13:30:00 2008-12-26 13:30:00 + 2009-03-03 13:30:00 2009-03-11 13:30:00 2009-03-19 13:30:00 2009-03-27 13:30:00 + 2009-06-02 13:30:00 2009-06-10 13:30:00 2009-06-18 13:30:00 2009-06-26 13:30:00 + 2009-09-01 13:30:00 2009-09-09 13:30:00 2009-09-17 13:30:00 2009-09-25 13:30:00 + 2009-10-06 13:30:00 2009-10-14 13:30:00 2009-10-15 13:30:00 2009-10-23 13:30:00 + 2009-12-01 13:30:00 2009-12-09 13:30:00 2009-12-17 13:30:00 2009-12-25 13:30:00 + 2010-03-02 13:30:00 2010-03-10 13:30:00 + +SQL Result: + + 2008-10-07 13:30:00 2008-10-08 13:30:00 2008-10-16 13:30:00 2008-10-24 13:30:00 + 2008-11-04 13:30:00 2008-11-12 13:30:00 2008-11-20 13:30:00 2008-11-28 13:30:00 + 2008-12-02 13:30:00 2008-12-10 13:30:00 2008-12-18 13:30:00 2008-12-26 13:30:00 + 2009-01-06 13:30:00 2009-01-14 13:30:00 2009-01-15 13:30:00 2009-01-23 13:30:00 + 2009-02-03 13:30:00 2009-02-11 13:30:00 2009-02-19 13:30:00 2009-02-27 13:30:00 + 2009-03-03 13:30:00 2009-03-11 13:30:00 2009-03-19 13:30:00 2009-03-27 13:30:00 + 2009-04-07 13:30:00 2009-04-08 13:30:00 2009-04-16 13:30:00 2009-04-24 13:30:00 + 2009-05-05 13:30:00 2009-05-13 13:30:00 + =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~= 20081017T084500 - RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=TU,FR;COUNT=30 Every tuesday and friday