diff --git a/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php b/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php --- a/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php +++ b/src/parser/calendar/data/PhutilCalendarRecurrenceRule.php @@ -352,20 +352,15 @@ break; case self::FREQUENCY_MONTHLY: $this->cursorMonth -= $interval; - - while ($this->cursorMonth < 1) { - $this->rewindMonth(); - } + $this->rewindMonth(); break; case self::FREQUENCY_DAILY: $this->cursorDay -= $interval; - - $week_start = $this->getWeekStart(); - while ($this->cursorDay < 1) { - $year_map = $this->getYearMap($this->cursorYear, $week_start); - $this->cursorDay += $year_map['monthDays'][$this->cursorMonth]; - $this->rewindMonth(); - } + $this->rewindDay(); + break; + case self::FREQUENCY_HOURLY: + $this->cursorHour -= $interval; + $this->rewindHour(); break; default: throw new Exception( @@ -1238,13 +1233,36 @@ if ($scale < self::SCALE_MONTHLY) { $parts[] = $this->stateDay; } + if ($scale < self::SCALE_DAILY) { + $parts[] = $this->stateHour; + } return implode('/', $parts); } private function rewindMonth() { - $this->cursorYear--; - $this->cursorMonth += 12; + while ($this->cursorMonth < 1) { + $this->cursorYear--; + $this->cursorMonth += 12; + } + } + + private function rewindDay() { + $week_start = $this->getWeekStart(); + while ($this->cursorDay < 1) { + $year_map = $this->getYearMap($this->cursorYear, $week_start); + $this->cursorDay += $year_map['monthDays'][$this->cursorMonth]; + $this->cursorMonth--; + $this->rewindMonth(); + } + } + + private function rewindHour() { + while ($this->cursorHour < 0) { + $this->cursorHour += 24; + $this->cursorDay--; + $this->rewindDay(); + } } private function advanceCursorState( diff --git a/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php b/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php --- a/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php +++ b/src/parser/calendar/data/__tests__/PhutilCalendarRecurrenceRuleTestCase.php @@ -853,6 +853,131 @@ '19980101T020000Z', ); + $tests[] = array( + 'BYMONTHDAY' => array(1, 3), + ); + $expect[] = array( + '19970903T000000Z', + '19970903T010000Z', + '19970903T020000Z', + ); + + $tests[] = array( + 'BYMONTH' => array(1, 3), + 'BYMONTHDAY' => array(5, 7), + ); + $expect[] = array( + '19980105T000000Z', + '19980105T010000Z', + '19980105T020000Z', + ); + + $tests[] = array( + 'BYDAY' => array('TU', 'TH'), + ); + $expect[] = array( + '19970902T090000Z', + '19970902T100000Z', + '19970902T110000Z', + ); + + $tests[] = array( + 'BYMONTH' => array(1, 3), + 'BYDAY' => array('TU', 'TH'), + ); + $expect[] = array( + '19980101T000000Z', + '19980101T010000Z', + '19980101T020000Z', + ); + + $tests[] = array( + 'BYMONTHDAY' => array(1, 3), + 'BYDAY' => array('TU', 'TH'), + ); + $expect[] = array( + '19980101T000000Z', + '19980101T010000Z', + '19980101T020000Z', + ); + + $tests[] = array( + 'BYMONTHDAY' => array(1, 3), + 'BYMONTH' => array(1, 3), + 'BYDAY' => array('TU', 'TH'), + ); + $expect[] = array( + '19980101T000000Z', + '19980101T010000Z', + '19980101T020000Z', + ); + + $tests[] = array( + 'COUNT' => 4, + 'BYYEARDAY' => array(1, 100, 200, 365), + ); + $expect[] = array( + '19971231T000000Z', + '19971231T010000Z', + '19971231T020000Z', + '19971231T030000Z', + ); + + $tests[] = array( + 'COUNT' => 4, + 'BYYEARDAY' => array(-365, -266, -166, -1), + ); + $expect[] = array( + '19971231T000000Z', + '19971231T010000Z', + '19971231T020000Z', + '19971231T030000Z', + ); + + $tests[] = array( + 'COUNT' => 4, + 'BYMONTH' => array(4, 7), + 'BYYEARDAY' => array(1, 100, 200, 365), + ); + $expect[] = array( + '19980410T000000Z', + '19980410T010000Z', + '19980410T020000Z', + '19980410T030000Z', + ); + + $tests[] = array( + 'COUNT' => 4, + 'BYMONTH' => array(4, 7), + 'BYYEARDAY' => array(-365, -266, -166, -1), + ); + $expect[] = array( + '19980410T000000Z', + '19980410T010000Z', + '19980410T020000Z', + '19980410T030000Z', + ); + + $tests[] = array( + 'BYHOUR' => array(6, 18), + ); + $expect[] = array( + '19970902T180000Z', + '19970903T060000Z', + '19970903T180000Z', + ); + + $tests[] = array( + 'BYMINUTE' => array(15, 45), + 'BYSECOND' => array(15, 45), + 'BYSETPOS' => array(3, -3), + ); + $expect[] = array( + '19970902T091545Z', + '19970902T094515Z', + '19970902T101545Z', + ); + $this->assertRules( array( 'FREQ' => 'HOURLY',