* * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace Eluceo\iCal\Property\Event; use Eluceo\iCal\ParameterBag; use Eluceo\iCal\Property\ValueInterface; use InvalidArgumentException; /** * Implementation of Recurrence Rule. * * @see https://tools.ietf.org/html/rfc5545#section-3.8.5.3 */ class RecurrenceRule implements ValueInterface { const FREQ_YEARLY = 'YEARLY'; const FREQ_MONTHLY = 'MONTHLY'; const FREQ_WEEKLY = 'WEEKLY'; const FREQ_DAILY = 'DAILY'; const FREQ_HOURLY = 'HOURLY'; const FREQ_MINUTELY = 'MINUTELY'; const FREQ_SECONDLY = 'SECONDLY'; const WEEKDAY_SUNDAY = 'SU'; const WEEKDAY_MONDAY = 'MO'; const WEEKDAY_TUESDAY = 'TU'; const WEEKDAY_WEDNESDAY = 'WE'; const WEEKDAY_THURSDAY = 'TH'; const WEEKDAY_FRIDAY = 'FR'; const WEEKDAY_SATURDAY = 'SA'; /** * The frequency of an Event. * * @var string */ protected $freq = self::FREQ_YEARLY; /** * BYSETPOS must require use of other BY*. * * @var bool */ protected $canUseBySetPos = false; /** * @var int|null */ protected $interval = 1; /** * @var int|null */ protected $count = null; /** * @var \DateTimeInterface|null */ protected $until = null; /** * @var string|null */ protected $wkst; /** * @var array|null */ protected $bySetPos = null; /** * @var string|null */ protected $byMonth; /** * @var string|null */ protected $byWeekNo; /** * @var string|null */ protected $byYearDay; /** * @var string|null */ protected $byMonthDay; /** * @var string|null */ protected $byDay; /** * @var string|null */ protected $byHour; /** * @var string|null */ protected $byMinute; /** * @var string|null */ protected $bySecond; public function getEscapedValue(): string { return $this->buildParameterBag()->toString(); } /** * @return ParameterBag */ protected function buildParameterBag() { $parameterBag = new ParameterBag(); $parameterBag->setParam('FREQ', $this->freq); if (null !== $this->interval) { $parameterBag->setParam('INTERVAL', $this->interval); } if (null !== $this->count) { $parameterBag->setParam('COUNT', $this->count); } if (null != $this->until) { $parameterBag->setParam('UNTIL', $this->until->format('Ymd\THis\Z')); } if (null !== $this->wkst) { $parameterBag->setParam('WKST', $this->wkst); } if (null !== $this->bySetPos && $this->canUseBySetPos) { $parameterBag->setParam('BYSETPOS', $this->bySetPos); } if (null !== $this->byMonth) { $parameterBag->setParam('BYMONTH', explode(',', $this->byMonth)); } if (null !== $this->byWeekNo) { $parameterBag->setParam('BYWEEKNO', explode(',', $this->byWeekNo)); } if (null !== $this->byYearDay) { $parameterBag->setParam('BYYEARDAY', explode(',', $this->byYearDay)); } if (null !== $this->byMonthDay) { $parameterBag->setParam('BYMONTHDAY', explode(',', $this->byMonthDay)); } if (null !== $this->byDay) { $parameterBag->setParam('BYDAY', explode(',', $this->byDay)); } if (null !== $this->byHour) { $parameterBag->setParam('BYHOUR', explode(',', $this->byHour)); } if (null !== $this->byMinute) { $parameterBag->setParam('BYMINUTE', explode(',', $this->byMinute)); } if (null !== $this->bySecond) { $parameterBag->setParam('BYSECOND', explode(',', $this->bySecond)); } return $parameterBag; } /** * @param int|null $count * * @return $this */ public function setCount($count) { $this->count = $count; return $this; } /** * @return int|null */ public function getCount() { return $this->count; } /** * @return $this */ public function setUntil(\DateTimeInterface $until = null) { $this->until = $until; return $this; } /** * @return \DateTimeInterface|null */ public function getUntil() { return $this->until; } /** * The FREQ rule part identifies the type of recurrence rule. This * rule part MUST be specified in the recurrence rule. Valid values * include. * * SECONDLY, to specify repeating events based on an interval of a second or more; * MINUTELY, to specify repeating events based on an interval of a minute or more; * HOURLY, to specify repeating events based on an interval of an hour or more; * DAILY, to specify repeating events based on an interval of a day or more; * WEEKLY, to specify repeating events based on an interval of a week or more; * MONTHLY, to specify repeating events based on an interval of a month or more; * YEARLY, to specify repeating events based on an interval of a year or more. * * @param string $freq * * @return $this * * @throws \InvalidArgumentException */ public function setFreq($freq) { if (@constant('static::FREQ_' . $freq) !== null) { $this->freq = $freq; } else { throw new \InvalidArgumentException("The Frequency {$freq} is not supported."); } return $this; } /** * @return string */ public function getFreq() { return $this->freq; } /** * The INTERVAL rule part contains a positive integer representing at * which intervals the recurrence rule repeats. * * @param int|null $interval * * @return $this */ public function setInterval($interval) { $this->interval = $interval; return $this; } /** * @return int|null */ public function getInterval() { return $this->interval; } /** * The WKST rule part specifies the day on which the workweek starts. * Valid values are MO, TU, WE, TH, FR, SA, and SU. * * @param string $value * * @return $this */ public function setWkst($value) { $this->wkst = $value; return $this; } /** * The BYSETPOS filters one interval of events by the specified position. * A positive position will start from the beginning and go forward while * a negative position will start at the end and move backward. * * Valid values are a comma separated string or an array of integers * from 1 to 366 or negative integers from -1 to -366. * * @param int|string|array|null $value * * @throws InvalidArgumentException * * @return $this */ public function setBySetPos($value) { if (null === $value) { $this->bySetPos = $value; return $this; } if (!(is_string($value) || is_array($value) || is_int($value))) { throw new InvalidArgumentException('Invalid value for BYSETPOS'); } $list = $value; if (is_int($value)) { if ($value === 0 || $value < -366 || $value > 366) { throw new InvalidArgumentException('Invalid value for BYSETPOS'); } $this->bySetPos = [$value]; return $this; } if (is_string($value)) { $list = explode(',', $value); } $output = []; foreach ($list as $item) { if (is_string($item)) { if (!preg_match('/^ *-?[0-9]* *$/', $item)) { throw new InvalidArgumentException('Invalid value for BYSETPOS'); } $item = intval($item); } if (!is_int($item) || $item === 0 || $item < -366 || $item > 366) { throw new InvalidArgumentException('Invalid value for BYSETPOS'); } $output[] = $item; } $this->bySetPos = $output; return $this; } /** * The BYMONTH rule part specifies a COMMA-separated list of months of the year. * Valid values are 1 to 12. * * @param int $month * * @throws \InvalidArgumentException * * @return $this */ public function setByMonth($month) { if (!is_integer($month) || $month <= 0 || $month > 12) { throw new InvalidArgumentException('Invalid value for BYMONTH'); } $this->byMonth = $month; $this->canUseBySetPos = true; return $this; } /** * The BYWEEKNO rule part specifies a COMMA-separated list of ordinals specifying weeks of the year. * Valid values are 1 to 53 or -53 to -1. * * @param int $value * * @throws \InvalidArgumentException * * @return $this */ public function setByWeekNo($value) { if (!is_integer($value) || $value > 53 || $value < -53 || $value === 0) { throw new InvalidArgumentException('Invalid value for BYWEEKNO'); } $this->byWeekNo = $value; $this->canUseBySetPos = true; return $this; } /** * The BYYEARDAY rule part specifies a COMMA-separated list of days of the year. * Valid values are 1 to 366 or -366 to -1. * * @param int $day * * @throws \InvalidArgumentException * * @return $this */ public function setByYearDay($day) { if (!is_integer($day) || $day > 366 || $day < -366 || $day === 0) { throw new InvalidArgumentException('Invalid value for BYYEARDAY'); } $this->byYearDay = $day; $this->canUseBySetPos = true; return $this; } /** * The BYMONTHDAY rule part specifies a COMMA-separated list of days of the month. * Valid values are 1 to 31 or -31 to -1. * * @param int $day * * @return $this * * @throws \InvalidArgumentException */ public function setByMonthDay($day) { if (!is_integer($day) || $day > 31 || $day < -31 || $day === 0) { throw new InvalidArgumentException('Invalid value for BYMONTHDAY'); } $this->byMonthDay = $day; $this->canUseBySetPos = true; return $this; } /** * The BYDAY rule part specifies a COMMA-separated list of days of the week;. * * SU indicates Sunday; MO indicates Monday; TU indicates Tuesday; * WE indicates Wednesday; TH indicates Thursday; FR indicates Friday; and SA indicates Saturday. * * Each BYDAY value can also be preceded by a positive (+n) or negative (-n) integer. * If present, this indicates the nth occurrence of a specific day within the MONTHLY or YEARLY "RRULE". * * @return $this */ public function setByDay(string $day) { $this->byDay = $day; $this->canUseBySetPos = true; return $this; } /** * The BYHOUR rule part specifies a COMMA-separated list of hours of the day. * Valid values are 0 to 23. * * @param int $value * * @return $this * * @throws \InvalidArgumentException */ public function setByHour($value) { if (!is_integer($value) || $value < 0 || $value > 23) { throw new \InvalidArgumentException('Invalid value for BYHOUR'); } $this->byHour = $value; $this->canUseBySetPos = true; return $this; } /** * The BYMINUTE rule part specifies a COMMA-separated list of minutes within an hour. * Valid values are 0 to 59. * * @param int $value * * @return $this * * @throws \InvalidArgumentException */ public function setByMinute($value) { if (!is_integer($value) || $value < 0 || $value > 59) { throw new \InvalidArgumentException('Invalid value for BYMINUTE'); } $this->byMinute = $value; $this->canUseBySetPos = true; return $this; } /** * The BYSECOND rule part specifies a COMMA-separated list of seconds within a minute. * Valid values are 0 to 60. * * @param int $value * * @return $this * * @throws \InvalidArgumentException */ public function setBySecond($value) { if (!is_integer($value) || $value < 0 || $value > 60) { throw new \InvalidArgumentException('Invalid value for BYSECOND'); } $this->bySecond = $value; $this->canUseBySetPos = true; return $this; } }