@@ -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 )) {
@@ -588,25 +613,38 @@ protected function nextYearly()
588
613
return ;
589
614
}
590
615
591
- $ currentMonth = $ this ->currentDate ->format ('n ' );
592
- $ currentYear = $ this ->currentDate ->format ('Y ' );
593
- $ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
594
-
595
616
$ advancedToNewMonth = false ;
596
617
597
618
// If we got a byDay or getMonthDay filter, we must first expand
598
619
// further.
599
620
if ($ this ->byDay || $ this ->byMonthDay ) {
600
621
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 ;
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
+ } elseif ($ occurrence [0 ] < $ currentDayOfMonth ) {
633
+ continue ;
634
+ }
635
+ if ($ occurrence [1 ] > $ currentHourOfMonth ) {
636
+ break 2 ;
637
+ } elseif ($ occurrence [1 ] < $ currentHourOfMonth ) {
638
+ continue ;
639
+ }
640
+ if ($ occurrence [2 ] > $ currentMinuteOfMonth ) {
641
+ break 2 ;
642
+ } elseif ($ occurrence [2 ] < $ currentMinuteOfMonth ) {
643
+ continue ;
644
+ }
645
+ if ($ occurrence [3 ] > $ currentSecondOfMonth ) {
646
+ break 2 ;
647
+ }
610
648
}
611
649
}
612
650
@@ -633,8 +671,8 @@ protected function nextYearly()
633
671
$ this ->currentDate = $ this ->currentDate ->setDate (
634
672
(int ) $ currentYear ,
635
673
(int ) $ currentMonth ,
636
- (int ) $ occurrence
637
- );
674
+ (int ) $ occurrence[ 0 ]
675
+ )-> setTime ( $ occurrence [ 1 ], $ occurrence [ 2 ], $ occurrence [ 3 ]) ;
638
676
639
677
return ;
640
678
} else {
@@ -798,7 +836,8 @@ protected function parseRRule($rrule)
798
836
* Returns all the occurrences for a monthly frequency with a 'byDay' or
799
837
* 'byMonthDay' expansion for the current month.
800
838
*
801
- * The returned list is an array of integers with the day of month (1-31).
839
+ * The returned list is an array of arrays with as first element the day of month (1-31);
840
+ * the hour; the minute and second of the occurence
802
841
*
803
842
* @return array
804
843
*/
@@ -884,8 +923,23 @@ protected function getMonthlyOccurrences()
884
923
} else {
885
924
$ result = $ byDayResults ;
886
925
}
887
- $ result = array_unique ($ result );
888
- sort ($ result , SORT_NUMERIC );
926
+
927
+ $ result = $ this ->addDailyOccurences ($ result );
928
+ $ result = array_unique ($ result , SORT_REGULAR );
929
+ $ sortLex = function ($ a , $ b ) {
930
+ if ($ a [0 ] != $ b [0 ]) {
931
+ return $ a [0 ] - $ b [0 ];
932
+ }
933
+ if ($ a [1 ] != $ b [1 ]) {
934
+ return $ a [1 ] - $ b [1 ];
935
+ }
936
+ if ($ a [2 ] != $ b [2 ]) {
937
+ return $ a [2 ] - $ b [2 ];
938
+ }
939
+
940
+ return $ a [3 ] - $ b [3 ];
941
+ };
942
+ usort ($ result , $ sortLex );
889
943
890
944
// The last thing that needs checking is the BYSETPOS. If it's set, it
891
945
// means only certain items in the set survive the filter.
@@ -903,11 +957,40 @@ protected function getMonthlyOccurrences()
903
957
}
904
958
}
905
959
906
- sort ( $ filteredResult , SORT_NUMERIC );
960
+ usort ( $ result , $ sortLex );
907
961
908
962
return $ filteredResult ;
909
963
}
910
964
965
+ /**
966
+ * Expends daily occurrences to an array of days that an event occurs on.
967
+ *
968
+ * @param array $result an array of integers with the day of month (1-31);
969
+ *
970
+ * @return array an array of arrays with the day of the month, hours, minute and seconds of the occurence
971
+ */
972
+ protected function addDailyOccurences (array $ result )
973
+ {
974
+ $ output = [];
975
+ $ hour = (int ) $ this ->currentDate ->format ('G ' );
976
+ $ minute = (int ) $ this ->currentDate ->format ('i ' );
977
+ $ second = (int ) $ this ->currentDate ->format ('s ' );
978
+ foreach ($ result as $ day ) {
979
+ $ seconds = $ this ->bySecond ? $ this ->bySecond : [$ second ];
980
+ $ minutes = $ this ->byMinute ? $ this ->byMinute : [$ minute ];
981
+ $ hours = $ this ->byHour ? $ this ->byHour : [$ hour ];
982
+ foreach ($ hours as $ h ) {
983
+ foreach ($ minutes as $ m ) {
984
+ foreach ($ seconds as $ s ) {
985
+ $ output [] = [(int ) $ day , (int ) $ h , (int ) $ m , (int ) $ s ];
986
+ }
987
+ }
988
+ }
989
+ }
990
+
991
+ return $ output ;
992
+ }
993
+
911
994
/**
912
995
* Simple mapping from iCalendar day names to day numbers.
913
996
*
0 commit comments