Skip to content

Commit 88ac2e2

Browse files
committed
Backport of sabre-io#716 -- support RDATE together with RRULE.
1 parent 900266b commit 88ac2e2

File tree

2 files changed

+82
-41
lines changed

2 files changed

+82
-41
lines changed

lib/Recur/EventIterator.php

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Sabre\VObject\Recur;
44

5+
use AppendIterator;
56
use DateTimeImmutable;
67
use DateTimeInterface;
78
use DateTimeZone;
@@ -167,18 +168,25 @@ public function __construct($input, $uid = null, ?DateTimeZone $timeZone = null)
167168
$this->eventDuration = 0;
168169
}
169170

171+
$this->recurIterators = [];
172+
if (isset($this->masterEvent->RRULE)) {
173+
foreach ($this->masterEvent->RRULE as $rRule) {
174+
$this->recurIterators[] = new RRuleIterator(
175+
$this->masterEvent->RRULE->getParts(),
176+
$this->startDate
177+
);
178+
}
179+
}
170180
if (isset($this->masterEvent->RDATE)) {
171-
$this->recurIterator = new RDateIterator(
172-
$this->masterEvent->RDATE->getParts(),
173-
$this->startDate
174-
);
175-
} elseif (isset($this->masterEvent->RRULE)) {
176-
$this->recurIterator = new RRuleIterator(
177-
$this->masterEvent->RRULE->getParts(),
178-
$this->startDate
179-
);
180-
} else {
181-
$this->recurIterator = new RRuleIterator(
181+
foreach ($this->masterEvent->RDATE as $rDate) {
182+
$this->recurIterators[] = new RDateIterator(
183+
$rDate->getParts(),
184+
$this->startDate,
185+
);
186+
}
187+
}
188+
if (empty($this->recurIterators)) {
189+
$this->recurIterators[] = new RRuleIterator(
182190
[
183191
'FREQ' => 'DAILY',
184192
'COUNT' => 1,
@@ -317,7 +325,9 @@ public function valid()
317325
#[\ReturnTypeWillChange]
318326
public function rewind()
319327
{
320-
$this->recurIterator->rewind();
328+
foreach ($this->recurIterators as $iterator) {
329+
$iterator->rewind();
330+
}
321331
// re-creating overridden event index.
322332
$index = [];
323333
foreach ($this->overriddenEvents as $key => $event) {
@@ -332,6 +342,15 @@ public function rewind()
332342
$this->nextDate = null;
333343
$this->currentDate = clone $this->startDate;
334344

345+
$this->currentCandidates = [];
346+
foreach ($this->recurIterators as $index => $iterator) {
347+
if (!$iterator->valid()) {
348+
continue;
349+
}
350+
$this->currentCandidates[$index] = $iterator->current()->getTimeStamp();
351+
}
352+
asort($this->currentCandidates);
353+
335354
$this->next();
336355
}
337356

@@ -354,13 +373,30 @@ public function next()
354373
// We need to do this until we find a date that's not in the
355374
// exception list.
356375
do {
357-
if (!$this->recurIterator->valid()) {
376+
if (empty($this->currentCandidates)) {
358377
$nextDate = null;
359378
break;
360379
}
361-
$nextDate = $this->recurIterator->current();
362-
$this->recurIterator->next();
363-
} while (isset($this->exceptions[$nextDate->getTimeStamp()]));
380+
$nextIndex = array_key_first($this->currentCandidates);
381+
$nextDate = $this->recurIterators[$nextIndex]->current();
382+
$nextStamp = $this->currentCandidates[$nextIndex];
383+
384+
// advance all iterators which match the current timestamp
385+
foreach ($this->currentCandidates as $index => $stamp) {
386+
if ($stamp > $nextStamp) {
387+
break;
388+
}
389+
$iterator = $this->recurIterators[$index];
390+
$iterator->next();
391+
if ($iterator->valid()) {
392+
$this->currentCandidates[$index] = $iterator->current()->getTimeStamp();
393+
asort($this->currentCandidates);
394+
} else {
395+
unset($this->currentCandidates[$index]);
396+
// resort not neccessary
397+
}
398+
}
399+
} while (isset($this->exceptions[$nextStamp]));
364400
}
365401

366402
// $nextDate now contains what rrule thinks is the next one, but an
@@ -408,15 +444,27 @@ public function fastForward(DateTimeInterface $dateTime)
408444
*/
409445
public function isInfinite()
410446
{
411-
return $this->recurIterator->isInfinite();
447+
foreach ($this->recurIterators as $iterator) {
448+
if ($iterator->isInfinite()) {
449+
return true;
450+
}
451+
}
452+
return false;
412453
}
413454

414455
/**
415-
* RRULE parser.
456+
* Array of RRULE parsers.
457+
*
458+
* @var array<int, RRuleIterator>
459+
*/
460+
protected $recurIterators;
461+
462+
/**
463+
* Array of current candidate timestamps.
416464
*
417-
* @var RRuleIterator
465+
* @var array<int, int>
418466
*/
419-
protected $recurIterator;
467+
protected $currentCandidates;
420468

421469
/**
422470
* The duration, in seconds, of the master event.

lib/Recur/RDateIterator.php

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Sabre\VObject\Recur;
44

5+
use DateTimeImmutable;
56
use DateTimeInterface;
67
use Iterator;
78
use Sabre\VObject\DateTimeParser;
@@ -30,7 +31,8 @@ public function __construct($rrule, DateTimeInterface $start)
3031
{
3132
$this->startDate = $start;
3233
$this->parseRDate($rrule);
33-
$this->currentDate = clone $this->startDate;
34+
array_unshift($this->dates, DateTimeImmutable::createFromInterface($this->startDate));
35+
$this->rewind();
3436
}
3537

3638
/* Implementation of the Iterator interface {{{ */
@@ -39,10 +41,16 @@ public function __construct($rrule, DateTimeInterface $start)
3941
public function current()
4042
{
4143
if (!$this->valid()) {
42-
return;
44+
return null;
4345
}
44-
45-
return clone $this->currentDate;
46+
if (is_string($this->dates[$this->counter])) {
47+
$this->dates[$this->counter] =
48+
DateTimeParser::parse(
49+
$this->dates[$this->counter],
50+
$this->startDate->getTimezone()
51+
);
52+
}
53+
return $this->dates[$this->counter];
4654
}
4755

4856
/**
@@ -65,7 +73,7 @@ public function key()
6573
#[\ReturnTypeWillChange]
6674
public function valid()
6775
{
68-
return $this->counter <= count($this->dates);
76+
return $this->counter < count($this->dates);
6977
}
7078

7179
/**
@@ -76,7 +84,6 @@ public function valid()
7684
#[\ReturnTypeWillChange]
7785
public function rewind()
7886
{
79-
$this->currentDate = clone $this->startDate;
8087
$this->counter = 0;
8188
}
8289

@@ -92,12 +99,6 @@ public function next()
9299
if (!$this->valid()) {
93100
return;
94101
}
95-
96-
$this->currentDate =
97-
DateTimeParser::parse(
98-
$this->dates[$this->counter - 1],
99-
$this->startDate->getTimezone()
100-
);
101102
}
102103

103104
/* End of Iterator implementation }}} */
@@ -118,7 +119,7 @@ public function isInfinite()
118119
*/
119120
public function fastForward(DateTimeInterface $dt)
120121
{
121-
while ($this->valid() && $this->currentDate < $dt) {
122+
while ($this->valid() && $this->current() < $dt) {
122123
$this->next();
123124
}
124125
}
@@ -132,14 +133,6 @@ public function fastForward(DateTimeInterface $dt)
132133
*/
133134
protected $startDate;
134135

135-
/**
136-
* The date of the current iteration. You can get this by calling
137-
* ->current().
138-
*
139-
* @var DateTimeInterface
140-
*/
141-
protected $currentDate;
142-
143136
/**
144137
* The current item in the list.
145138
*

0 commit comments

Comments
 (0)