View source
<?php
declare (strict_types=1);
namespace Drupal\date_recur\Rl;
use Drupal\date_recur\DateRange;
use Drupal\date_recur\DateRecurHelperInterface;
use Drupal\date_recur\Exception\DateRecurHelperArgumentException;
use RRule\RfcParser;
use RRule\RRule;
use RRule\RSet;
class RlHelper implements DateRecurHelperInterface {
protected $set;
protected $timeZone;
protected $recurDiff;
public function __construct(string $string, \DateTimeInterface $dtStart, ?\DateTimeInterface $dtStartEnd = NULL) {
$dtStartEnd = $dtStartEnd ?? clone $dtStart;
$this->recurDiff = $dtStart
->diff($dtStartEnd);
$this->timeZone = $dtStart
->getTimezone();
if (strpos($string, "\n") === FALSE && strpos($string, 'RRULE:') !== 0) {
$string = "RRULE:{$string}";
}
$parts = [
'RRULE' => [],
'RDATE' => [],
'EXRULE' => [],
'EXDATE' => [],
];
$lines = explode("\n", $string);
foreach ($lines as $n => $line) {
$line = trim($line);
if (FALSE === strpos($line, ':')) {
throw new DateRecurHelperArgumentException(sprintf('Multiline RRULE must be prefixed with either: RRULE, EXDATE, EXRULE, or RDATE. Missing for line %s', $n + 1));
}
[
$part,
$partValue,
] = explode(':', $line, 2);
if (!isset($parts[$part])) {
throw new DateRecurHelperArgumentException("Unsupported line: " . $part);
}
$parts[$part][] = $partValue;
}
if (($count = count($parts['RRULE'])) !== 1) {
throw new DateRecurHelperArgumentException(sprintf('One RRULE must be provided. %d provided.', $count));
}
$this->set = new RSet();
foreach ($parts as $type => $values) {
foreach ($values as $value) {
switch ($type) {
case 'RRULE':
$this->set
->addRRule(new RRule($value, $dtStart));
break;
case 'RDATE':
$dates = RfcParser::parseRDate('RDATE:' . $value);
array_walk($dates, function (\DateTimeInterface $value) : void {
$this->set
->addDate($value);
});
break;
case 'EXDATE':
$dates = RfcParser::parseExDate('EXDATE:' . $value);
array_walk($dates, function (\DateTimeInterface $value) : void {
$this->set
->addExDate($value);
});
break;
case 'EXRULE':
$this->set
->addExRule($value);
}
}
}
}
public static function createInstance(string $string, \DateTimeInterface $dtStart, ?\DateTimeInterface $dtStartEnd = NULL) : DateRecurHelperInterface {
return new static($string, $dtStart, $dtStartEnd);
}
public function getRules() : array {
return array_map(function (RRule $rule) : RlDateRecurRule {
$parts = array_filter($rule
->getRule());
return new RlDateRecurRule($parts);
}, $this->set
->getRRules());
}
public function isInfinite() : bool {
return $this->set
->isInfinite();
}
public function generateOccurrences(?\DateTimeInterface $rangeStart = NULL, ?\DateTimeInterface $rangeEnd = NULL) : \Generator {
foreach ($this->set as $occurrenceStart) {
$occurrenceEnd = clone $occurrenceStart;
$occurrenceEnd
->add($this->recurDiff);
if ($rangeStart) {
if ($occurrenceStart < $rangeStart && $occurrenceEnd < $rangeStart) {
continue;
}
}
if ($rangeEnd) {
if ($occurrenceStart > $rangeEnd && $occurrenceEnd > $rangeEnd) {
break;
}
}
(yield new DateRange($occurrenceStart, $occurrenceEnd));
}
}
public function getOccurrences(\DateTimeInterface $rangeStart = NULL, ?\DateTimeInterface $rangeEnd = NULL, ?int $limit = NULL) : array {
if ($this
->isInfinite() && !isset($rangeEnd) && !isset($limit)) {
throw new \InvalidArgumentException('An infinite rule must have a date or count limit.');
}
$generator = $this
->generateOccurrences($rangeStart, $rangeEnd);
if (isset($limit)) {
if (!is_int($limit) || $limit < 0) {
throw new \InvalidArgumentException('Invalid count limit.');
}
$occurrences = [];
foreach ($generator as $value) {
if (count($occurrences) >= $limit) {
break;
}
$occurrences[] = $value;
}
return $occurrences;
}
return iterator_to_array($generator);
}
public function getExcluded() : array {
return array_map(function (\DateTime $date) : \DateTime {
return $date
->setTimezone($this->timeZone);
}, $this->set
->getExDates());
}
public function current() : DateRange {
$occurrenceStart = $this->set
->current();
$occurrenceEnd = clone $occurrenceStart;
$occurrenceEnd
->add($this->recurDiff);
return new DateRange($occurrenceStart, $occurrenceEnd);
}
public function next() : void {
$this->set
->next();
}
public function key() : ?int {
return $this->set
->key();
}
public function valid() : bool {
return $this->set
->valid();
}
public function rewind() : void {
$this->set
->rewind();
}
public function getRlRuleset() : RSet {
return $this->set;
}
}