Changeset View
Changeset View
Standalone View
Standalone View
src/parser/calendar/data/PhutilCalendarRecurrenceRule.php
Show First 20 Lines • Show All 204 Lines • ▼ Show 20 Lines | public function getByHour() { | ||||
return $this->byHour; | return $this->byHour; | ||||
} | } | ||||
public function setByDay(array $by_day) { | public function setByDay(array $by_day) { | ||||
$constants = self::getAllWeekdayConstants(); | $constants = self::getAllWeekdayConstants(); | ||||
$constants = implode('|', $constants); | $constants = implode('|', $constants); | ||||
$pattern = '/^(?:[+-]?([1-9]\d?))?('.$constants.')\z/'; | $pattern = '/^(?:[+-]?([1-9]\d?))?('.$constants.')\z/'; | ||||
foreach ($by_day as $value) { | foreach ($by_day as $key => $value) { | ||||
$matches = null; | $matches = null; | ||||
if (!preg_match($pattern, $value, $matches)) { | if (!preg_match($pattern, $value, $matches)) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'RRULE BYDAY value "%s" is invalid: rule part must be in the '. | 'RRULE BYDAY value "%s" is invalid: rule part must be in the '. | ||||
'expected form (like "MO", "-3TH", or "+2SU").', | 'expected form (like "MO", "-3TH", or "+2SU").', | ||||
$value)); | $value)); | ||||
} | } | ||||
// The maximum allowed value is 53, which corresponds to "the 53rd | // The maximum allowed value is 53, which corresponds to "the 53rd | ||||
// Monday every year" or similar when evaluated against a YEARLY rule. | // Monday every year" or similar when evaluated against a YEARLY rule. | ||||
$maximum = 53; | $maximum = 53; | ||||
$magnitude = (int)$matches[1]; | $magnitude = (int)$matches[1]; | ||||
if ($magnitude > $maximum) { | if ($magnitude > $maximum) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'RRULE BYDAY value "%s" has an offset with magnitude "%s", but '. | 'RRULE BYDAY value "%s" has an offset with magnitude "%s", but '. | ||||
'the maximum permitted value is "%s".', | 'the maximum permitted value is "%s".', | ||||
$value, | $value, | ||||
$magnitude, | $magnitude, | ||||
$maximum)); | $maximum)); | ||||
} | } | ||||
// Normalize "+3FR" into "3FR". | |||||
$by_day[$key] = ltrim($value, '+'); | |||||
} | } | ||||
$this->byDay = array_fuse($by_day); | $this->byDay = array_fuse($by_day); | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getByDay() { | public function getByDay() { | ||||
return $this->byDay; | return $this->byDay; | ||||
▲ Show 20 Lines • Show All 339 Lines • ▼ Show 20 Lines | protected function nextMonth() { | ||||
$by_setpos = $this->getBySetPosition(); | $by_setpos = $this->getBySetPosition(); | ||||
// If we have a BYMONTHDAY, we consider that set of days in every month. | // If we have a BYMONTHDAY, we consider that set of days in every month. | ||||
// For example, "FREQ=YEARLY;BYMONTHDAY=3" means "the third day of every | // For example, "FREQ=YEARLY;BYMONTHDAY=3" means "the third day of every | ||||
// month", so we need to expand the month set if the constraint is present. | // month", so we need to expand the month set if the constraint is present. | ||||
$by_monthday = $this->getByMonthDay(); | $by_monthday = $this->getByMonthDay(); | ||||
// Likewise, we need to generate all months if we have BYYEARDAY or | // Likewise, we need to generate all months if we have BYYEARDAY or | ||||
// BYWEEKNO. | // BYWEEKNO or BYDAY. | ||||
$by_yearday = $this->getByYearDay(); | $by_yearday = $this->getByYearDay(); | ||||
$by_weekno = $this->getByWeekNumber(); | $by_weekno = $this->getByWeekNumber(); | ||||
$by_day = $this->getByDay(); | |||||
while (!$this->setMonths) { | while (!$this->setMonths) { | ||||
$this->nextYear(); | $this->nextYear(); | ||||
$is_dynamic = $is_monthly | $is_dynamic = $is_monthly | ||||
|| $by_month | || $by_month | ||||
|| $by_monthday | || $by_monthday | ||||
|| $by_yearday | || $by_yearday | ||||
|| $by_weekno | || $by_weekno | ||||
|| $by_day | |||||
|| ($scale < self::SCALE_MONTHLY); | || ($scale < self::SCALE_MONTHLY); | ||||
if ($is_dynamic) { | if ($is_dynamic) { | ||||
$months = $this->newMonthsSet( | $months = $this->newMonthsSet( | ||||
($is_monthly ? $interval : 1), | ($is_monthly ? $interval : 1), | ||||
$by_month); | $by_month); | ||||
} else { | } else { | ||||
$months = array( | $months = array( | ||||
▲ Show 20 Lines • Show All 159 Lines • ▼ Show 20 Lines | if ($interval_week) { | ||||
$key = "{$month_idx}M{$day_idx}D"; | $key = "{$month_idx}M{$day_idx}D"; | ||||
$selection[] = $year_map['info'][$key]; | $selection[] = $year_map['info'][$key]; | ||||
$this->cursorDay += $interval_day; | $this->cursorDay += $interval_day; | ||||
} | } | ||||
} | } | ||||
$frequency = $this->getFrequency(); | |||||
$is_yearly = ($frequency == self::FREQUENCY_YEARLY); | |||||
$is_monthly = ($frequency == self::FREQUENCY_MONTHLY); | |||||
// As a special case, BYDAY applies to relative month offsets if BYMONTH | |||||
// is present in a YEARLY rule. | |||||
if ($is_yearly) { | |||||
if ($this->getByMonth()) { | |||||
$is_yearly = false; | |||||
$is_monthly = true; | |||||
} | |||||
} | |||||
$weeks = array(); | $weeks = array(); | ||||
foreach ($selection as $key => $info) { | foreach ($selection as $key => $info) { | ||||
if ($info['month'] != $this->stateMonth) { | if ($info['month'] != $this->stateMonth) { | ||||
continue; | continue; | ||||
} | } | ||||
if ($by_day) { | if ($by_day) { | ||||
// TODO: This only handles "BYDAY=MO,TU". It does not yet properly | |||||
// handle "BYDAY=+1FR" (e.g., the first Friday in the month). | |||||
if (empty($by_day[$info['weekday']])) { | if (empty($by_day[$info['weekday']])) { | ||||
if ($is_yearly) { | |||||
if (empty($by_day[$info['weekday.yearly']]) && | |||||
empty($by_day[$info['-weekday.yearly']])) { | |||||
continue; | |||||
} | |||||
} else if ($is_monthly) { | |||||
if (empty($by_day[$info['weekday.monthly']]) && | |||||
empty($by_day[$info['-weekday.monthly']])) { | |||||
continue; | |||||
} | |||||
} else { | |||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
} | |||||
if ($by_monthday) { | if ($by_monthday) { | ||||
if (empty($by_monthday[$info['monthday']]) && | if (empty($by_monthday[$info['monthday']]) && | ||||
empty($by_monthday[$info['-monthday']])) { | empty($by_monthday[$info['-monthday']])) { | ||||
continue; | continue; | ||||
} | } | ||||
} | } | ||||
▲ Show 20 Lines • Show All 104 Lines • ▼ Show 20 Lines | if ($first_week_size >= 4) { | ||||
$week_number = 0; | $week_number = 0; | ||||
} | } | ||||
$info_map = array(); | $info_map = array(); | ||||
$weekday_map = self::getWeekdayIndexMap(); | $weekday_map = self::getWeekdayIndexMap(); | ||||
$weekday_map = array_flip($weekday_map); | $weekday_map = array_flip($weekday_map); | ||||
$yearly_counts = array(); | |||||
$monthly_counts = array(); | |||||
$month_number = 1; | $month_number = 1; | ||||
$month_day = 1; | $month_day = 1; | ||||
for ($year_day = 1; $year_day <= $max_day; $year_day++) { | for ($year_day = 1; $year_day <= $max_day; $year_day++) { | ||||
$key = "{$month_number}M{$month_day}D"; | $key = "{$month_number}M{$month_day}D"; | ||||
$short_day = $weekday_map[$weekday]; | |||||
if (empty($yearly_counts[$short_day])) { | |||||
$yearly_counts[$short_day] = 0; | |||||
} | |||||
$yearly_counts[$short_day]++; | |||||
if (empty($monthly_counts[$month_number][$short_day])) { | |||||
$monthly_counts[$month_number][$short_day] = 0; | |||||
} | |||||
$monthly_counts[$month_number][$short_day]++; | |||||
$info = array( | $info = array( | ||||
'year' => $year, | 'year' => $year, | ||||
'key' => $key, | 'key' => $key, | ||||
'month' => $month_number, | 'month' => $month_number, | ||||
'monthday' => $month_day, | 'monthday' => $month_day, | ||||
'-monthday' => -$month_days[$month_number] + $month_day - 1, | '-monthday' => -$month_days[$month_number] + $month_day - 1, | ||||
'yearday' => $year_day, | 'yearday' => $year_day, | ||||
'-yearday' => -$max_day + $year_day - 1, | '-yearday' => -$max_day + $year_day - 1, | ||||
'week' => $week_number, | 'week' => $week_number, | ||||
'weekday' => $weekday_map[$weekday], | 'weekday' => $short_day, | ||||
'weekday.yearly' => $yearly_counts[$short_day], | |||||
'weekday.monthly' => $monthly_counts[$month_number][$short_day], | |||||
); | ); | ||||
$info_map[$key] = $info; | $info_map[$key] = $info; | ||||
$weekday = ($weekday + 1) % 7; | $weekday = ($weekday + 1) % 7; | ||||
if ($weekday === $weekday_index) { | if ($weekday === $weekday_index) { | ||||
$week_number++; | $week_number++; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | foreach ($info_map as $key => $info) { | ||||
// If this day is part of the last partial week of the year, give | // If this day is part of the last partial week of the year, give | ||||
// it week numbers from the next year. | // it week numbers from the next year. | ||||
$info['week'] = 1; | $info['week'] = 1; | ||||
$info['-week'] = -$next_year_weeks; | $info['-week'] = -$next_year_weeks; | ||||
} else { | } else { | ||||
$info['-week'] = -$week_number + $week - 1; | $info['-week'] = -$week_number + $week - 1; | ||||
} | } | ||||
// Do all the arithmetic to figure out if this is the -19th Thursday | |||||
// in the year and such. | |||||
$month_number = $info['month']; | |||||
$short_day = $info['weekday']; | |||||
$monthly_count = $monthly_counts[$month_number][$short_day]; | |||||
$monthly_index = $info['weekday.monthly']; | |||||
$info['-weekday.monthly'] = -$monthly_count + $monthly_index - 1; | |||||
$info['-weekday.monthly'] .= $short_day; | |||||
$info['weekday.monthly'] .= $short_day; | |||||
$yearly_count = $yearly_counts[$short_day]; | |||||
$yearly_index = $info['weekday.yearly']; | |||||
$info['-weekday.yearly'] = -$yearly_count + $yearly_index - 1; | |||||
$info['-weekday.yearly'] .= $short_day; | |||||
$info['weekday.yearly'] .= $short_day; | |||||
$info_map[$key] = $info; | $info_map[$key] = $info; | ||||
} | } | ||||
return array( | return array( | ||||
'info' => $info_map, | 'info' => $info_map, | ||||
'weekCount' => $week_number, | 'weekCount' => $week_number, | ||||
'dayCount' => $max_day, | 'dayCount' => $max_day, | ||||
'monthDays' => $month_days, | 'monthDays' => $month_days, | ||||
▲ Show 20 Lines • Show All 89 Lines • Show Last 20 Lines |