Skip to content

Commit a00a460

Browse files
committed
Improve fast forward performance
1 parent 2089c44 commit a00a460

File tree

1 file changed

+87
-45
lines changed

1 file changed

+87
-45
lines changed

lib/Recur/RRuleIterator.php

+87-45
Original file line numberDiff line numberDiff line change
@@ -85,30 +85,27 @@ public function rewind()
8585

8686
/**
8787
* Goes on to the next iteration.
88+
* @param int $amount
8889
*/
89-
public function next()
90+
public function next($amount = 1)
9091
{
9192
// Otherwise, we find the next event in the normal RRULE
9293
// sequence.
9394
switch ($this->frequency) {
94-
case 'hourly':
95-
$this->nextHourly();
95+
case 'hourly' :
96+
$this->nextHourly($amount);
9697
break;
97-
98-
case 'daily':
99-
$this->nextDaily();
98+
case 'daily' :
99+
$this->nextDaily($amount);
100100
break;
101-
102-
case 'weekly':
103-
$this->nextWeekly();
101+
case 'weekly' :
102+
$this->nextWeekly($amount);
104103
break;
105-
106-
case 'monthly':
107-
$this->nextMonthly();
104+
case 'monthly' :
105+
$this->nextMonthly($amount);
108106
break;
109-
110-
case 'yearly':
111-
$this->nextYearly();
107+
case 'yearly' :
108+
$this->nextYearly($amount);
112109
break;
113110
}
114111
++$this->counter;
@@ -134,9 +131,49 @@ public function isInfinite()
134131
*/
135132
public function fastForward(DateTimeInterface $dt)
136133
{
134+
if (!isset($this->count)) {
135+
do {
136+
$diff = $this->currentDate->diff($dt);
137+
switch ($this->frequency) {
138+
case 'hourly' :
139+
$i = $diff->days * 24;
140+
break;
141+
case 'daily' :
142+
$i = $diff->days;
143+
break;
144+
case 'weekly' :
145+
$i = $diff->days / 7;
146+
break;
147+
case 'monthly' :
148+
$i = $diff->days / 30;
149+
break;
150+
case 'yearly' :
151+
$i = $diff->days / 365;
152+
break;
153+
}
154+
$i /= $this->interval;
155+
$i /= 4;
156+
$i = floor($i);
157+
$i = max(1, $i);
158+
do {
159+
$previousDate = clone $this->currentDate;
160+
$this->next($i);
161+
} while ($this->valid() && $this->currentDate < $dt);
162+
163+
$this->currentDate = $previousDate;
164+
// do one step to avoid deadlock
165+
$this->next();
166+
} while ($i > 5 && $this->valid() && $this->currentDate < $dt);
167+
}
168+
137169
while ($this->valid() && $this->currentDate < $dt) {
138170
$this->next();
139171
}
172+
173+
if (!isset($this->count)) {
174+
// We don't know the counter at this point anymore
175+
$this->counter = NAN;
176+
}
140177
}
141178

142179
/**
@@ -306,19 +343,18 @@ public function fastForward(DateTimeInterface $dt)
306343
/**
307344
* Does the processing for advancing the iterator for hourly frequency.
308345
*/
309-
protected function nextHourly()
346+
protected function nextHourly($amount = 1)
310347
{
311-
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours');
348+
$this->currentDate = $this->currentDate->modify('+'.$amount * $this->interval.' hours');
312349
}
313350

314351
/**
315352
* Does the processing for advancing the iterator for daily frequency.
316353
*/
317-
protected function nextDaily()
354+
protected function nextDaily($amount = 1)
318355
{
319356
if (!$this->byHour && !$this->byDay) {
320-
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
321-
357+
$this->currentDate = $this->currentDate->modify('+'.$amount * $this->interval.' days');
322358
return;
323359
}
324360

@@ -338,14 +374,17 @@ protected function nextDaily()
338374
if ($this->byHour) {
339375
if ('23' == $this->currentDate->format('G')) {
340376
// to obey the interval rule
341-
$this->currentDate = $this->currentDate->modify('+'.$this->interval - 1 .' days');
377+
$this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' days');
378+
$amount = 1;
342379
}
343380

344381
$this->currentDate = $this->currentDate->modify('+1 hours');
345382
} else {
346-
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
383+
$this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' days');
384+
$amount = 1;
347385
}
348386

387+
349388
// Current month of the year
350389
$currentMonth = $this->currentDate->format('n');
351390

@@ -364,11 +403,10 @@ protected function nextDaily()
364403
/**
365404
* Does the processing for advancing the iterator for weekly frequency.
366405
*/
367-
protected function nextWeekly()
368-
{
369-
if (!$this->byHour && !$this->byDay) {
370-
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks');
406+
protected function nextWeekly($amount = 1) {
371407

408+
if (!$this->byHour && !$this->byDay) {
409+
$this->currentDate = $this->currentDate->modify('+' .($amount * $this->interval).' weeks');
372410
return;
373411
}
374412

@@ -397,9 +435,9 @@ protected function nextWeekly()
397435
$currentHour = (int) $this->currentDate->format('G');
398436

399437
// We need to roll over to the next week
400-
if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
401-
$this->currentDate = $this->currentDate->modify('+'.$this->interval - 1 .' weeks');
402-
438+
if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
439+
$this->currentDate = $this->currentDate->modify('+'.(($amount * $this->interval) - 1).' weeks');
440+
$amount = 1;
403441
// We need to go to the first day of this week, but only if we
404442
// are not already on this first day of this week.
405443
if ($this->currentDate->format('w') != $firstDay) {
@@ -414,7 +452,7 @@ protected function nextWeekly()
414452
/**
415453
* Does the processing for advancing the iterator for monthly frequency.
416454
*/
417-
protected function nextMonthly()
455+
protected function nextMonthly($amount = 1)
418456
{
419457
$currentDayOfMonth = $this->currentDate->format('j');
420458
$currentHourOfMonth = $this->currentDate->format('G');
@@ -425,9 +463,9 @@ protected function nextMonthly()
425463
// occur to the next month. We Must skip these invalid
426464
// entries.
427465
if ($currentDayOfMonth < 29) {
428-
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' months');
466+
$this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' months');
429467
} else {
430-
$increase = 0;
468+
$increase = $amount - 1;
431469
do {
432470
++$increase;
433471
$tempDate = clone $this->currentDate;
@@ -443,7 +481,7 @@ protected function nextMonthly()
443481
$occurrences = $this->getMonthlyOccurrences();
444482

445483
foreach ($occurrences as $occurrence) {
446-
// The first occurrence thats higher than the current
484+
// The first occurrence that's higher than the current
447485
// day of the month wins.
448486
if ($occurrence[0] > $currentDayOfMonth) {
449487
break 2;
@@ -469,13 +507,14 @@ protected function nextMonthly()
469507
// If we made it all the way here, it means there were no
470508
// valid occurrences, and we need to advance to the next
471509
// month.
472-
//
473-
// This line does not currently work in hhvm. Temporary workaround
474-
// follows:
475-
// $this->currentDate->modify('first day of this month');
476-
$this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
510+
$this->currentDate = $this->currentDate->setDate(
511+
(int) $this->currentDate->format('Y'),
512+
(int) $this->currentDate->format('n'),
513+
1
514+
);
477515
// end of workaround
478-
$this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months');
516+
$this->currentDate = $this->currentDate->modify('+ '.($amount * $this->interval).' months');
517+
$amount = 1;
479518

480519
// This goes to 0 because we need to start counting at the
481520
// beginning.
@@ -495,10 +534,10 @@ protected function nextMonthly()
495534
/**
496535
* Does the processing for advancing the iterator for yearly frequency.
497536
*/
498-
protected function nextYearly()
537+
protected function nextYearly($amount = 1)
499538
{
500-
$currentMonth = $this->currentDate->format('n');
501539
$currentYear = $this->currentDate->format('Y');
540+
$currentMonth = $this->currentDate->format('n');
502541
$currentDayOfMonth = $this->currentDate->format('j');
503542
$currentHourOfMonth = $this->currentDate->format('G');
504543
$currentMinuteOfMonth = $this->currentDate->format('i');
@@ -562,7 +601,8 @@ protected function nextYearly()
562601
}
563602

564603
// if there is no date found, check the next year
565-
$currentYear += $this->interval;
604+
$currentYear += $amount * $this->interval;
605+
$amount = 1;
566606
}
567607
}
568608

@@ -604,12 +644,13 @@ protected function nextYearly()
604644
}
605645

606646
// if there is no date found, check the next year
607-
$currentYear += $this->interval;
647+
$currentYear += ($amount * $this->interval);
648+
$amount = 1;
608649
}
609650
}
610651

611652
// The easiest form
612-
$this->currentDate = $this->currentDate->modify('+'.$this->interval.' years');
653+
$this->currentDate = $this->currentDate->modify('+'.($amount * $this->interval).' years');
613654

614655
return;
615656
}
@@ -656,7 +697,8 @@ protected function nextYearly()
656697
do {
657698
++$currentMonth;
658699
if ($currentMonth > 12) {
659-
$currentYear += $this->interval;
700+
$currentYear += ($amount * $this->interval);
701+
$amount = 1;
660702
$currentMonth = 1;
661703
}
662704
} while (!in_array($currentMonth, $this->byMonth));

0 commit comments

Comments
 (0)