Skip to content

Commit 2089c44

Browse files
committedNov 29, 2018
Add daily occurences to nextMonth and NextYear
1 parent 01d65af commit 2089c44

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
@@ -417,6 +417,9 @@ protected function nextWeekly()
417417
protected function nextMonthly()
418418
{
419419
$currentDayOfMonth = $this->currentDate->format('j');
420+
$currentHourOfMonth = $this->currentDate->format('G');
421+
$currentMinuteOfMonth = $this->currentDate->format('i');
422+
$currentSecondOfMonth = $this->currentDate->format('s');
420423
if (!$this->byMonthDay && !$this->byDay) {
421424
// If the current day is higher than the 28th, rollover can
422425
// occur to the next month. We Must skip these invalid
@@ -442,7 +445,23 @@ protected function nextMonthly()
442445
foreach ($occurrences as $occurrence) {
443446
// The first occurrence thats higher than the current
444447
// day of the month wins.
445-
if ($occurrence > $currentDayOfMonth) {
448+
if ($occurrence[0] > $currentDayOfMonth) {
449+
break 2;
450+
} elseif ($occurrence[0] < $currentDayOfMonth) {
451+
continue;
452+
}
453+
if ($occurrence[1] > $currentHourOfMonth) {
454+
break 2;
455+
} elseif ($occurrence[1] < $currentHourOfMonth) {
456+
continue;
457+
}
458+
459+
if ($occurrence[2] > $currentMinuteOfMonth) {
460+
break 2;
461+
} elseif ($occurrence[2] < $currentMinuteOfMonth) {
462+
continue;
463+
}
464+
if ($occurrence[3] > $currentSecondOfMonth) {
446465
break 2;
447466
}
448467
}
@@ -461,13 +480,16 @@ protected function nextMonthly()
461480
// This goes to 0 because we need to start counting at the
462481
// beginning.
463482
$currentDayOfMonth = 0;
483+
$currentHourOfMonth = 0;
484+
$currentMinuteOfMonth = 0;
485+
$currentSecondOfMonth = 0;
464486
}
465487

466488
$this->currentDate = $this->currentDate->setDate(
467489
(int) $this->currentDate->format('Y'),
468490
(int) $this->currentDate->format('n'),
469-
(int) $occurrence
470-
);
491+
$occurrence[0]
492+
)->setTime($occurrence[1], $occurrence[2], $occurrence[3]);
471493
}
472494

473495
/**
@@ -478,6 +500,9 @@ protected function nextYearly()
478500
$currentMonth = $this->currentDate->format('n');
479501
$currentYear = $this->currentDate->format('Y');
480502
$currentDayOfMonth = $this->currentDate->format('j');
503+
$currentHourOfMonth = $this->currentDate->format('G');
504+
$currentMinuteOfMonth = $this->currentDate->format('i');
505+
$currentSecondOfMonth = $this->currentDate->format('s');
481506

482507
// No sub-rules, so we just advance by year
483508
if (empty($this->byMonth)) {
@@ -589,25 +614,38 @@ protected function nextYearly()
589614
return;
590615
}
591616

592-
$currentMonth = $this->currentDate->format('n');
593-
$currentYear = $this->currentDate->format('Y');
594-
$currentDayOfMonth = $this->currentDate->format('j');
595-
596617
$advancedToNewMonth = false;
597618

598619
// If we got a byDay or getMonthDay filter, we must first expand
599620
// further.
600621
if ($this->byDay || $this->byMonthDay) {
601622
while (true) {
602-
$occurrences = $this->getMonthlyOccurrences();
603-
604-
foreach ($occurrences as $occurrence) {
605-
// The first occurrence that's higher than the current
606-
// day of the month wins.
607-
// If we advanced to the next month or year, the first
608-
// occurrence is always correct.
609-
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
610-
break 2;
623+
// If the start date is incorrect we must directly jump to the next value
624+
if (in_array($currentMonth, $this->byMonth)) {
625+
$occurrences = $this->getMonthlyOccurrences();
626+
foreach ($occurrences as $occurrence) {
627+
// The first occurrence that's higher than the current
628+
// day of the month wins.
629+
// If we advanced to the next month or year, the first
630+
// occurrence is always correct.
631+
if ($occurrence[0] > $currentDayOfMonth || $advancedToNewMonth) {
632+
break 2;
633+
} elseif ($occurrence[0] < $currentDayOfMonth) {
634+
continue;
635+
}
636+
if ($occurrence[1] > $currentHourOfMonth) {
637+
break 2;
638+
} elseif ($occurrence[1] < $currentHourOfMonth) {
639+
continue;
640+
}
641+
if ($occurrence[2] > $currentMinuteOfMonth) {
642+
break 2;
643+
} elseif ($occurrence[2] < $currentMinuteOfMonth) {
644+
continue;
645+
}
646+
if ($occurrence[3] > $currentSecondOfMonth) {
647+
break 2;
648+
}
611649
}
612650
}
613651

@@ -634,8 +672,8 @@ protected function nextYearly()
634672
$this->currentDate = $this->currentDate->setDate(
635673
(int) $currentYear,
636674
(int) $currentMonth,
637-
(int) $occurrence
638-
);
675+
(int) $occurrence[0]
676+
)->setTime($occurrence[1], $occurrence[2], $occurrence[3]);
639677

640678
return;
641679
} else {
@@ -799,7 +837,8 @@ protected function parseRRule($rrule)
799837
* Returns all the occurrences for a monthly frequency with a 'byDay' or
800838
* 'byMonthDay' expansion for the current month.
801839
*
802-
* The returned list is an array of integers with the day of month (1-31).
840+
* The returned list is an array of arrays with as first element the day of month (1-31);
841+
* the hour; the minute and second of the occurence
803842
*
804843
* @return array
805844
*/
@@ -885,8 +924,23 @@ protected function getMonthlyOccurrences()
885924
} else {
886925
$result = $byDayResults;
887926
}
888-
$result = array_unique($result);
889-
sort($result, SORT_NUMERIC);
927+
928+
$result = $this->addDailyOccurences($result);
929+
$result = array_unique($result, SORT_REGULAR);
930+
$sortLex = function ($a, $b) {
931+
if ($a[0] != $b[0]) {
932+
return $a[0] - $b[0];
933+
}
934+
if ($a[1] != $b[1]) {
935+
return $a[1] - $b[1];
936+
}
937+
if ($a[2] != $b[2]) {
938+
return $a[2] - $b[2];
939+
}
940+
941+
return $a[3] - $b[3];
942+
};
943+
usort($result, $sortLex);
890944

891945
// The last thing that needs checking is the BYSETPOS. If it's set, it
892946
// means only certain items in the set survive the filter.
@@ -904,11 +958,40 @@ protected function getMonthlyOccurrences()
904958
}
905959
}
906960

907-
sort($filteredResult, SORT_NUMERIC);
961+
usort($result, $sortLex);
908962

909963
return $filteredResult;
910964
}
911965

966+
/**
967+
* Expends daily occurrences to an array of days that an event occurs on.
968+
*
969+
* @param array $result an array of integers with the day of month (1-31);
970+
*
971+
* @return array an array of arrays with the day of the month, hours, minute and seconds of the occurence
972+
*/
973+
protected function addDailyOccurences(array $result)
974+
{
975+
$output = [];
976+
$hour = (int) $this->currentDate->format('G');
977+
$minute = (int) $this->currentDate->format('i');
978+
$second = (int) $this->currentDate->format('s');
979+
foreach ($result as $day) {
980+
$seconds = $this->bySecond ? $this->bySecond : [$second];
981+
$minutes = $this->byMinute ? $this->byMinute : [$minute];
982+
$hours = $this->byHour ? $this->byHour : [$hour];
983+
foreach ($hours as $h) {
984+
foreach ($minutes as $m) {
985+
foreach ($seconds as $s) {
986+
$output[] = [(int) $day, (int) $h, (int) $m, (int) $s];
987+
}
988+
}
989+
}
990+
}
991+
992+
return $output;
993+
}
994+
912995
/**
913996
* Simple mapping from iCalendar day names to day numbers.
914997
*

‎tests/VObject/Recur/RRuleIteratorTest.php

+40
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,46 @@ public function testYearlyByYearDayNegative()
594594
);
595595
}
596596

597+
public function testFirstLastSundayEveryOtherYearAt1530and1730InJanuary()
598+
{
599+
$this->parse('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=1SU,-1SU;BYHOUR=15,17;BYMINUTE=30,35;BYSECOND=15,56',
600+
'1999-12-01 12:34:56',
601+
[
602+
'1999-12-01 12:34:56',
603+
'2001-01-07 15:30:15', '2001-01-07 15:30:56', '2001-01-07 15:35:15', '2001-01-07 15:35:56',
604+
'2001-01-07 17:30:15', '2001-01-07 17:30:56', '2001-01-07 17:35:15', '2001-01-07 17:35:56',
605+
606+
'2001-01-28 15:30:15', '2001-01-28 15:30:56', '2001-01-28 15:35:15', '2001-01-28 15:35:56',
607+
'2001-01-28 17:30:15', '2001-01-28 17:30:56', '2001-01-28 17:35:15', '2001-01-28 17:35:56',
608+
609+
'2003-01-05 15:30:15', '2003-01-05 15:30:56', '2003-01-05 15:35:15', '2003-01-05 15:35:56',
610+
'2003-01-05 17:30:15', '2003-01-05 17:30:56', '2003-01-05 17:35:15', '2003-01-05 17:35:56',
611+
612+
'2003-01-26 15:30:15', '2003-01-26 15:30:56', '2003-01-26 15:35:15', '2003-01-26 15:35:56',
613+
'2003-01-26 17:30:15', '2003-01-26 17:30:56', '2003-01-26 17:35:15', '2003-01-26 17:35:56',
614+
]);
615+
}
616+
617+
public function testFirstFourthSundayEveryOtherMonthAt830and930()
618+
{
619+
$this->parse('FREQ=MONTHLY;INTERVAL=2;BYDAY=1SU,4SU;BYHOUR=15,17;BYMINUTE=30,32;BYSECOND=11,12',
620+
'2001-01-01 12:34:56',
621+
[
622+
'2001-01-01 12:34:56',
623+
'2001-01-07 15:30:11', '2001-01-07 15:30:12', '2001-01-07 15:32:11', '2001-01-07 15:32:12',
624+
'2001-01-07 17:30:11', '2001-01-07 17:30:12', '2001-01-07 17:32:11', '2001-01-07 17:32:12',
625+
626+
'2001-01-28 15:30:11', '2001-01-28 15:30:12', '2001-01-28 15:32:11', '2001-01-28 15:32:12',
627+
'2001-01-28 17:30:11', '2001-01-28 17:30:12', '2001-01-28 17:32:11', '2001-01-28 17:32:12',
628+
629+
'2001-03-04 15:30:11', '2001-03-04 15:30:12', '2001-03-04 15:32:11', '2001-03-04 15:32:12',
630+
'2001-03-04 17:30:11', '2001-03-04 17:30:12', '2001-03-04 17:32:11', '2001-03-04 17:32:12',
631+
632+
'2001-03-25 15:30:11', '2001-03-25 15:30:12', '2001-03-25 15:32:11', '2001-03-25 15:32:12',
633+
'2001-03-25 17:30:11', '2001-03-25 17:30:12', '2001-03-25 17:32:11', '2001-03-25 17:32:12',
634+
]);
635+
}
636+
597637
/**
598638
* @expectedException \Sabre\VObject\InvalidDataException
599639
*/

0 commit comments

Comments
 (0)