@@ -420,6 +420,9 @@ protected function nextWeekly()
420
420
protected function nextMonthly ()
421
421
{
422
422
$ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
423
+ $ currentHourOfMonth = $ this ->currentDate ->format ('G ' );
424
+ $ currentMinuteOfMonth = $ this ->currentDate ->format ('i ' );
425
+ $ currentSecondOfMonth = $ this ->currentDate ->format ('s ' );
423
426
if (!$ this ->byMonthDay && !$ this ->byDay ) {
424
427
// If the current day is higher than the 28th, rollover can
425
428
// occur to the next month. We Must skip these invalid
@@ -445,7 +448,23 @@ protected function nextMonthly()
445
448
foreach ($ occurrences as $ occurrence ) {
446
449
// The first occurrence thats higher than the current
447
450
// 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 ) {
449
468
break 2 ;
450
469
}
451
470
}
@@ -464,6 +483,9 @@ protected function nextMonthly()
464
483
// This goes to 0 because we need to start counting at the
465
484
// beginning.
466
485
$ currentDayOfMonth = 0 ;
486
+ $ currentHourOfMonth = 0 ;
487
+ $ currentMinuteOfMonth = 0 ;
488
+ $ currentSecondOfMonth = 0 ;
467
489
468
490
// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
469
491
// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
@@ -477,8 +499,8 @@ protected function nextMonthly()
477
499
$ this ->currentDate = $ this ->currentDate ->setDate (
478
500
(int ) $ this ->currentDate ->format ('Y ' ),
479
501
(int ) $ this ->currentDate ->format ('n ' ),
480
- ( int ) $ occurrence
481
- );
502
+ $ occurrence[ 0 ]
503
+ )-> setTime ( $ occurrence [ 1 ], $ occurrence [ 2 ], $ occurrence [ 3 ]) ;
482
504
}
483
505
484
506
/**
@@ -489,6 +511,9 @@ protected function nextYearly()
489
511
$ currentMonth = $ this ->currentDate ->format ('n ' );
490
512
$ currentYear = $ this ->currentDate ->format ('Y ' );
491
513
$ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
514
+ $ currentHourOfMonth = $ this ->currentDate ->format ('G ' );
515
+ $ currentMinuteOfMonth = $ this ->currentDate ->format ('i ' );
516
+ $ currentSecondOfMonth = $ this ->currentDate ->format ('s ' );
492
517
493
518
// No sub-rules, so we just advance by year
494
519
if (empty ($ this ->byMonth )) {
@@ -599,25 +624,38 @@ protected function nextYearly()
599
624
return ;
600
625
}
601
626
602
- $ currentMonth = $ this ->currentDate ->format ('n ' );
603
- $ currentYear = $ this ->currentDate ->format ('Y ' );
604
- $ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
605
-
606
627
$ advancedToNewMonth = false ;
607
628
608
629
// If we got a byDay or getMonthDay filter, we must first expand
609
630
// further.
610
631
if ($ this ->byDay || $ this ->byMonthDay ) {
611
632
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
+ }
621
659
}
622
660
}
623
661
@@ -644,8 +682,8 @@ protected function nextYearly()
644
682
$ this ->currentDate = $ this ->currentDate ->setDate (
645
683
(int ) $ currentYear ,
646
684
(int ) $ currentMonth ,
647
- (int ) $ occurrence
648
- );
685
+ (int ) $ occurrence[ 0 ]
686
+ )-> setTime ( $ occurrence [ 1 ], $ occurrence [ 2 ], $ occurrence [ 3 ]) ;
649
687
650
688
return ;
651
689
} else {
@@ -809,7 +847,8 @@ protected function parseRRule($rrule)
809
847
* Returns all the occurrences for a monthly frequency with a 'byDay' or
810
848
* 'byMonthDay' expansion for the current month.
811
849
*
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
813
852
*
814
853
* @return array
815
854
*/
@@ -895,8 +934,23 @@ protected function getMonthlyOccurrences()
895
934
} else {
896
935
$ result = $ byDayResults ;
897
936
}
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 );
900
954
901
955
// The last thing that needs checking is the BYSETPOS. If it's set, it
902
956
// means only certain items in the set survive the filter.
@@ -914,11 +968,40 @@ protected function getMonthlyOccurrences()
914
968
}
915
969
}
916
970
917
- sort ( $ filteredResult , SORT_NUMERIC );
971
+ usort ( $ result , $ sortLex );
918
972
919
973
return $ filteredResult ;
920
974
}
921
975
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
+
922
1005
/**
923
1006
* Simple mapping from iCalendar day names to day numbers.
924
1007
*
0 commit comments