@@ -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
+ } 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 ) {
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,39 @@ 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
+
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
+ }
610
648
}
611
649
}
612
650
@@ -633,9 +671,8 @@ protected function nextYearly()
633
671
$ this ->currentDate = $ this ->currentDate ->setDate (
634
672
(int ) $ currentYear ,
635
673
(int ) $ currentMonth ,
636
- (int ) $ occurrence
637
- );
638
-
674
+ (int ) $ occurrence [0 ]
675
+ )->setTime ($ occurrence [1 ], $ occurrence [2 ], $ occurrence [3 ]);
639
676
return ;
640
677
} else {
641
678
// These are the 'byMonth' rules, if there are no byDay or
@@ -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,22 @@ 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
+ return $ a [3 ] - $ b [3 ];
939
+ };
940
+ usort ($ result , $ sortLex );
889
941
890
942
// The last thing that needs checking is the BYSETPOS. If it's set, it
891
943
// means only certain items in the set survive the filter.
@@ -903,11 +955,37 @@ protected function getMonthlyOccurrences()
903
955
}
904
956
}
905
957
906
- sort ( $ filteredResult , SORT_NUMERIC );
958
+ usort ( $ result , $ sortLex );
907
959
908
960
return $ filteredResult ;
909
961
}
910
962
963
+ /**
964
+ * Expends daily occurrences to an array of days that an event occurs on
965
+ * @param array $result an array of integers with the day of month (1-31);
966
+ * @return array an array of arrays with the day of the month, hours, minute and seconds of the occurence
967
+ */
968
+ protected function addDailyOccurences (array $ result ) {
969
+ $ output = [];
970
+ $ hour = (int ) $ this ->currentDate ->format ('G ' );
971
+ $ minute = (int ) $ this ->currentDate ->format ('i ' );
972
+ $ second = (int ) $ this ->currentDate ->format ('s ' );
973
+ foreach ($ result as $ day )
974
+ {
975
+ $ seconds = $ this ->bySecond ? $ this ->bySecond : [ $ second ];
976
+ $ minutes = $ this ->byMinute ? $ this ->byMinute : [ $ minute ];
977
+ $ hours = $ this ->byHour ? $ this ->byHour : [ $ hour ];
978
+ foreach ($ hours as $ h ) {
979
+ foreach ($ minutes as $ m ) {
980
+ foreach ($ seconds as $ s ) {
981
+ $ output [] = [(int ) $ day , (int ) $ h , (int ) $ m , (int ) $ s ];
982
+ }
983
+ }
984
+ }
985
+ }
986
+ return $ output ;
987
+ }
988
+
911
989
/**
912
990
* Simple mapping from iCalendar day names to day numbers.
913
991
*
0 commit comments