Skip to content

Commit 0636743

Browse files
committed
Add daily occurences to nextMonth and NextYear
1 parent 5b86533 commit 0636743

File tree

2 files changed

+153
-22
lines changed

2 files changed

+153
-22
lines changed

lib/Recur/RRuleIterator.php

+104-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,22 @@ 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+
if ($occurrence[2] > $currentMinuteOfMonth) {
459+
break 2;
460+
} elseif ($occurrence[2] < $currentMinuteOfMonth) {
461+
continue;
462+
}
463+
if ($occurrence[3] > $currentSecondOfMonth) {
446464
break 2;
447465
}
448466
}
@@ -461,13 +479,16 @@ protected function nextMonthly()
461479
// This goes to 0 because we need to start counting at the
462480
// beginning.
463481
$currentDayOfMonth = 0;
482+
$currentHourOfMonth = 0;
483+
$currentMinuteOfMonth = 0;
484+
$currentSecondOfMonth = 0;
464485
}
465486

466487
$this->currentDate = $this->currentDate->setDate(
467488
(int) $this->currentDate->format('Y'),
468489
(int) $this->currentDate->format('n'),
469-
(int) $occurrence
470-
);
490+
$occurrence[0]
491+
)->setTime($occurrence[1], $occurrence[2], $occurrence[3]);
471492
}
472493

473494
/**
@@ -478,6 +499,9 @@ protected function nextYearly()
478499
$currentMonth = $this->currentDate->format('n');
479500
$currentYear = $this->currentDate->format('Y');
480501
$currentDayOfMonth = $this->currentDate->format('j');
502+
$currentHourOfMonth = $this->currentDate->format('G');
503+
$currentMinuteOfMonth = $this->currentDate->format('i');
504+
$currentSecondOfMonth = $this->currentDate->format('s');
481505

482506
// No sub-rules, so we just advance by year
483507
if (empty($this->byMonth)) {
@@ -588,25 +612,38 @@ protected function nextYearly()
588612
return;
589613
}
590614

591-
$currentMonth = $this->currentDate->format('n');
592-
$currentYear = $this->currentDate->format('Y');
593-
$currentDayOfMonth = $this->currentDate->format('j');
594-
595615
$advancedToNewMonth = false;
596616

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

@@ -633,8 +670,8 @@ protected function nextYearly()
633670
$this->currentDate = $this->currentDate->setDate(
634671
(int) $currentYear,
635672
(int) $currentMonth,
636-
(int) $occurrence
637-
);
673+
(int) $occurrence[0]
674+
)->setTime($occurrence[1], $occurrence[2], $occurrence[3]);
638675

639676
return;
640677
} else {
@@ -798,7 +835,8 @@ protected function parseRRule($rrule)
798835
* Returns all the occurrences for a monthly frequency with a 'byDay' or
799836
* 'byMonthDay' expansion for the current month.
800837
*
801-
* The returned list is an array of integers with the day of month (1-31).
838+
* The returned list is an array of arrays with as first element the day of month (1-31);
839+
* the hour; the minute and second of the occurence
802840
*
803841
* @return array
804842
*/
@@ -884,8 +922,23 @@ protected function getMonthlyOccurrences()
884922
} else {
885923
$result = $byDayResults;
886924
}
887-
$result = array_unique($result);
888-
sort($result, SORT_NUMERIC);
925+
926+
$result = $this->addDailyOccurences($result);
927+
$result = array_unique($result, SORT_REGULAR);
928+
$sortLex = function ($a, $b) {
929+
if ($a[0] != $b[0]) {
930+
return $a[0] - $b[0];
931+
}
932+
if ($a[1] != $b[1]) {
933+
return $a[1] - $b[1];
934+
}
935+
if ($a[2] != $b[2]) {
936+
return $a[2] - $b[2];
937+
}
938+
939+
return $a[3] - $b[3];
940+
};
941+
usort($result, $sortLex);
889942

890943
// The last thing that needs checking is the BYSETPOS. If it's set, it
891944
// means only certain items in the set survive the filter.
@@ -903,11 +956,40 @@ protected function getMonthlyOccurrences()
903956
}
904957
}
905958

906-
sort($filteredResult, SORT_NUMERIC);
959+
usort($result, $sortLex);
907960

908961
return $filteredResult;
909962
}
910963

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

tests/VObject/Recur/RRuleIteratorTest.php

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

579+
public function testEverySundayEveryOtherYearAt1530and1730()
580+
{
581+
$this->parse('FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;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-14 15:30:15', '2001-01-14 15:30:56', '2001-01-14 15:35:15', '2001-01-14 15:35:56',
589+
'2001-01-14 17:30:15', '2001-01-14 17:30:56', '2001-01-14 17:35:15', '2001-01-14 17:35:56',
590+
591+
'2001-01-21 15:30:15', '2001-01-21 15:30:56', '2001-01-21 15:35:15', '2001-01-21 15:35:56',
592+
'2001-01-21 17:30:15', '2001-01-21 17:30:56', '2001-01-21 17:35:15', '2001-01-21 17:35:56',
593+
594+
'2001-01-28 15:30:15', '2001-01-28 15:30:56', '2001-01-28 15:35:15', '2001-01-28 15:35:56',
595+
'2001-01-28 17:30:15', '2001-01-28 17:30:56', '2001-01-28 17:35:15', '2001-01-28 17:35:56',
596+
597+
'2003-01-05 15:30:15', '2003-01-05 15:30:56', '2003-01-05 15:35:15', '2003-01-05 15:35:56',
598+
'2003-01-05 17:30:15', '2003-01-05 17:30:56', '2003-01-05 17:35:15', '2003-01-05 17:35:56',
599+
600+
'2003-01-12 15:30:15', '2003-01-12 15:30:56', '2003-01-12 15:35:15', '2003-01-12 15:35:56',
601+
'2003-01-12 17:30:15', '2003-01-12 17:30:56', '2003-01-12 17:35:15', '2003-01-12 17:35:56',
602+
603+
'2003-01-19 15:30:15', '2003-01-19 15:30:56', '2003-01-19 15:35:15', '2003-01-19 15:35:56',
604+
'2003-01-19 17:30:15', '2003-01-19 17:30:56', '2003-01-19 17:35:15', '2003-01-19 17:35:56',
605+
606+
'2003-01-26 15:30:15', '2003-01-26 15:30:56', '2003-01-26 15:35:15', '2003-01-26 15:35:56',
607+
'2003-01-26 17:30:15', '2003-01-26 17:30:56', '2003-01-26 17:35:15', '2003-01-26 17:35:56',
608+
]);
609+
}
610+
611+
public function testEverySundayEveryOtherMonthAt830and930()
612+
{
613+
$this->parse('FREQ=MONTHLY;INTERVAL=2;BYDAY=SU;BYHOUR=15,17;BYMINUTE=30;BYSECOND=11,12',
614+
'2001-01-01 12:34:56',
615+
[
616+
'2001-01-01 12:34:56',
617+
'2001-01-07 15:30:11', '2001-01-07 15:30:12', '2001-01-07 17:30:11', '2001-01-07 17:30:12',
618+
'2001-01-14 15:30:11', '2001-01-14 15:30:12', '2001-01-14 17:30:11', '2001-01-14 17:30:12',
619+
'2001-01-21 15:30:11', '2001-01-21 15:30:12', '2001-01-21 17:30:11', '2001-01-21 17:30:12',
620+
'2001-01-28 15:30:11', '2001-01-28 15:30:12', '2001-01-28 17:30:11', '2001-01-28 17:30:12',
621+
'2001-03-04 15:30:11', '2001-03-04 15:30:12', '2001-03-04 17:30:11', '2001-03-04 17:30:12',
622+
'2001-03-11 15:30:11', '2001-03-11 15:30:12', '2001-03-11 17:30:11', '2001-03-11 17:30:12',
623+
'2001-03-18 15:30:11', '2001-03-18 15:30:12', '2001-03-18 17:30:11', '2001-03-18 17:30:12',
624+
'2001-03-25 15:30:11', '2001-03-25 15:30:12', '2001-03-25 17:30:11', '2001-03-25 17:30:12',
625+
]);
626+
}
627+
579628
/**
580629
* @expectedException \Sabre\VObject\InvalidDataException
581630
*/

0 commit comments

Comments
 (0)