@@ -417,6 +417,9 @@ protected function nextWeekly()
417
417
protected function nextMonthly ()
418
418
{
419
419
$ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
420
+ $ currentHourOfMonth = $ this ->currentDate ->format ('G ' );
421
+ $ currentMinuteOfMonth = $ this ->currentDate ->format ('i ' );
422
+ $ currentSecondOfMonth = $ this ->currentDate ->format ('s ' );
420
423
if (!$ this ->byMonthDay && !$ this ->byDay ) {
421
424
// If the current day is higher than the 28th, rollover can
422
425
// occur to the next month. We Must skip these invalid
@@ -442,7 +445,23 @@ protected function nextMonthly()
442
445
foreach ($ occurrences as $ occurrence ) {
443
446
// The first occurrence thats higher than the current
444
447
// 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 ) {
446
465
break 2 ;
447
466
}
448
467
}
@@ -461,13 +480,16 @@ protected function nextMonthly()
461
480
// This goes to 0 because we need to start counting at the
462
481
// beginning.
463
482
$ currentDayOfMonth = 0 ;
483
+ $ currentHourOfMonth = 0 ;
484
+ $ currentMinuteOfMonth = 0 ;
485
+ $ currentSecondOfMonth = 0 ;
464
486
}
465
487
466
488
$ this ->currentDate = $ this ->currentDate ->setDate (
467
489
(int ) $ this ->currentDate ->format ('Y ' ),
468
490
(int ) $ this ->currentDate ->format ('n ' ),
469
- ( int ) $ occurrence
470
- );
491
+ $ occurrence[ 0 ]
492
+ )-> setTime ( $ occurrence [ 1 ], $ occurrence [ 2 ], $ occurrence [ 3 ]) ;
471
493
}
472
494
473
495
/**
@@ -478,6 +500,9 @@ protected function nextYearly()
478
500
$ currentMonth = $ this ->currentDate ->format ('n ' );
479
501
$ currentYear = $ this ->currentDate ->format ('Y ' );
480
502
$ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
503
+ $ currentHourOfMonth = $ this ->currentDate ->format ('G ' );
504
+ $ currentMinuteOfMonth = $ this ->currentDate ->format ('i ' );
505
+ $ currentSecondOfMonth = $ this ->currentDate ->format ('s ' );
481
506
482
507
// No sub-rules, so we just advance by year
483
508
if (empty ($ this ->byMonth )) {
@@ -589,25 +614,38 @@ protected function nextYearly()
589
614
return ;
590
615
}
591
616
592
- $ currentMonth = $ this ->currentDate ->format ('n ' );
593
- $ currentYear = $ this ->currentDate ->format ('Y ' );
594
- $ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
595
-
596
617
$ advancedToNewMonth = false ;
597
618
598
619
// If we got a byDay or getMonthDay filter, we must first expand
599
620
// further.
600
621
if ($ this ->byDay || $ this ->byMonthDay ) {
601
622
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
+ }
611
649
}
612
650
}
613
651
@@ -634,8 +672,8 @@ protected function nextYearly()
634
672
$ this ->currentDate = $ this ->currentDate ->setDate (
635
673
(int ) $ currentYear ,
636
674
(int ) $ currentMonth ,
637
- (int ) $ occurrence
638
- );
675
+ (int ) $ occurrence[ 0 ]
676
+ )-> setTime ( $ occurrence [ 1 ], $ occurrence [ 2 ], $ occurrence [ 3 ]) ;
639
677
640
678
return ;
641
679
} else {
@@ -799,7 +837,8 @@ protected function parseRRule($rrule)
799
837
* Returns all the occurrences for a monthly frequency with a 'byDay' or
800
838
* 'byMonthDay' expansion for the current month.
801
839
*
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
803
842
*
804
843
* @return array
805
844
*/
@@ -885,8 +924,23 @@ protected function getMonthlyOccurrences()
885
924
} else {
886
925
$ result = $ byDayResults ;
887
926
}
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 );
890
944
891
945
// The last thing that needs checking is the BYSETPOS. If it's set, it
892
946
// means only certain items in the set survive the filter.
@@ -904,11 +958,40 @@ protected function getMonthlyOccurrences()
904
958
}
905
959
}
906
960
907
- sort ( $ filteredResult , SORT_NUMERIC );
961
+ usort ( $ result , $ sortLex );
908
962
909
963
return $ filteredResult ;
910
964
}
911
965
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
+
912
995
/**
913
996
* Simple mapping from iCalendar day names to day numbers.
914
997
*
0 commit comments