Skip to content

Commit 7453379

Browse files
committed
Backport of sabre-io/vobject#716 -- supprot RDATE together with RRULE.
1 parent 8772d85 commit 7453379

2 files changed

Lines changed: 85 additions & 40 deletions

File tree

sabre/vobject/lib/Recur/EventIterator.php

Lines changed: 68 additions & 18 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,22 +168,31 @@ public function __construct($input, $uid = null, ?DateTimeZone $timeZone = null)
167168
$this->eventDuration = 0;
168169
}
169170

171+
$this->recurIterators = [];
172+
$isRecurring = false;
173+
if (isset($this->masterEvent->RRULE)) {
174+
foreach ($this->masterEvent->RRULE as $rRule) {
175+
$this->recurIterators[] = new RRuleIterator(
176+
$this->masterEvent->RRULE->getParts(),
177+
$this->startDate
178+
);
179+
}
180+
$isRecurring = true;
181+
}
170182
if (isset($this->masterEvent->RDATE)) {
171183
$rdateValues = [];
172184
foreach ($this->masterEvent->RDATE as $rdate) {
173185
$rdateValues = array_merge($rdateValues, $rdate->getParts());
174186
}
175-
$this->recurIterator = new RDateIterator(
187+
$this->recurIterators[] = new RDateIterator(
176188
$rdateValues,
177-
$this->startDate
178-
);
179-
} elseif (isset($this->masterEvent->RRULE)) {
180-
$this->recurIterator = new RRuleIterator(
181-
$this->masterEvent->RRULE->getParts(),
182-
$this->startDate
189+
$this->startDate,
190+
omitStart: $isRecurring
183191
);
184-
} else {
185-
$this->recurIterator = new RRuleIterator(
192+
$isRecurring = true;
193+
}
194+
if (!$isRecurring) {
195+
$this->recurIterators[] = new RRuleIterator(
186196
[
187197
'FREQ' => 'DAILY',
188198
'COUNT' => 1,
@@ -321,7 +331,9 @@ public function valid()
321331
#[\ReturnTypeWillChange]
322332
public function rewind()
323333
{
324-
$this->recurIterator->rewind();
334+
foreach ($this->recurIterators as $iterator) {
335+
$iterator->rewind();
336+
}
325337
// re-creating overridden event index.
326338
$index = [];
327339
foreach ($this->overriddenEvents as $key => $event) {
@@ -336,6 +348,15 @@ public function rewind()
336348
$this->nextDate = null;
337349
$this->currentDate = clone $this->startDate;
338350

351+
$this->currentCandidates = [];
352+
foreach ($this->recurIterators as $index => $iterator) {
353+
if (!$iterator->valid()) {
354+
continue;
355+
}
356+
$this->currentCandidates[$index] = $iterator->current()->getTimeStamp();
357+
}
358+
asort($this->currentCandidates);
359+
339360
$this->next();
340361
}
341362

@@ -358,13 +379,30 @@ public function next()
358379
// We need to do this until we find a date that's not in the
359380
// exception list.
360381
do {
361-
if (!$this->recurIterator->valid()) {
382+
if (empty($this->currentCandidates)) {
362383
$nextDate = null;
363384
break;
364385
}
365-
$nextDate = $this->recurIterator->current();
366-
$this->recurIterator->next();
367-
} while (isset($this->exceptions[$nextDate->getTimeStamp()]));
386+
$nextIndex = array_key_first($this->currentCandidates);
387+
$nextDate = $this->recurIterators[$nextIndex]->current();
388+
$nextStamp = $this->currentCandidates[$nextIndex];
389+
390+
// advance all iterators which match the current timestamp
391+
foreach ($this->currentCandidates as $index => $stamp) {
392+
if ($stamp > $nextStamp) {
393+
break;
394+
}
395+
$iterator = $this->recurIterators[$index];
396+
$iterator->next();
397+
if ($iterator->valid()) {
398+
$this->currentCandidates[$index] = $iterator->current()->getTimeStamp();
399+
asort($this->currentCandidates);
400+
} else {
401+
unset($this->currentCandidates[$index]);
402+
// resort not neccessary
403+
}
404+
}
405+
} while (isset($this->exceptions[$nextStamp]));
368406
}
369407

370408
// $nextDate now contains what rrule thinks is the next one, but an
@@ -412,15 +450,27 @@ public function fastForward(DateTimeInterface $dateTime)
412450
*/
413451
public function isInfinite()
414452
{
415-
return $this->recurIterator->isInfinite();
453+
foreach ($this->recurIterators as $iterator) {
454+
if ($iterator->isInfinite()) {
455+
return true;
456+
}
457+
}
458+
return false;
416459
}
417460

418461
/**
419-
* RRULE parser.
462+
* Array of RRULE parsers.
463+
*
464+
* @var array<int, RRuleIterator>
465+
*/
466+
protected $recurIterators;
467+
468+
/**
469+
* Array of current candidate timestamps.
420470
*
421-
* @var RRuleIterator
471+
* @var array<int, int>
422472
*/
423-
protected $recurIterator;
473+
protected $currentCandidates;
424474

425475
/**
426476
* The duration, in seconds, of the master event.

sabre/vobject/lib/Recur/RDateIterator.php

Lines changed: 17 additions & 22 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;
@@ -26,11 +27,14 @@ class RDateIterator implements Iterator
2627
*
2728
* @param string|array $rrule
2829
*/
29-
public function __construct($rrule, DateTimeInterface $start)
30+
public function __construct($rrule, DateTimeInterface $start, bool $omitStart = false)
3031
{
3132
$this->startDate = $start;
3233
$this->parseRDate($rrule);
33-
$this->currentDate = clone $this->startDate;
34+
if (!$omitStart) {
35+
array_unshift($this->dates, DateTimeImmutable::createFromInterface($this->startDate));
36+
}
37+
$this->rewind();
3438
}
3539

3640
/* Implementation of the Iterator interface {{{ */
@@ -39,10 +43,16 @@ public function __construct($rrule, DateTimeInterface $start)
3943
public function current()
4044
{
4145
if (!$this->valid()) {
42-
return;
46+
return null;
4347
}
44-
45-
return clone $this->currentDate;
48+
if (is_string($this->dates[$this->counter])) {
49+
$this->dates[$this->counter] =
50+
DateTimeParser::parse(
51+
$this->dates[$this->counter],
52+
$this->startDate->getTimezone()
53+
);
54+
}
55+
return $this->dates[$this->counter];
4656
}
4757

4858
/**
@@ -65,7 +75,7 @@ public function key()
6575
#[\ReturnTypeWillChange]
6676
public function valid()
6777
{
68-
return $this->counter <= count($this->dates);
78+
return $this->counter < count($this->dates);
6979
}
7080

7181
/**
@@ -76,7 +86,6 @@ public function valid()
7686
#[\ReturnTypeWillChange]
7787
public function rewind()
7888
{
79-
$this->currentDate = clone $this->startDate;
8089
$this->counter = 0;
8190
}
8291

@@ -92,12 +101,6 @@ public function next()
92101
if (!$this->valid()) {
93102
return;
94103
}
95-
96-
$this->currentDate =
97-
DateTimeParser::parse(
98-
$this->dates[$this->counter - 1],
99-
$this->startDate->getTimezone()
100-
);
101104
}
102105

103106
/* End of Iterator implementation }}} */
@@ -118,7 +121,7 @@ public function isInfinite()
118121
*/
119122
public function fastForward(DateTimeInterface $dt)
120123
{
121-
while ($this->valid() && $this->currentDate < $dt) {
124+
while ($this->valid() && $this->current() < $dt) {
122125
$this->next();
123126
}
124127
}
@@ -132,14 +135,6 @@ public function fastForward(DateTimeInterface $dt)
132135
*/
133136
protected $startDate;
134137

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-
143138
/**
144139
* The current item in the list.
145140
*

0 commit comments

Comments
 (0)