@@ -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,22 @@ 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
+ if ($ occurrence [2 ] > $ currentMinuteOfMonth ) {
459
+ break 2 ;
460
+ } elseif ($ occurrence [2 ] < $ currentMinuteOfMonth ) {
461
+ continue ;
462
+ }
463
+ if ($ occurrence [3 ] > $ currentSecondOfMonth ) {
446
464
break 2 ;
447
465
}
448
466
}
@@ -461,13 +479,16 @@ protected function nextMonthly()
461
479
// This goes to 0 because we need to start counting at the
462
480
// beginning.
463
481
$ currentDayOfMonth = 0 ;
482
+ $ currentHourOfMonth = 0 ;
483
+ $ currentMinuteOfMonth = 0 ;
484
+ $ currentSecondOfMonth = 0 ;
464
485
}
465
486
466
487
$ this ->currentDate = $ this ->currentDate ->setDate (
467
488
(int ) $ this ->currentDate ->format ('Y ' ),
468
489
(int ) $ this ->currentDate ->format ('n ' ),
469
- ( int ) $ occurrence
470
- );
490
+ $ occurrence[ 0 ]
491
+ )-> setTime ( $ occurrence [ 1 ], $ occurrence [ 2 ], $ occurrence [ 3 ]) ;
471
492
}
472
493
473
494
/**
@@ -478,6 +499,9 @@ protected function nextYearly()
478
499
$ currentMonth = $ this ->currentDate ->format ('n ' );
479
500
$ currentYear = $ this ->currentDate ->format ('Y ' );
480
501
$ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
502
+ $ currentHourOfMonth = $ this ->currentDate ->format ('G ' );
503
+ $ currentMinuteOfMonth = $ this ->currentDate ->format ('i ' );
504
+ $ currentSecondOfMonth = $ this ->currentDate ->format ('s ' );
481
505
482
506
// No sub-rules, so we just advance by year
483
507
if (empty ($ this ->byMonth )) {
@@ -588,25 +612,38 @@ protected function nextYearly()
588
612
return ;
589
613
}
590
614
591
- $ currentMonth = $ this ->currentDate ->format ('n ' );
592
- $ currentYear = $ this ->currentDate ->format ('Y ' );
593
- $ currentDayOfMonth = $ this ->currentDate ->format ('j ' );
594
-
595
615
$ advancedToNewMonth = false ;
596
616
597
617
// If we got a byDay or getMonthDay filter, we must first expand
598
618
// further.
599
619
if ($ this ->byDay || $ this ->byMonthDay ) {
600
620
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
+ }
610
647
}
611
648
}
612
649
@@ -633,8 +670,8 @@ protected function nextYearly()
633
670
$ this ->currentDate = $ this ->currentDate ->setDate (
634
671
(int ) $ currentYear ,
635
672
(int ) $ currentMonth ,
636
- (int ) $ occurrence
637
- );
673
+ (int ) $ occurrence[ 0 ]
674
+ )-> setTime ( $ occurrence [ 1 ], $ occurrence [ 2 ], $ occurrence [ 3 ]) ;
638
675
639
676
return ;
640
677
} else {
@@ -798,7 +835,8 @@ protected function parseRRule($rrule)
798
835
* Returns all the occurrences for a monthly frequency with a 'byDay' or
799
836
* 'byMonthDay' expansion for the current month.
800
837
*
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
802
840
*
803
841
* @return array
804
842
*/
@@ -884,8 +922,23 @@ protected function getMonthlyOccurrences()
884
922
} else {
885
923
$ result = $ byDayResults ;
886
924
}
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 );
889
942
890
943
// The last thing that needs checking is the BYSETPOS. If it's set, it
891
944
// means only certain items in the set survive the filter.
@@ -903,11 +956,40 @@ protected function getMonthlyOccurrences()
903
956
}
904
957
}
905
958
906
- sort ( $ filteredResult , SORT_NUMERIC );
959
+ usort ( $ result , $ sortLex );
907
960
908
961
return $ filteredResult ;
909
962
}
910
963
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
+
911
993
/**
912
994
* Simple mapping from iCalendar day names to day numbers.
913
995
*
0 commit comments