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 @@ -23,6 +23,8 @@ private $cursorHour; private $cursorWeek; private $cursorDay; + private $cursorDayMonth; + private $cursorDayYear; private $cursorMonth; private $cursorYear; @@ -322,6 +324,8 @@ // TODO: Figure this out. $this->cursorWeek = null; $this->cursorDay = $date->getDay(); + $this->cursorDayMonth = $date->getMonth(); + $this->cursorDayYear = $date->getYear(); $this->cursorMonth = $date->getMonth(); $this->cursorYear = $date->getYear(); @@ -594,29 +598,28 @@ $by_monthday = $this->getByMonthDay(); $by_yearday = $this->getByYearDay(); $by_weekno = $this->getByWeekNumber(); + $by_month = $this->getByMonth(); $week_start = $this->getWeekStart(); while (!$this->setDays) { $this->nextMonth(); - if ($is_daily || $by_day || $by_monthday || $by_yearday || $by_weekno) { + $is_dyanmic = $is_daily + || $by_day + || $by_monthday + || $by_yearday + || $by_weekno + || ($scale < self::SCALE_DAILY); + + if ($is_dyanmic) { $weeks = $this->newDaysSet( - ($is_daily ? $interval : null), + ($is_daily ? $interval : 1), ($is_weekly ? $interval : null), $by_day, $by_monthday, $by_yearday, $by_weekno, $week_start); - } else if ($scale < self::SCALE_DAILY) { - $weeks = $this->newDaysSet( - 1, - null, - array(), - array(), - array(), - array(), - $week_start); } else { // The cursor day may not actually exist in the current month, so // make sure the day is valid before we generate a set which contains @@ -788,10 +791,11 @@ $by_weekno, $week_start) { - $year_map = $this->getYearMap($this->stateYear, $week_start); $selection = array(); if ($interval_week) { + $year_map = $this->getYearMap($this->stateYear, $week_start); + while (true) { // TODO: This is all garbage? if ($this->cursorWeek > $year_map['weekCount']) { @@ -811,29 +815,41 @@ $this->cursorWeek += $interval_week; } } else { - $month_idx = $this->stateMonth; - if (!$interval_day) { $interval_day = 1; } - // If we have a BYDAY, BYMONTHDAY, BYYEARDAY or BYWEEKNO selector and - // this isn't the initial month, reset the day cursor to the first of the - // month to make sure we examine the entire month. If we don't do this, - // we can have a situation where an event occurs "every Monday in - // October", but has a start date on the 19th of August, and misses - // Mondays in October prior to the 19th. - if ($by_day || $by_monthday || $by_yearday || $by_weekno) { - if ($this->stateYear !== $this->initialYear || - $this->stateMonth !== $this->initialMonth) { - $this->cursorDay = 1; + // If the day cursor is behind the current year and month, we need to + // forward it in INTERVAL increments so we end up with the right offset + // in the current month. + $year_map = $this->getYearMap($this->cursorDayYear, $week_start); + while (($this->cursorDayYear < $this->stateYear) || + ($this->cursorDayYear == $this->stateYear && + $this->cursorDayMonth < $this->stateMonth)) { + $this->cursorDay += $interval_day; + if ($this->cursorDay > $year_map['monthDays'][$this->cursorDayMonth]) { + $this->cursorDay -= $year_map['monthDays'][$this->cursorDayMonth]; + $this->cursorDayMonth++; + if ($this->cursorDayMonth > 12) { + $this->cursorDayMonth = 1; + $this->cursorDayYear++; + $year_map = $this->getYearMap($this->cursorDayYear, $week_start); + } } } while (true) { + $month_idx = $this->stateMonth; $month_days = $year_map['monthDays'][$month_idx]; if ($this->cursorDay > $month_days) { $this->cursorDay -= $month_days; + $this->cursorDayMonth++; + if ($this->cursorDayMonth > 12) { + $this->cursorDayMonth = 1; + $this->cursorDayYear++; + // NOTE: The year map is now out of date, but we're about to break + // out of the loop anyway so it doesn't matter. + } break; } 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 @@ -684,6 +684,69 @@ $expect); } + public function testDailyRecurrenceRules() { + $tests = array(); + $expect = array(); + + $tests[] = array(); + $expect[] = array( + '19970902', + '19970903', + '19970904', + ); + + $tests[] = array( + 'INTERVAL' => 2, + ); + $expect[] = array( + '19970902', + '19970904', + '19970906', + ); + + $tests[] = array( + 'INTERVAL' => 92, + ); + $expect[] = array( + '19970902', + '19971203', + '19980305', + ); + + $tests[] = array( + 'BYMONTH' => array(1, 3), + ); + $expect[] = array( + '19980101', + '19980102', + '19980103', + ); + + // This is testing that INTERVAL is respected in the presence of a BYMONTH + // filter which skips some months + $tests[] = array( + 'BYMONTH' => array(12), + 'INTERVAL' => 17, + ); + $expect[] = array( + '19971213', + '19971230', + '19981205', + ); + + + + $this->assertRules( + array( + 'FREQ' => 'DAILY', + 'COUNT' => 3, + 'DTSTART' => '19970902', + ), + $tests, + $expect); + } + + private function assertRules(array $defaults, array $tests, array $expect) { foreach ($tests as $key => $test) { $options = $test + $defaults;