@@ -179,7 +179,19 @@ class RecurIterator {
179
179
this . initialized = options . initialized || false ;
180
180
181
181
if ( ! this . initialized ) {
182
- this . init ( ) ;
182
+ try {
183
+ this . init ( ) ;
184
+ } catch ( e ) {
185
+ if ( e instanceof InvalidRecurrenceRuleError ) {
186
+ // Init may error if there are no possible recurrence instances from
187
+ // the rule, but we don't want to bubble this error up. Instead, we
188
+ // create an empty iterator.
189
+ this . completed = true ;
190
+ } else {
191
+ // Propagate other errors to consumers.
192
+ throw e ;
193
+ }
194
+ }
183
195
}
184
196
}
185
197
@@ -251,14 +263,28 @@ class RecurIterator {
251
263
}
252
264
253
265
if ( this . rule . freq == "YEARLY" ) {
254
- for ( ; ; ) {
266
+ // Some yearly recurrence rules may be specific enough to not actually
267
+ // occur on a yearly basis, e.g. the 29th day of February or the fifth
268
+ // Monday of a given month. The standard isn't clear on the intended
269
+ // behavior in these cases, but `libical` at least will iterate until it
270
+ // finds a matching year.
271
+ // CAREFUL: Some rules may specify an occurrence that can never happen,
272
+ // e.g. the first Monday of April so long as it falls on the 15th
273
+ // through the 21st. Detecting these is non-trivial, so ensure that we
274
+ // stop iterating at some point.
275
+ const untilYear = this . rule . until ? this . rule . until . year : 20000 ;
276
+ while ( this . last . year <= untilYear ) {
255
277
this . expand_year_days ( this . last . year ) ;
256
278
if ( this . days . length > 0 ) {
257
279
break ;
258
280
}
259
281
this . increment_year ( this . rule . interval ) ;
260
282
}
261
283
284
+ if ( this . days . length == 0 ) {
285
+ throw new InvalidRecurrenceRuleError ( ) ;
286
+ }
287
+
262
288
this . _nextByYearDay ( ) ;
263
289
}
264
290
@@ -348,11 +374,10 @@ class RecurIterator {
348
374
349
375
if ( ( this . rule . count && this . occurrence_number >= this . rule . count ) ||
350
376
( this . rule . until && this . last . compare ( this . rule . until ) > 0 ) ) {
351
-
352
- //XXX: right now this is just a flag and has no impact
353
- // we can simplify the above case to check for completed later.
354
377
this . completed = true ;
378
+ }
355
379
380
+ if ( this . completed ) {
356
381
return null ;
357
382
}
358
383
@@ -362,7 +387,6 @@ class RecurIterator {
362
387
return this . last ;
363
388
}
364
389
365
-
366
390
let valid ;
367
391
do {
368
392
valid = 1 ;
@@ -1391,4 +1415,18 @@ class RecurIterator {
1391
1415
return result ;
1392
1416
}
1393
1417
}
1418
+
1419
+ /**
1420
+ * An error indicating that a recurrence rule is invalid and produces no
1421
+ * occurrences.
1422
+ *
1423
+ * @extends {Error }
1424
+ * @class
1425
+ */
1426
+ class InvalidRecurrenceRuleError extends Error {
1427
+ constructor ( ) {
1428
+ super ( "Recurrence rule has no valid occurrences" ) ;
1429
+ }
1430
+ }
1431
+
1394
1432
export default RecurIterator ;
0 commit comments