@@ -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+ } else if ($ occurrence [0 ] < $ currentDayOfMonth ) {
451+ continue ;
452+ }
453+ if ($ occurrence [1 ] > $ currentHourOfMonth ) {
454+ break 2 ;
455+ } else if ($ occurrence [1 ] < $ currentHourOfMonth ) {
456+ continue ;
457+ }
458+ if ($ occurrence [2 ] > $ currentMinuteOfMonth ) {
459+ break 2 ;
460+ } else if ($ 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,39 @@ 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+
622+ // If the start date is incorrect we must directly jump to the next value
623+ if (in_array ($ currentMonth , $ this ->byMonth )) {
624+ $ occurrences = $ this ->getMonthlyOccurrences ();
625+ foreach ($ occurrences as $ occurrence ) {
626+ // The first occurrence that's higher than the current
627+ // day of the month wins.
628+ // If we advanced to the next month or year, the first
629+ // occurrence is always correct.
630+ if ($ occurrence [0 ] > $ currentDayOfMonth || $ advancedToNewMonth ) {
631+ break 2 ;
632+ } else if ($ occurrence [0 ] < $ currentDayOfMonth ) {
633+ continue ;
634+ }
635+ if ($ occurrence [1 ] > $ currentHourOfMonth ) {
636+ break 2 ;
637+ } else if ($ occurrence [1 ] < $ currentHourOfMonth ) {
638+ continue ;
639+ }
640+ if ($ occurrence [2 ] > $ currentMinuteOfMonth ) {
641+ break 2 ;
642+ } else if ($ occurrence [2 ] < $ currentMinuteOfMonth ) {
643+ continue ;
644+ }
645+ if ($ occurrence [3 ] > $ currentSecondOfMonth ) {
646+ break 2 ;
647+ }
610648 }
611649 }
612650
@@ -633,9 +671,8 @@ protected function nextYearly()
633671 $ this ->currentDate = $ this ->currentDate ->setDate (
634672 (int ) $ currentYear ,
635673 (int ) $ currentMonth ,
636- (int ) $ occurrence
637- );
638-
674+ (int ) $ occurrence [0 ]
675+ )->setTime ($ occurrence [1 ], $ occurrence [2 ], $ occurrence [3 ]);
639676 return ;
640677 } else {
641678 // These are the 'byMonth' rules, if there are no byDay or
@@ -884,8 +921,22 @@ protected function getMonthlyOccurrences()
884921 } else {
885922 $ result = $ byDayResults ;
886923 }
887- $ result = array_unique ($ result );
888- sort ($ result , SORT_NUMERIC );
924+
925+ $ result = $ this ->addDailyOccurences ($ result );
926+ $ result = array_unique ($ result , SORT_REGULAR );
927+ $ sortLex = function ($ a , $ b ) {
928+ if ($ a [0 ] != $ b [0 ]) {
929+ return $ a [0 ] - $ b [0 ];
930+ }
931+ if ($ a [1 ] != $ b [1 ]) {
932+ return $ a [1 ] - $ b [1 ];
933+ }
934+ if ($ a [2 ] != $ b [2 ]) {
935+ return $ a [2 ] - $ b [2 ];
936+ }
937+ return $ a [3 ] - $ b [3 ];
938+ };
939+ usort ($ result , $ sortLex );
889940
890941 // The last thing that needs checking is the BYSETPOS. If it's set, it
891942 // means only certain items in the set survive the filter.
@@ -903,11 +954,32 @@ protected function getMonthlyOccurrences()
903954 }
904955 }
905956
906- sort ( $ filteredResult , SORT_NUMERIC );
957+ usort ( $ result , $ sortLex );
907958
908959 return $ filteredResult ;
909960 }
910961
962+ protected function addDailyOccurences (array $ result ) {
963+ $ output = [];
964+ $ hour = (int ) $ this ->currentDate ->format ('G ' );
965+ $ minute = (int ) $ this ->currentDate ->format ('i ' );
966+ $ second = (int ) $ this ->currentDate ->format ('s ' );
967+ foreach ($ result as $ day )
968+ {
969+ $ seconds = $ this ->bySecond ? $ this ->bySecond : [ $ second ];
970+ $ minutes = $ this ->byMinute ? $ this ->byMinute : [ $ minute ];
971+ $ hours = $ this ->byHour ? $ this ->byHour : [ $ hour ];
972+ foreach ($ hours as $ h ) {
973+ foreach ($ minutes as $ m ) {
974+ foreach ($ seconds as $ s ) {
975+ $ output [] = [(int ) $ day , (int ) $ h , (int ) $ m , (int ) $ s ];
976+ }
977+ }
978+ }
979+ }
980+ return $ output ;
981+ }
982+
911983 /**
912984 * Simple mapping from iCalendar day names to day numbers.
913985 *
0 commit comments