Page MenuHomePhabricator

D16628.diff
No OneTemporary

D16628.diff

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
@@ -21,10 +21,10 @@
private $cursorSecond;
private $cursorMinute;
private $cursorHour;
+ private $cursorHourState;
private $cursorWeek;
private $cursorDay;
- private $cursorDayMonth;
- private $cursorDayYear;
+ private $cursorDayState;
private $cursorMonth;
private $cursorYear;
@@ -379,8 +379,15 @@
$this->minimumEpoch = null;
}
- $this->cursorDayMonth = $this->cursorMonth;
- $this->cursorDayYear = $this->cursorYear;
+ $cursor_state = array(
+ 'year' => $this->cursorYear,
+ 'month' => $this->cursorMonth,
+ 'day' => $this->cursorDay,
+ 'hour' => $this->cursorHour,
+ );
+
+ $this->cursorDayState = $cursor_state;
+ $this->cursorHourState = $cursor_state;
$by_hour = $this->getByHour();
$by_minute = $this->getByMinute();
@@ -567,14 +574,14 @@
while (!$this->setHours) {
$this->nextDay();
- if ($is_hourly || $by_hour) {
+ $is_dynamic = $is_hourly
+ || $by_hour
+ || ($scale < self::SCALE_HOURLY);
+
+ if ($is_dynamic) {
$hours = $this->newHoursSet(
($is_hourly ? $interval : 1),
$by_hour);
- } else if ($scale < self::SCALE_HOURLY) {
- $hours = $this->newHoursSet(
- 1,
- array());
} else {
$hours = array(
$this->cursorHour,
@@ -609,14 +616,14 @@
while (!$this->setDays) {
$this->nextMonth();
- $is_dyanmic = $is_daily
+ $is_dynamic = $is_daily
|| $by_day
|| $by_monthday
|| $by_yearday
|| $by_weekno
|| ($scale < self::SCALE_DAILY);
- if ($is_dyanmic) {
+ if ($is_dynamic) {
$weeks = $this->newDaysSet(
($is_daily ? $interval : 1),
($is_weekly ? $interval : null),
@@ -771,8 +778,15 @@
// events.
$hours_in_day = 24;
- if ($this->cursorHour >= $hours_in_day) {
- $this->cursorHour -= $hours_in_day;
+ // If the hour cursor is behind the current time, we need to forward it in
+ // INTERVAL increments so we end up with the right offset.
+ list($skip, $this->cursorHourState) = $this->advanceCursorState(
+ $this->cursorHourState,
+ self::SCALE_HOURLY,
+ $interval,
+ $this->getWeekStart());
+
+ if ($skip) {
return array();
}
@@ -796,7 +810,6 @@
$by_weekno,
$week_start) {
-
$selection = array();
if ($interval_week) {
$year_map = $this->getYearMap($this->stateYear, $week_start);
@@ -827,43 +840,30 @@
// 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);
- }
- }
- }
+ list($skip, $this->cursorDayState) = $this->advanceCursorState(
+ $this->cursorDayState,
+ self::SCALE_DAILY,
+ $interval_day,
+ $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++;
+ if (!$skip) {
+ $year_map = $this->getYearMap($this->stateYear, $week_start);
+ while (true) {
+ $month_idx = $this->stateMonth;
+ $month_days = $year_map['monthDays'][$month_idx];
+ if ($this->cursorDay > $month_days) {
// 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;
}
- break;
- }
- $day_idx = $this->cursorDay;
+ $day_idx = $this->cursorDay;
- $key = "{$month_idx}M{$day_idx}D";
- $selection[] = $year_map['info'][$key];
+ $key = "{$month_idx}M{$day_idx}D";
+ $selection[] = $year_map['info'][$key];
- $this->cursorDay += $interval_day;
+ $this->cursorDay += $interval_day;
+ }
}
}
@@ -1247,4 +1247,117 @@
$this->cursorMonth += 12;
}
+ private function advanceCursorState(
+ array $cursor,
+ $scale,
+ $interval,
+ $week_start) {
+
+ $state = array(
+ 'year' => $this->stateYear,
+ 'month' => $this->stateMonth,
+ 'day' => $this->stateDay,
+ 'hour' => $this->stateHour,
+ );
+
+ // In the common case when the interval is 1, we'll visit every possible
+ // value so we don't need to do any math and can just jump to the first
+ // hour, day, etc.
+ if ($interval == 1) {
+ if ($this->isCursorBehind($cursor, $state, $scale)) {
+ switch ($scale) {
+ case self::SCALE_DAILY:
+ $this->cursorDay = 1;
+ break;
+ case self::SCALE_HOURLY:
+ $this->cursorHour = 0;
+ break;
+ }
+ }
+ return array(false, $state);
+ }
+
+ $year_map = $this->getYearMap($cursor['year'], $week_start);
+ while ($this->isCursorBehind($cursor, $state, $scale)) {
+ switch ($scale) {
+ case self::SCALE_DAILY:
+ $cursor['day'] += $interval;
+ break;
+ case self::SCALE_HOURLY:
+ $cursor['hour'] += $interval;
+ break;
+ }
+
+ if ($scale <= self::SCALE_HOURLY) {
+ while ($cursor['hour'] >= 24) {
+ $cursor['hour'] -= 24;
+ $cursor['day']++;
+ }
+ }
+
+ if ($scale <= self::SCALE_DAILY) {
+ while ($cursor['day'] > $year_map['monthDays'][$cursor['month']]) {
+ $cursor['day'] -= $year_map['monthDays'][$cursor['month']];
+ $cursor['month']++;
+ if ($cursor['month'] > 12) {
+ $cursor['month'] -= 12;
+ $cursor['year']++;
+ $year_map = $this->getYearMap($cursor['year'], $week_start);
+ }
+ }
+ }
+ }
+
+ switch ($scale) {
+ case self::SCALE_DAILY:
+ $this->cursorDay = $cursor['day'];
+ break;
+ case self::SCALE_HOURLY:
+ $this->cursorHour = $cursor['hour'];
+ break;
+ }
+
+ $skip = $this->isCursorBehind($state, $cursor, $scale);
+
+ return array($skip, $cursor);
+ }
+
+ private function isCursorBehind(array $cursor, array $state, $scale) {
+ if ($cursor['year'] < $state['year']) {
+ return true;
+ } else if ($cursor['year'] > $state['year']) {
+ return false;
+ }
+
+ if ($cursor['month'] < $state['month']) {
+ return true;
+ } else if ($cursor['month'] > $state['month']) {
+ return false;
+ }
+
+ if ($scale >= self::SCALE_DAILY) {
+ return false;
+ }
+
+ if ($cursor['day'] < $state['day']) {
+ return true;
+ } else if ($cursor['day'] > $state['day']) {
+ return false;
+ }
+
+ if ($scale >= self::SCALE_HOURLY) {
+ return false;
+ }
+
+ if ($cursor['hour'] < $state['hour']) {
+ return true;
+ } else if ($cursor['hour'] > $state['hour']) {
+ return false;
+ }
+
+ return false;
+ }
+
+
+
}
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
@@ -815,6 +815,54 @@
$expect);
}
+ public function testHourlyRecurrenceRules() {
+ $tests = array();
+ $expect = array();
+
+ $tests[] = array();
+ $expect[] = array(
+ '19970902T090000Z',
+ '19970902T100000Z',
+ '19970902T110000Z',
+ );
+
+ $tests[] = array(
+ 'INTERVAL' => 2,
+ );
+ $expect[] = array(
+ '19970902T090000Z',
+ '19970902T110000Z',
+ '19970902T130000Z',
+ );
+
+ $tests[] = array(
+ 'INTERVAL' => 769,
+ );
+ $expect[] = array(
+ '19970902T090000Z',
+ '19971004T100000Z',
+ '19971105T110000Z',
+ );
+
+ $tests[] = array(
+ 'BYMONTH' => array(1, 3),
+ );
+ $expect[] = array(
+ '19980101T000000Z',
+ '19980101T010000Z',
+ '19980101T020000Z',
+ );
+
+ $this->assertRules(
+ array(
+ 'FREQ' => 'HOURLY',
+ 'COUNT' => 3,
+ 'DTSTART' => '19970902T090000Z',
+ ),
+ $tests,
+ $expect);
+ }
+
private function assertRules(array $defaults, array $tests, array $expect) {
foreach ($tests as $key => $test) {

File Metadata

Mime Type
text/plain
Expires
Sat, Mar 15, 4:35 PM (3 d, 4 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7358814
Default Alt Text
D16628.diff (9 KB)

Event Timeline