Skip to content

Commit f941bca

Browse files
KAYLukasvalentinbonneaud
authored andcommitted
Add daily occurences to nextMonth and NextYear
1 parent 21c2cff commit f941bca

File tree

2 files changed

+145
-22
lines changed

2 files changed

+145
-22
lines changed

lib/Recur/RRuleIterator.php

+105-22
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ protected function nextWeekly()
420420
protected function nextMonthly()
421421
{
422422
$currentDayOfMonth = $this->currentDate->format('j');
423+
$currentHourOfMonth = $this->currentDate->format('G');
424+
$currentMinuteOfMonth = $this->currentDate->format('i');
425+
$currentSecondOfMonth = $this->currentDate->format('s');
423426
if (!$this->byMonthDay && !$this->byDay) {
424427
// If the current day is higher than the 28th, rollover can
425428
// occur to the next month. We Must skip these invalid
@@ -445,7 +448,23 @@ protected function nextMonthly()
445448
foreach ($occurrences as $occurrence) {
446449
// The first occurrence thats higher than the current
447450
// day of the month wins.
448-
if ($occurrence > $currentDayOfMonth) {
451+
if ($occurrence[0] > $currentDayOfMonth) {
452+
break 2;
453+
} elseif ($occurrence[0] < $currentDayOfMonth) {
454+
continue;
455+
}
456+
if ($occurrence[1] > $currentHourOfMonth) {
457+
break 2;
458+
} elseif ($occurrence[1] < $currentHourOfMonth) {
459+
continue;
460+
}
461+
462+
if ($occurrence[2] > $currentMinuteOfMonth) {
463+
break 2;
464+
} elseif ($occurrence[2] < $currentMinuteOfMonth) {
465+
continue;
466+
}
467+
if ($occurrence[3] > $currentSecondOfMonth) {
449468
break 2;
450469
}
451470
}
@@ -464,6 +483,9 @@ protected function nextMonthly()
464483
// This goes to 0 because we need to start counting at the
465484
// beginning.
466485
$currentDayOfMonth = 0;
486+
$currentHourOfMonth = 0;
487+
$currentMinuteOfMonth = 0;
488+
$currentSecondOfMonth = 0;
467489

468490
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
469491
// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
@@ -477,8 +499,8 @@ protected function nextMonthly()
477499
$this->currentDate = $this->currentDate->setDate(
478500
(int) $this->currentDate->format('Y'),
479501
(int) $this->currentDate->format('n'),
480-
(int) $occurrence
481-
);
502+
$occurrence[0]
503+
)->setTime($occurrence[1], $occurrence[2], $occurrence[3]);
482504
}
483505

484506
/**
@@ -489,6 +511,9 @@ protected function nextYearly()
489511
$currentMonth = $this->currentDate->format('n');
490512
$currentYear = $this->currentDate->format('Y');
491513
$currentDayOfMonth = $this->currentDate->format('j');
514+
$currentHourOfMonth = $this->currentDate->format('G');
515+
$currentMinuteOfMonth = $this->currentDate->format('i');
516+
$currentSecondOfMonth = $this->currentDate->format('s');
492517

493518
// No sub-rules, so we just advance by year
494519
if (empty($this->byMonth)) {
@@ -599,25 +624,38 @@ protected function nextYearly()
599624
return;
600625
}
601626

602-
$currentMonth = $this->currentDate->format('n');
603-
$currentYear = $this->currentDate->format('Y');
604-
$currentDayOfMonth = $this->currentDate->format('j');
605-
606627
$advancedToNewMonth = false;
607628

608629
// If we got a byDay or getMonthDay filter, we must first expand
609630
// further.
610631
if ($this->byDay || $this->byMonthDay) {
611632
while (true) {
612-
$occurrences = $this->getMonthlyOccurrences();
613-
614-
foreach ($occurrences as $occurrence) {
615-
// The first occurrence that's higher than the current
616-
// day of the month wins.
617-
// If we advanced to the next month or year, the first
618-
// occurrence is always correct.
619-
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
620-
break 2;
633+
// If the start date is incorrect we must directly jump to the next value
634+
if (in_array($currentMonth, $this->byMonth)) {
635+
$occurrences = $this->getMonthlyOccurrences();
636+
foreach ($occurrences as $occurrence) {
637+
// The first occurrence that's higher than the current
638+
// day of the month wins.
639+
// If we advanced to the next month or year, the first
640+
// occurrence is always correct.
641+
if ($occurrence[0] > $currentDayOfMonth || $advancedToNewMonth) {
642+
break 2;
643+
} elseif ($occurrence[0] < $currentDayOfMonth) {
644+
continue;
645+
}
646+
if ($occurrence[1] > $currentHourOfMonth) {
647+
break 2;
648+
} elseif ($occurrence[1] < $currentHourOfMonth) {
649+
continue;
650+
}
651+
if ($occurrence[2] > $currentMinuteOfMonth) {
652+
break 2;
653+
} elseif ($occurrence[2] < $currentMinuteOfMonth) {
654+
continue;
655+
}
656+
if ($occurrence[3] > $currentSecondOfMonth) {
657+
break 2;
658+
}
621659
}
622660
}
623661

@@ -644,8 +682,8 @@ protected function nextYearly()
644682
$this->currentDate = $this->currentDate->setDate(
645683
(int) $currentYear,
646684
(int) $currentMonth,
647-
(int) $occurrence
648-
);
685+
(int) $occurrence[0]
686+
)->setTime($occurrence[1], $occurrence[2], $occurrence[3]);
649687

650688
return;
651689
} else {
@@ -809,7 +847,8 @@ protected function parseRRule($rrule)
809847
* Returns all the occurrences for a monthly frequency with a 'byDay' or
810848
* 'byMonthDay' expansion for the current month.
811849
*
812-
* The returned list is an array of integers with the day of month (1-31).
850+
* The returned list is an array of arrays with as first element the day of month (1-31);
851+
* the hour; the minute and second of the occurence
813852
*
814853
* @return array
815854
*/
@@ -895,8 +934,23 @@ protected function getMonthlyOccurrences()
895934
} else {
896935
$result = $byDayResults;
897936
}
898-
$result = array_unique($result);
899-
sort($result, SORT_NUMERIC);
937+
938+
$result = $this->addDailyOccurences($result);
939+
$result = array_unique($result, SORT_REGULAR);
940+
$sortLex = function ($a, $b) {
941+
if ($a[0] != $b[0]) {
942+
return $a[0] - $b[0];
943+
}
944+
if ($a[1] != $b[1]) {
945+
return $a[1] - $b[1];
946+
}
947+
if ($a[2] != $b[2]) {
948+
return $a[2] - $b[2];
949+
}
950+
951+
return $a[3] - $b[3];
952+
};
953+
usort($result, $sortLex);
900954

901955
// The last thing that needs checking is the BYSETPOS. If it's set, it
902956
// means only certain items in the set survive the filter.
@@ -914,11 +968,40 @@ protected function getMonthlyOccurrences()
914968
}
915969
}
916970

917-
sort($filteredResult, SORT_NUMERIC);
971+
usort($result, $sortLex);
918972

919973
return $filteredResult;
920974
}
921975

976+
/**
977+
* Expends daily occurrences to an array of days that an event occurs on.
978+
*
979+
* @param array $result an array of integers with the day of month (1-31);
980+
*
981+
* @return array an array of arrays with the day of the month, hours, minute and seconds of the occurence
982+
*/
983+
protected function addDailyOccurences(array $result)
984+
{
985+
$output = [];
986+
$hour = (int) $this->currentDate->format('G');
987+
$minute = (int) $this->currentDate->format('i');
988+
$second = (int) $this->currentDate->format('s');
989+
foreach ($result as $day) {
990+
$seconds = $this->bySecond ? $this->bySecond : [$second];
991+
$minutes = $this->byMinute ? $this->byMinute : [$minute];
992+
$hours = $this->byHour ? $this->byHour : [$hour];
993+
foreach ($hours as $h) {
994+
foreach ($minutes as $m) {
995+
foreach ($seconds as $s) {
996+
$output[] = [(int) $day, (int) $h, (int) $m, (int) $s];
997+
}
998+
}
999+
}
1000+
}
1001+
1002+
return $output;
1003+
}
1004+
9221005
/**
9231006
* Simple mapping from iCalendar day names to day numbers.
9241007
*

tests/VObject/Recur/RRuleIteratorTest.php

+40
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,46 @@ public function testYearlyByYearDayNegative()
576576
);
577577
}
578578

579+
public function testFirstLastSundayEveryOtherYearAt1530and1730InJanuary()
580+
{
581+
$this->parse('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=1SU,-1SU;BYHOUR=15,17;BYMINUTE=30,35;BYSECOND=15,56',
582+
'1999-12-01 12:34:56',
583+
[
584+
'1999-12-01 12:34:56',
585+
'2001-01-07 15:30:15', '2001-01-07 15:30:56', '2001-01-07 15:35:15', '2001-01-07 15:35:56',
586+
'2001-01-07 17:30:15', '2001-01-07 17:30:56', '2001-01-07 17:35:15', '2001-01-07 17:35:56',
587+
588+
'2001-01-28 15:30:15', '2001-01-28 15:30:56', '2001-01-28 15:35:15', '2001-01-28 15:35:56',
589+
'2001-01-28 17:30:15', '2001-01-28 17:30:56', '2001-01-28 17:35:15', '2001-01-28 17:35:56',
590+
591+
'2003-01-05 15:30:15', '2003-01-05 15:30:56', '2003-01-05 15:35:15', '2003-01-05 15:35:56',
592+
'2003-01-05 17:30:15', '2003-01-05 17:30:56', '2003-01-05 17:35:15', '2003-01-05 17:35:56',
593+
594+
'2003-01-26 15:30:15', '2003-01-26 15:30:56', '2003-01-26 15:35:15', '2003-01-26 15:35:56',
595+
'2003-01-26 17:30:15', '2003-01-26 17:30:56', '2003-01-26 17:35:15', '2003-01-26 17:35:56',
596+
]);
597+
}
598+
599+
public function testFirstFourthSundayEveryOtherMonthAt830and930()
600+
{
601+
$this->parse('FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU,4SU;BYHOUR=15,17;BYMINUTE=30,32;BYSECOND=11,12',
602+
'2001-01-01 12:34:56',
603+
[
604+
'2001-01-01 12:34:56',
605+
'2001-01-07 15:30:11', '2001-01-07 15:30:12', '2001-01-07 15:32:11', '2001-01-07 15:32:12',
606+
'2001-01-07 17:30:11', '2001-01-07 17:30:12', '2001-01-07 17:32:11', '2001-01-07 17:32:12',
607+
608+
'2001-01-28 15:30:11', '2001-01-28 15:30:12', '2001-01-28 15:32:11', '2001-01-28 15:32:12',
609+
'2001-01-28 17:30:11', '2001-01-28 17:30:12', '2001-01-28 17:32:11', '2001-01-28 17:32:12',
610+
611+
'2001-03-04 15:30:11', '2001-03-04 15:30:12', '2001-03-04 15:32:11', '2001-03-04 15:32:12',
612+
'2001-03-04 17:30:11', '2001-03-04 17:30:12', '2001-03-04 17:32:11', '2001-03-04 17:32:12',
613+
614+
'2001-03-25 15:30:11', '2001-03-25 15:30:12', '2001-03-25 15:32:11', '2001-03-25 15:32:12',
615+
'2001-03-25 17:30:11', '2001-03-25 17:30:12', '2001-03-25 17:32:11', '2001-03-25 17:32:12',
616+
]);
617+
}
618+
579619
/**
580620
* @expectedException \Sabre\VObject\InvalidDataException
581621
*/

0 commit comments

Comments
 (0)