View source
<?php
namespace Drupal\date_recur;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Component\Datetime\DateTimePlus;
use RRule\RRule;
use RRule\RfcParser;
use RRule\RSet;
class DateRecurRRule implements \Iterator {
const RFC_DATE_FORMAT = 'Ymd\\THis\\Z';
protected $rrule;
protected $startDate;
protected $startDateEnd;
protected $recurTime;
protected $recurDiff;
protected $originalRuleString;
protected $parts;
protected $setParts;
protected $occurrences;
protected $timezone;
protected $timezoneOffset;
public function __construct($rrule, $startDate, $startDateEnd = NULL, $timezone = NULL) {
$this->originalRuleString = $rrule;
$this->startDate = $startDate;
$this->recurTime = $this->startDate
->format('H:i');
if (empty($startDateEnd)) {
$startDateEnd = clone $startDate;
}
$this->startDateEnd = $startDateEnd;
$this->recurDiff = $this->startDate
->diff($startDateEnd);
$this::parseRrule($rrule, $startDate);
$this->rrule = new DateRecurDefaultRSet();
$this->rrule
->addRRule(new DateRecurDefaultRRule($this->parts));
if (!empty($this->setParts)) {
foreach ($this->setParts as $type => $type_parts) {
foreach ($type_parts as $part) {
list(, $part) = explode(':', $part);
switch ($type) {
case 'RDATE':
$this->rrule
->addDate($part);
break;
case 'EXDATE':
$this->rrule
->addExDate($part);
break;
case 'EXRULE':
$this->rrule
->addExRule($part);
}
}
}
}
if ($timezone) {
$this->timezone = $timezone;
$start = clone $this->startDate;
$start
->setTimezone(new \DateTimeZone($this->timezone));
$this->timezoneOffset = $start
->getOffset();
$this->rrule
->setTimezoneOffset($this->timezoneOffset);
}
}
public function getTimezoneOffset() {
if (!empty($this->timezoneOffset)) {
return $this->timezoneOffset;
}
return FALSE;
}
public function getParts() {
return $this->parts;
}
public function parseRrule($rrule, $startDate, $check_only = FALSE) {
if (strpos($rrule, "\n") === FALSE && strpos($rrule, 'RRULE:') !== 0) {
$rrule = "RRULE:{$rrule}";
}
$dtstart = 'DTSTART:' . $startDate
->format(self::RFC_DATE_FORMAT);
$set_keys = [
'RDATE',
'EXRULE',
'EXDATE',
];
$rules = $set_parts = [];
foreach (explode("\n", $rrule) as $key => $part) {
$els = explode(':', $part);
if (in_array($els[0], $set_keys)) {
$set_parts[$els[0]][] = $part;
}
else {
if ($els[0] == 'RRULE') {
$rules[] = $part;
}
else {
if ($els[0] == 'DTSTART') {
$dtstart = $part;
}
else {
throw new \InvalidArgumentException("Unsupported line: " . $part);
}
}
}
}
if (!count($rules)) {
throw new \InvalidArgumentException("Missing RRULE line: " . $rrule);
}
if (count($rules) > 1) {
throw new \InvalidArgumentException("More than one RRULE line is not supported.");
}
$rrule = $dtstart . "\n" . $rules[0];
if (empty($parts['WKST'])) {
$parts['WKST'] = 'MO';
}
$this->parts = RfcParser::parseRRule($rrule);
$this->setParts = $set_parts;
}
public static function validateRule($rrule, $startDate) {
$rule = new self($rrule, $startDate);
return TRUE;
}
public function getOccurrences($start = NULL, $end = NULL, $num = NULL) {
return $this
->createOccurrences($start, $end, $num);
}
public function getOccurrencesBetween($start, $end) {
return $this
->getOccurrences($start, $end);
}
public function getNextOccurrences($start, $num) {
return $this
->getOccurrences($start, NULL, $num);
}
public function isInfinite() {
return $this->rrule
->isInfinite();
}
public function getOccurrencesForCacheStorage(\DateTime $until, $storageFormat) {
$occurrences = [];
if (!$this->rrule
->isInfinite()) {
$occurrences += $this
->createOccurrences(NULL, NULL, NULL, FALSE);
}
else {
$occurrences += $this
->createOccurrences(NULL, $until, NULL, FALSE);
}
foreach ($occurrences as &$row) {
foreach ($row as $key => $date) {
if (!empty($date)) {
$row[$key] = self::massageDateValueForStorage($date, $storageFormat);
}
}
}
return $occurrences;
}
protected function createOccurrences($start = NULL, $end = NULL, $num = NULL, $display = TRUE) {
if ($this->rrule
->isInfinite() && $end === NULL && $num === NULL) {
throw new \LogicException('Cannot get all occurrences of an infinite recurrence rule.');
}
$occurrences = [];
foreach ($this->rrule as $occurrence) {
if ($start !== NULL && $occurrence < $start) {
continue;
}
if ($end !== NULL && $occurrence > $end) {
break;
}
if ($num !== NULL && count($occurrences) >= $num) {
break;
}
$occurrences[] = $this
->massageOccurrence($occurrence, $display);
}
return $occurrences;
}
protected function massageOccurrence(\DateTime $occurrence, $display = TRUE) {
$date = DrupalDateTime::createFromFormat('Ymd H:i', $occurrence
->format('Ymd') . ' ' . $this->recurTime, $this->startDate
->getTimezone());
if ($display) {
$date = $this
->adjustDateForDisplay($date);
}
$date_end = clone $date;
if (!empty($this->recurDiff)) {
$date_end = $date_end
->add($this->recurDiff);
}
return [
'value' => $date,
'end_value' => $date_end,
];
}
public function adjustDateForDisplay($date) {
if (empty($this->timezone)) {
return $date;
}
return $date
->setTimezone(new \DateTimeZone($this->timezone));
}
public static function massageDateValueForStorage($date, $format) {
if ($format == DATETIME_DATE_STORAGE_FORMAT) {
datetime_date_default_time($date);
}
$date
->setTimezone(new \DateTimeZone(DATETIME_STORAGE_TIMEZONE));
return $date
->format($format);
}
public function getWeekdays() {
$weekdays = [];
$byday = $this->parts['BYDAY'];
if (!is_array($byday)) {
$byday = explode(',', $byday);
}
$this->byweekday = array();
$this->byweekday_nth = array();
foreach ($byday as $value) {
$value = trim(strtoupper($value));
$valid = preg_match('/^([+-]?[0-9]+)?([A-Z]{2})$/', $value, $matches);
if (!empty($matches[2])) {
$day = RRule::$week_days[$matches[2]];
$weekdays[$day] = $day;
}
}
return $weekdays;
if (!empty($this->parts['BYDAY'])) {
$days = explode(',', $this->parts['BYDAY']);
foreach ($days as $day) {
$day = preg_replace('/[\\+\\-0-9]*/', '', $day);
$weekdays[$day] = $day;
}
}
return $weekdays;
}
public function humanReadable() {
return $this->rrule
->humanReadable();
}
public function current() {
if ($date = $this->rrule
->current()) {
return $this
->massageOccurrence($date);
}
}
public function next() {
if ($date = $this->rrule
->next()) {
return $this
->massageOccurrence($date);
}
}
public function key() {
return $this->rrule
->key();
}
public function valid() {
return $this->rrule
->valid();
}
public function rewind() {
$this->rrule
->rewind();
}
}