@@ -233,7 +233,7 @@ class RecurIterator {
233
233
this . last . second = this . setup_defaults ( "BYSECOND" , "SECONDLY" , this . dtstart . second ) ;
234
234
this . last . minute = this . setup_defaults ( "BYMINUTE" , "MINUTELY" , this . dtstart . minute ) ;
235
235
this . last . hour = this . setup_defaults ( "BYHOUR" , "HOURLY" , this . dtstart . hour ) ;
236
- let dayOffset = this . last . day = this . setup_defaults ( "BYMONTHDAY" , "DAILY" , this . dtstart . day ) ;
236
+ this . last . day = this . setup_defaults ( "BYMONTHDAY" , "DAILY" , this . dtstart . day ) ;
237
237
this . last . month = this . setup_defaults ( "BYMONTH" , "MONTHLY" , this . dtstart . month ) ;
238
238
239
239
if ( this . rule . freq == "WEEKLY" ) {
@@ -262,77 +262,79 @@ class RecurIterator {
262
262
this . _nextByYearDay ( ) ;
263
263
}
264
264
265
- if ( this . rule . freq == "MONTHLY" && this . has_by_data ( "BYDAY" ) ) {
266
- let tempLast = null ;
267
- let initLast = this . last . clone ( ) ;
268
- let daysInMonth = Time . daysInMonth ( this . last . month , this . last . year ) ;
269
-
270
- // Check every weekday in BYDAY with relative dow and pos.
271
- for ( let bydow of this . by_data . BYDAY ) {
272
- this . last = initLast . clone ( ) ;
273
- let [ pos , dow ] = this . ruleDayOfWeek ( bydow ) ;
274
- let dayOfMonth = this . last . nthWeekDay ( dow , pos ) ;
275
-
276
- // If |pos| >= 6, the byday is invalid for a monthly rule.
277
- if ( pos >= 6 || pos <= - 6 ) {
278
- throw new Error ( "Malformed values in BYDAY part" ) ;
279
- }
265
+ if ( this . rule . freq == "MONTHLY" ) {
266
+ if ( this . has_by_data ( "BYDAY" ) ) {
267
+ let tempLast = null ;
268
+ let initLast = this . last . clone ( ) ;
269
+ let daysInMonth = Time . daysInMonth ( this . last . month , this . last . year ) ;
270
+
271
+ // Check every weekday in BYDAY with relative dow and pos.
272
+ for ( let bydow of this . by_data . BYDAY ) {
273
+ this . last = initLast . clone ( ) ;
274
+ let [ pos , dow ] = this . ruleDayOfWeek ( bydow ) ;
275
+ let dayOfMonth = this . last . nthWeekDay ( dow , pos ) ;
276
+
277
+ // If |pos| >= 6, the byday is invalid for a monthly rule.
278
+ if ( pos >= 6 || pos <= - 6 ) {
279
+ throw new Error ( "Malformed values in BYDAY part" ) ;
280
+ }
280
281
281
- // If a Byday with pos=+/-5 is not in the current month it
282
- // must be searched in the next months.
283
- if ( dayOfMonth > daysInMonth || dayOfMonth <= 0 ) {
284
- // Skip if we have already found a "last" in this month.
285
- if ( tempLast && tempLast . month == initLast . month ) {
286
- continue ;
282
+ // If a Byday with pos=+/-5 is not in the current month it
283
+ // must be searched in the next months.
284
+ if ( dayOfMonth > daysInMonth || dayOfMonth <= 0 ) {
285
+ // Skip if we have already found a "last" in this month.
286
+ if ( tempLast && tempLast . month == initLast . month ) {
287
+ continue ;
288
+ }
289
+ while ( dayOfMonth > daysInMonth || dayOfMonth <= 0 ) {
290
+ this . increment_month ( ) ;
291
+ daysInMonth = Time . daysInMonth ( this . last . month , this . last . year ) ;
292
+ dayOfMonth = this . last . nthWeekDay ( dow , pos ) ;
293
+ }
287
294
}
288
- while ( dayOfMonth > daysInMonth || dayOfMonth <= 0 ) {
289
- this . increment_month ( ) ;
290
- daysInMonth = Time . daysInMonth ( this . last . month , this . last . year ) ;
291
- dayOfMonth = this . last . nthWeekDay ( dow , pos ) ;
295
+
296
+ this . last . day = dayOfMonth ;
297
+ if ( ! tempLast || this . last . compare ( tempLast ) < 0 ) {
298
+ tempLast = this . last . clone ( ) ;
292
299
}
293
300
}
294
-
295
- this . last . day = dayOfMonth ;
296
- if ( ! tempLast || this . last . compare ( tempLast ) < 0 ) {
297
- tempLast = this . last . clone ( ) ;
301
+ this . last = tempLast . clone ( ) ;
302
+
303
+ //XXX: This feels like a hack, but we need to initialize
304
+ // the BYMONTHDAY case correctly and byDayAndMonthDay handles
305
+ // this case. It accepts a special flag which will avoid incrementing
306
+ // the initial value without the flag days that match the start time
307
+ // would be missed.
308
+ if ( this . has_by_data ( 'BYMONTHDAY' ) ) {
309
+ this . _byDayAndMonthDay ( true ) ;
298
310
}
299
- }
300
- this . last = tempLast . clone ( ) ;
301
-
302
- //XXX: This feels like a hack, but we need to initialize
303
- // the BYMONTHDAY case correctly and byDayAndMonthDay handles
304
- // this case. It accepts a special flag which will avoid incrementing
305
- // the initial value without the flag days that match the start time
306
- // would be missed.
307
- if ( this . has_by_data ( 'BYMONTHDAY' ) ) {
308
- this . _byDayAndMonthDay ( true ) ;
309
- }
310
-
311
- if ( this . last . day > daysInMonth || this . last . day == 0 ) {
312
- throw new Error ( "Malformed values in BYDAY part" ) ;
313
- }
314
- } else if ( this . has_by_data ( "BYMONTHDAY" ) ) {
315
- // Attempting to access `this.last.day` will cause the date to be normalised.
316
- // So it will never be a negative value or more than the number of days in the month.
317
- // We keep the value in a separate variable instead.
318
311
319
- // Change the day value so that normalisation won't change the month.
320
- this . last . day = 1 ;
321
- let daysInMonth = Time . daysInMonth ( this . last . month , this . last . year ) ;
312
+ if ( this . last . day > daysInMonth || this . last . day == 0 ) {
313
+ throw new Error ( "Malformed values in BYDAY part" ) ;
314
+ }
315
+ } else if ( this . has_by_data ( "BYMONTHDAY" ) ) {
316
+ // Change the day value so that normalisation won't change the month.
317
+ this . last . day = 1 ;
322
318
323
- if ( dayOffset < 0 ) {
324
- // A negative value represents days before the end of the month.
325
- this . last . day = daysInMonth + dayOffset + 1 ;
326
- } else if ( this . by_data . BYMONTHDAY [ 0 ] > daysInMonth ) {
327
- // There's no occurrence in this month, find the next valid month.
328
- // The longest possible sequence of skipped months is February-April-June,
329
- // so we might need to call next_month up to three times.
330
- if ( ! this . next_month ( ) && ! this . next_month ( ) && ! this . next_month ( ) ) {
331
- throw new Error ( "No possible occurrences" ) ;
319
+ // Get a sorted list of days in the starting month that match the rule.
320
+ let normalized = this . normalizeByMonthDayRules (
321
+ this . last . year ,
322
+ this . last . month ,
323
+ this . rule . parts . BYMONTHDAY
324
+ ) . filter ( d => d >= this . last . day ) ;
325
+
326
+ if ( normalized . length ) {
327
+ // There's at least one valid day, use it.
328
+ this . last . day = normalized [ 0 ] ;
329
+ this . by_data . BYMONTHDAY = normalized ;
330
+ } else {
331
+ // There's no occurrence in this month, find the next valid month.
332
+ // The longest possible sequence of skipped months is February-April-June,
333
+ // so we might need to call next_month up to three times.
334
+ if ( ! this . next_month ( ) && ! this . next_month ( ) && ! this . next_month ( ) ) {
335
+ throw new Error ( "No possible occurrences" ) ;
336
+ }
332
337
}
333
- } else {
334
- // Otherwise, reset the day.
335
- this . last . day = dayOffset ;
336
338
}
337
339
}
338
340
}
@@ -513,7 +515,10 @@ class RecurIterator {
513
515
let rule ;
514
516
515
517
for ( ; ruleIdx < len ; ruleIdx ++ ) {
516
- rule = rules [ ruleIdx ] ;
518
+ rule = parseInt ( rules [ ruleIdx ] , 10 ) ;
519
+ if ( isNaN ( rule ) ) {
520
+ throw new Error ( 'Invalid BYMONTHDAY value' ) ;
521
+ }
517
522
518
523
// if this rule falls outside of given
519
524
// month discard it.
@@ -747,6 +752,9 @@ class RecurIterator {
747
752
if ( this . by_indices . BYMONTHDAY >= this . by_data . BYMONTHDAY . length ) {
748
753
this . by_indices . BYMONTHDAY = 0 ;
749
754
this . increment_month ( ) ;
755
+ if ( this . by_indices . BYMONTHDAY >= this . by_data . BYMONTHDAY . length ) {
756
+ return 0 ;
757
+ }
750
758
}
751
759
752
760
let daysInMonth = Time . daysInMonth ( this . last . month , this . last . year ) ;
@@ -762,7 +770,6 @@ class RecurIterator {
762
770
} else {
763
771
this . last . day = day ;
764
772
}
765
-
766
773
} else {
767
774
this . increment_month ( ) ;
768
775
let daysInMonth = Time . daysInMonth ( this . last . month , this . last . year ) ;
@@ -835,7 +842,6 @@ class RecurIterator {
835
842
}
836
843
837
844
next_year ( ) {
838
-
839
845
if ( this . next_hour ( ) == 0 ) {
840
846
return 0 ;
841
847
}
@@ -844,6 +850,13 @@ class RecurIterator {
844
850
this . days_index = 0 ;
845
851
do {
846
852
this . increment_year ( this . rule . interval ) ;
853
+ if ( this . has_by_data ( "BYMONTHDAY" ) ) {
854
+ this . by_data . BYMONTHDAY = this . normalizeByMonthDayRules (
855
+ this . last . year ,
856
+ this . last . month ,
857
+ this . rule . parts . BYMONTHDAY
858
+ ) ;
859
+ }
847
860
this . expand_year_days ( this . last . year ) ;
848
861
} while ( this . days . length == 0 ) ;
849
862
}
@@ -854,19 +867,19 @@ class RecurIterator {
854
867
}
855
868
856
869
_nextByYearDay ( ) {
857
- let doy = this . days [ this . days_index ] ;
858
- let year = this . last . year ;
859
- if ( doy < 1 ) {
860
- // Time.fromDayOfYear(doy, year) indexes relative to the
861
- // start of the given year. That is different from the
862
- // semantics of BYYEARDAY where negative indexes are an
863
- // offset from the end of the given year.
864
- doy += 1 ;
865
- year += 1 ;
866
- }
867
- let next = Time . fromDayOfYear ( doy , year ) ;
868
- this . last . day = next . day ;
869
- this . last . month = next . month ;
870
+ let doy = this . days [ this . days_index ] ;
871
+ let year = this . last . year ;
872
+ if ( doy < 1 ) {
873
+ // Time.fromDayOfYear(doy, year) indexes relative to the
874
+ // start of the given year. That is different from the
875
+ // semantics of BYYEARDAY where negative indexes are an
876
+ // offset from the end of the given year.
877
+ doy += 1 ;
878
+ year += 1 ;
879
+ }
880
+ let next = Time . fromDayOfYear ( doy , year ) ;
881
+ this . last . day = next . day ;
882
+ this . last . month = next . month ;
870
883
}
871
884
872
885
/**
@@ -953,9 +966,19 @@ class RecurIterator {
953
966
this . increment_year ( years ) ;
954
967
}
955
968
}
969
+
970
+ if ( this . has_by_data ( "BYMONTHDAY" ) ) {
971
+ this . by_data . BYMONTHDAY = this . normalizeByMonthDayRules (
972
+ this . last . year ,
973
+ this . last . month ,
974
+ this . rule . parts . BYMONTHDAY
975
+ ) ;
976
+ }
956
977
}
957
978
958
979
increment_year ( inc ) {
980
+ // Don't jump into the next month if this.last is Feb 29.
981
+ this . last . day = 1 ;
959
982
this . last . year += inc ;
960
983
}
961
984
@@ -1177,6 +1200,14 @@ class RecurIterator {
1177
1200
} else {
1178
1201
this . days = [ ] ;
1179
1202
}
1203
+
1204
+ let daysInYear = Time . isLeapYear ( aYear ) ? 366 : 365 ;
1205
+ this . days . sort ( ( a , b ) => {
1206
+ if ( a < 0 ) a += daysInYear + 1 ;
1207
+ if ( b < 0 ) b += daysInYear + 1 ;
1208
+ return a - b ;
1209
+ } ) ;
1210
+
1180
1211
return 0 ;
1181
1212
}
1182
1213
0 commit comments