PageSourceSearch

https://prh.fi/stato/js/lib/rrule.js

js prh.fi collected 2026-09-24 08:46:32 UTC 68,542 bytes, 2,281 lines download raw bytes

1/*!
2 * rrule.js - Library for working with recurrence rules for calendar dates.
3 * https://github.com/jkbrzt/rrule
4 *
5 * Copyright 2010, Jakub Roztocil and Lars Schoning
6 * Licenced under the BSD licence.
7 * https://github.com/jkbrzt/rrule/blob/master/LICENCE
8 *
9 * Based on:
10 * python-dateutil - Extensions to the standard Python datetime module.
11 * Copyright (c) 2003-2011 - Gustavo Niemeyer <[email protected]>
12 * Copyright (c) 2012 - Tomi Pieviläinen <[email protected]>
13 * https://github.com/jkbrzt/rrule/blob/master/LICENCE
14 *
15 */
16/* global module, define */
17
18;(function (root, factory) {
19  if (typeof module === 'object' && module.exports) {
20    module.exports = factory()
21  } else if (typeof define === 'function' && define.amd) {
22    define([], factory)
23  } else {
24    root.RRule = factory(root)
25    root.RRuleSet = root.RRule.RRuleSet
26    root.rrulestr = root.RRule.rrulestr
27  }
28}(typeof window === 'object' ? window : this, function (root) {
29  // =============================================================================
30  // Date utilities
31  // =============================================================================
32
33  /**
34   * General date-related utilities.
35   * Also handles several incompatibilities between JavaScript and Python
36   *
37   */
38  var dateutil = {
39    MONTH_DAYS: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
40
41    /**
42     * Number of milliseconds of one day
43     */
44    ONE_DAY: 1000 * 60 * 60 * 24,
45
46    /**
47     * @see: <http://docs.python.org/library/datetime.html#datetime.MAXYEAR>
48     */
49    MAXYEAR: 9999,
50
51    /**
52     * Python uses 1-Jan-1 as the base for calculating ordinals but we don't
53     * want to confuse the JS engine with milliseconds > Number.MAX_NUMBER,
54     * therefore we use 1-Jan-1970 instead
55     */
56    ORDINAL_BASE: new Date(1970, 0, 1),
57
58    /**
59     * Python: MO-SU: 0 - 6
60     * JS: SU-SAT 0 - 6
61     */
62    PY_WEEKDAYS: [6, 0, 1, 2, 3, 4, 5],
63
64    /**
65     * py_date.timetuple()[7]
66     */
67    getYearDay: function (date) {
68      var dateNoTime = new Date(
69        date.getFullYear(), date.getMonth(), date.getDate())
70      return Math.ceil(
71        (dateNoTime - new Date(date.getFullYear(), 0, 1)) / dateutil.ONE_DAY) + 1
72    },
73
74    isLeapYear: function (year) {
75      if (year instanceof Date) year = year.getFullYear()
76      return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)
77    },
78
79    /**
80     * @return {Number} the date's timezone offset in ms
81     */
82    tzOffset: function (date) {
83      return date.getTimezoneOffset() * 60 * 1000
84    },
85
86    /**
87     * @see: <http://www.mcfedries.com/JavaScript/DaysBetween.asp>
88     */
89    daysBetween: function (date1, date2) {
90      // The number of milliseconds in one day
91      // Convert both dates to milliseconds
92      var date1ms = date1.getTime() - dateutil.tzOffset(date1)
93      var date2ms = date2.getTime() - dateutil.tzOffset(date2)
94      // Calculate the difference in milliseconds
95      var differencems = Math.abs(date1ms - date2ms)
96      // Convert back to days and return
97      return Math.round(differencems / dateutil.ONE_DAY)
98    },
99
100    /**
101     * @see: <http://docs.python.org/library/datetime.html#datetime.date.toordinal>
102     */
103    toOrdinal: function (date) {
104      if (date < dateutil.ORDINAL_BASE) {
105        throw new Error('dates lower than ' + dateutil.ORDINAL_BASE + ' are not supported')
106      }
107      return dateutil.daysBetween(date, dateutil.ORDINAL_BASE)
108    },
109
110    /**
111     * @see - <http://docs.python.org/library/datetime.html#datetime.date.fromordinal>
112     */
113    fromOrdinal: function (ordinal) {
114      var millisecsFromBase = ordinal * dateutil.ONE_DAY
115      return new Date(dateutil.ORDINAL_BASE.getTime() -
116        dateutil.tzOffset(dateutil.ORDINAL_BASE) +
117        millisecsFromBase +
118        dateutil.tzOffset(new Date(millisecsFromBase)))
119    },
120
121    /**
122     * @see: <http://docs.python.org/library/calendar.html#calendar.monthrange>
123     */
124    monthRange: function (year, month) {
125      var date = new Date(year, month, 1)
126      return [dateutil.getWeekday(date), dateutil.getMonthDays(date)]
127    },
128
129    getMonthDays: function (date) {
130      var month = date.getMonth()
131      return month === 1 && dateutil.isLeapYear(date)
132        ? 29 : dateutil.MONTH_DAYS[month]
133    },
134
135    /**
136     * @return {Number} python-like weekday
137     */
138    getWeekday: function (date) {
139      return dateutil.PY_WEEKDAYS[date.getDay()]
140    },
141
142    /**
143     * @see: <http://docs.python.org/library/datetime.html#datetime.datetime.combine>
144     */
145    combine: function (date, time) {
146      time = time || date
147      return new Date(
148        date.getFullYear(), date.getMonth(), date.getDate(),
149        time.getHours(), time.getMinutes(), time.getSeconds(),
150        time.getMilliseconds())
151    },
152
153    clone: function (date) {
154      var dolly = new Date(date.getTime())
155      return dolly
156    },
157
158    cloneDates: function (dates) {
159      var clones = []
160      for (var i = 0; i < dates.length; i++) {
161        clones.push(dateutil.clone(dates[i]))
162      }
163      return clones
164    },
165
166    /**
167     * Sorts an array of Date or dateutil.Time objects
168     */
169    sort: function (dates) {
170      dates.sort(function (a, b) {
171        return a.getTime() - b.getTime()
172      })
173    },
174
175    timeToUntilString: function (time) {
176      var comp
177      var date = new Date(time)
178      var comps = [
179        date.getUTCFullYear(),
180        date.getUTCMonth() + 1,
181        date.getUTCDate(),
182        'T',
183        date.getUTCHours(),
184        date.getUTCMinutes(),
185        date.getUTCSeconds(),
186        'Z'
187      ]
188
189      for (var i = 0; i < comps.length; i++) {
190        comp = comps[i]
191        if (!/[TZ]/.test(comp) && comp < 10) comps[i] = '0' + String(comp)
192      }
193      return comps.join('')
194    },
195
196    untilStringToDate: function (until) {
197      var re = /^(\d{4})(\d{2})(\d{2})(T(\d{2})(\d{2})(\d{2})Z?)?$/
198      var bits = re.exec(until)
199      if (!bits) throw new Error('Invalid UNTIL value: ' + until)
200      return new Date(Date.UTC(
201        bits[1],
202        bits[2] - 1,
203        bits[3],
204        bits[5] || 0,
205        bits[6] || 0,
206        bits[7] || 0))
207    }
208  }
209
210  dateutil.Time = function (hour, minute, second, millisecond) {
211    this.hour = hour
212    this.minute = minute
213    this.second = second
214    this.millisecond = millisecond || 0
215  }
216
217  dateutil.Time.prototype = {
218    constructor: dateutil.Time,
219    getHours: function () {
220      return this.hour
221    },
222    getMinutes: function () {
223      return this.minute
224    },
225    getSeconds: function () {
226      return this.second
227    },
228    getMilliseconds: function () {
229      return this.millisecond
230    },
231    getTime: function () {
232      return ((this.hour * 60 * 60) + (this.minute * 60) + this.second) * 1000 +
233        this.millisecond
234    }
235  }
236
237  // =============================================================================
238  // Helper functions
239  // =============================================================================
240
241  /**
242   * Simplified version of python's range()
243   */
244  var range = function (start, end) {
245    if (arguments.length === 1) {
246      end = start
247      start = 0
248    }
249    var rang = []
250    for (var i = start; i < end; i++) rang.push(i)
251    return rang
252  }
253
254  var repeat = function (value, times) {
255    var i = 0
256    var array = []
257
258    if (value instanceof Array) {
259      for (; i < times; i++) array[i] = [].concat(value)
260    } else {
261      for (; i < times; i++) array[i] = value
262    }
263    return array
264  }
265
266  /**
267   * Python like split
268   */
269  var split = function (str, sep, num) {
270    var splits = str.split(sep)
271    return num
272      ? splits.slice(0, num).concat([splits.slice(num).join(sep)]) : splits
273  }
274
275  /**
276   * closure/goog/math/math.js:modulo
277   * Copyright 2006 The Closure Library Authors.
278   * The % operator in JavaScript returns the remainder of a / b, but differs from
279   * some other languages in that the result will have the same sign as the
280   * dividend. For example, -1 % 8 == -1, whereas in some other languages
281   * (such as Python) the result would be 7. This function emulates the more
282   * correct modulo behavior, which is useful for certain applications such as
283   * calculating an offset index in a circular list.
284   *
285   * @param {number} a The dividend.
286   * @param {number} b The divisor.
287   * @return {number} a % b where the result is between 0 and b (either 0 <= x < b
288   *     or b < x <= 0, depending on the sign of b).
289   */
290  var pymod = function (a, b) {
291    var r = a % b
292    // If r and b differ in sign, add b to wrap the result to the correct sign.
293    return (r * b < 0) ? r + b : r
294  }
295
296  /**
297   * @see: <http://docs.python.org/library/functions.html#divmod>
298   */
299  var divmod = function (a, b) {
300    return {div: Math.floor(a / b), mod: pymod(a, b)}
301  }
302
303  /**
304   * Python-like boolean
305   * @return {Boolean} value of an object/primitive, taking into account
306   * the fact that in Python an empty list's/tuple's
307   * boolean value is False, whereas in JS it's true
308   */
309  var plb = function (obj) {
310    return (obj instanceof Array && obj.length === 0)
311      ? false : Boolean(obj)
312  }
313
314  /**
315   * Return true if a value is in an array
316   */
317  var contains = function (arr, val) {
318    return arr.indexOf(val) !== -1
319  }
320
321  // =============================================================================
322  // Date masks
323  // =============================================================================
324
325  // Every mask is 7 days longer to handle cross-year weekly periods.
326
327  var M365MASK = [].concat(
328    repeat(1, 31), repeat(2, 28), repeat(3, 31),
329    repeat(4, 30), repeat(5, 31), repeat(6, 30),
330    repeat(7, 31), repeat(8, 31), repeat(9, 30),
331    repeat(10, 31), repeat(11, 30), repeat(12, 31),
332    repeat(1, 7))
333
334  var M366MASK = [].concat(
335    repeat(1, 31), repeat(2, 29), repeat(3, 31),
336    repeat(4, 30), repeat(5, 31), repeat(6, 30),
337    repeat(7, 31), repeat(8, 31), repeat(9, 30),
338    repeat(10, 31), repeat(11, 30), repeat(12, 31),
339    repeat(1, 7))
340
341  var M28 = range(1, 29)
342  var M29 = range(1, 30)
343  var M30 = range(1, 31)
344  var M31 = range(1, 32)
345
346  var MDAY366MASK = [].concat(
347    M31, M29, M31,
348    M30, M31, M30,
349    M31, M31, M30,
350    M31, M30, M31,
351    M31.slice(0, 7))
352
353  var MDAY365MASK = [].concat(
354    M31, M28, M31,
355    M30, M31, M30,
356    M31, M31, M30,
357    M31, M30, M31,
358    M31.slice(0, 7))
359
360  M28 = range(-28, 0)
361  M29 = range(-29, 0)
362  M30 = range(-30, 0)
363  M31 = range(-31, 0)
364
365  var NMDAY366MASK = [].concat(
366    M31, M29, M31,
367    M30, M31, M30,
368    M31, M31, M30,
369    M31, M30, M31,
370    M31.slice(0, 7))
371
372  var NMDAY365MASK = [].concat(
373    M31, M28, M31,
374    M30, M31, M30,
375    M31, M31, M30,
376    M31, M30, M31,
377    M31.slice(0, 7))
378
379  var M366RANGE = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
380  var M365RANGE = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
381
382  var WDAYMASK = (function () {
383    for (var wdaymask = [], i = 0; i < 55; i++) wdaymask = wdaymask.concat(range(7))
384    return wdaymask
385  }())
386
387  // =============================================================================
388  // Weekday
389  // =============================================================================
390
391  var Weekday = function (weekday, n) {
392    if (n === 0) throw new Error("Can't create weekday with n == 0")
393    this.weekday = weekday
394    this.n = n
395  }
396
397  Weekday.prototype = {
398    constructor: Weekday,
399    // __call__ - Cannot call the object directly, do it through
400    // e.g. RRule.TH.nth(-1) instead,
401    nth: function (n) {
402      return this.n === n ? this : new Weekday(this.weekday, n)
403    },
404
405    // __eq__
406    equals: function (other) {
407      return this.weekday === other.weekday && this.n === other.n
408    },
409
410    // __repr__
411    toString: function () {
412      var s = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'][this.weekday]
413      if (this.n) s = (this.n > 0 ? '+' : '') + String(this.n) + s
414      return s
415    },
416
417    getJsWeekday: function () {
418      return this.weekday === 6 ? 0 : this.weekday + 1
419    }
420
421  }
422
423  // =============================================================================
424  // RRule
425  // =============================================================================
426
427  /**
428   *
429   * @param {Object?} options - see <http://labix.org/python-dateutil/#head-cf004ee9a75592797e076752b2a889c10f445418>
430   *        The only required option is `freq`, one of RRule.YEARLY, RRule.MONTHLY, ...
431   * @constructor
432   */
433  var RRule = function (options, noCache) {
434    options = options || {}
435    // RFC string
436    this._string = null
437    this._cache = noCache ? null : {
438      all: false,
439      before: [],
440      after: [],
441      between: []
442    }
443
444    // used by toString()
445    this.origOptions = {}
446
447    var invalid = []
448    var keys = Object.keys(options)
449    var defaultKeys = Object.keys(RRule.DEFAULT_OPTIONS)
450
451    // Shallow copy for origOptions and check for invalid
452    keys.forEach(function (key) {
453      this.origOptions[key] = options[key]
454      if (!contains(defaultKeys, key)) invalid.push(key)
455    }, this)
456
457    if (invalid.length) throw new Error('Invalid options: ' + invalid.join(', '))
458
459    if (!RRule.FREQUENCIES[options.freq] && options.byeaster === null) {
460      throw new Error('Invalid frequency: ' + String(options.freq))
461    }
462
463    // Merge in default options
464    defaultKeys.forEach(function (key) {
465      if (!contains(keys, key)) options[key] = RRule.DEFAULT_OPTIONS[key]
466    })
467
468    var opts = this.options = options
469
470    if (opts.byeaster !== null) opts.freq = RRule.YEARLY
471    if (!opts.dtstart) opts.dtstart = new Date(new Date().setMilliseconds(0))
472
473    var millisecondModulo = opts.dtstart.getTime() % 1000
474    if (opts.wkst === null) {
475      opts.wkst = RRule.MO.weekday
476    } else if (typeof opts.wkst === 'number') {
477      // cool, just keep it like that
478    } else {
479      opts.wkst = opts.wkst.weekday
480    }
481
482    if (opts.bysetpos !== null) {
483      if (typeof opts.bysetpos === 'number') opts.bysetpos = [opts.bysetpos]
484
485      for (var i = 0; i < opts.bysetpos.length; i++) {
486        var v = opts.bysetpos[i]
487        if (v === 0 || !(v >= -366 && v <= 366)) {
488          throw new Error('bysetpos must be between 1 and 366,' +
489            ' or between -366 and -1')
490        }
491      }
492    }
493
494    if (!(plb(opts.byweekno) || plb(opts.byyearday) || plb(opts.bymonthday) ||
495      opts.byweekday !== null || opts.byeaster !== null)) {
496      switch (opts.freq) {
497        case RRule.YEARLY:
498          if (!opts.bymonth) opts.bymonth = opts.dtstart.getMonth() + 1
499          opts.bymonthday = opts.dtstart.getDate()
500          break
501        case RRule.MONTHLY:
502          opts.bymonthday = opts.dtstart.getDate()
503          break
504        case RRule.WEEKLY:
505          opts.byweekday = dateutil.getWeekday(opts.dtstart)
506          break
507      }
508    }
509
510    // bymonth
511    if (opts.bymonth !== null && !(opts.bymonth instanceof Array)) {
512      opts.bymonth = [opts.bymonth]
513    }
514    // byyearday
515    if (opts.byyearday !== null && !(opts.byyearday instanceof Array)) {
516      opts.byyearday = [opts.byyearday]
517    }
518
519    // bymonthday
520    if (opts.bymonthday === null) {
521      opts.bymonthday = []
522      opts.bynmonthday = []
523    } else if (opts.bymonthday instanceof Array) {
524      var bymonthday = []
525      var bynmonthday = []
526
527      for (i = 0; i < opts.bymonthday.length; i++) {
528        v = opts.bymonthday[i]
529        if (v > 0) {
530          bymonthday.push(v)
531        } else if (v < 0) {
532          bynmonthday.push(v)
533        }
534      }
535      opts.bymonthday = bymonthday
536      opts.bynmonthday = bynmonthday
537    } else {
538      if (opts.bymonthday < 0) {
539        opts.bynmonthday = [opts.bymonthday]
540        opts.bymonthday = []
541      } else {
542        opts.bynmonthday = []
543        opts.bymonthday = [opts.bymonthday]
544      }
545    }
546
547    // byweekno
548    if (opts.byweekno !== null && !(opts.byweekno instanceof Array)) {
549      opts.byweekno = [opts.byweekno]
550    }
551
552    // byweekday / bynweekday
553    if (opts.byweekday === null) {
554      opts.bynweekday = null
555    } else if (typeof opts.byweekday === 'number') {
556      opts.byweekday = [opts.byweekday]
557      opts.bynweekday = null
558    } else if (opts.byweekday instanceof Weekday) {
559      if (!opts.byweekday.n || opts.freq > RRule.MONTHLY) {
560        opts.byweekday = [opts.byweekday.weekday]
561        opts.bynweekday = null
562      } else {
563        opts.bynweekday = [
564          [opts.byweekday.weekday, opts.byweekday.n]
565        ]
566        opts.byweekday = null
567      }
568    } else {
569      var byweekday = []
570      var bynweekday = []
571
572      for (i = 0; i < opts.byweekday.length; i++) {
573        var wday = opts.byweekday[i]
574
575        if (typeof wday === 'number') {
576          byweekday.push(wday)
577        } else if (!wday.n || opts.freq > RRule.MONTHLY) {
578          byweekday.push(wday.weekday)
579        } else {
580          bynweekday.push([wday.weekday, wday.n])
581        }
582      }
583      opts.byweekday = plb(byweekday) ? byweekday : null
584      opts.bynweekday = plb(bynweekday) ? bynweekday : null
585    }
586
587    // byhour
588    if (opts.byhour === null) {
589      opts.byhour = (opts.freq < RRule.HOURLY) ? [opts.dtstart.getHours()] : null
590    } else if (typeof opts.byhour === 'number') {
591      opts.byhour = [opts.byhour]
592    }
593
594    // byminute
595    if (opts.byminute === null) {
596      opts.byminute = (opts.freq < RRule.MINUTELY)
597        ? [opts.dtstart.getMinutes()] : null
598    } else if (typeof opts.byminute === 'number') {
599      opts.byminute = [opts.byminute]
600    }
601
602    // bysecond
603    if (opts.bysecond === null) {
604      opts.bysecond = (opts.freq < RRule.SECONDLY)
605        ? [opts.dtstart.getSeconds()] : null
606    } else if (typeof opts.bysecond === 'number') {
607      opts.bysecond = [opts.bysecond]
608    }
609
610    if (opts.freq >= RRule.HOURLY) {
611      this.timeset = null
612    } else {
613      this.timeset = []
614      for (i = 0; i < opts.byhour.length; i++) {
615        var hour = opts.byhour[i]
616        for (var j = 0; j < opts.byminute.length; j++) {
617          var minute = opts.byminute[j]
618          for (var k = 0; k < opts.bysecond.length; k++) {
619            var second = opts.bysecond[k]
620            // python:
621            // datetime.time(hour, minute, second,
622            // tzinfo=self._tzinfo))
623            this.timeset.push(new dateutil.Time(hour, minute, second, millisecondModulo))
624          }
625        }
626      }
627      dateutil.sort(this.timeset)
628    }
629  }
630
631  // RRule class 'constants'
632
633  RRule.FREQUENCIES = [
634    'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
635    'HOURLY', 'MINUTELY', 'SECONDLY'
636  ]
637
638  RRule.YEARLY = 0
639  RRule.MONTHLY = 1
640  RRule.WEEKLY = 2
641  RRule.DAILY = 3
642  RRule.HOURLY = 4
643  RRule.MINUTELY = 5
644  RRule.SECONDLY = 6
645
646  RRule.MO = new Weekday(0)
647  RRule.TU = new Weekday(1)
648  RRule.WE = new Weekday(2)
649  RRule.TH = new Weekday(3)
650  RRule.FR = new Weekday(4)
651  RRule.SA = new Weekday(5)
652  RRule.SU = new Weekday(6)
653
654  RRule.DEFAULT_OPTIONS = {
655    freq: null,
656    dtstart: null,
657    interval: 1,
658    wkst: RRule.MO,
659    count: null,
660    until: null,
661    bysetpos: null,
662    bymonth: null,
663    bymonthday: null,
664    bynmonthday: null,
665    byyearday: null,
666    byweekno: null,
667    byweekday: null,
668    bynweekday: null,
669    byhour: null,
670    byminute: null,
671    bysecond: null,
672    byeaster: null
673  }
674
675  RRule.parseText = function (text, language) {
676    return getnlp().parseText(text, language)
677  }
678
679  RRule.fromText = function (text, language) {
680    return getnlp().fromText(text, language)
681  }
682
683  RRule.optionsToString = function (options) {
684    var key, value, strValues
685    var pairs = []
686    var keys = Object.keys(options)
687    var defaultKeys = Object.keys(RRule.DEFAULT_OPTIONS)
688
689    for (var i = 0; i < keys.length; i++) {
690      if (!contains(defaultKeys, keys[i])) continue
691
692      key = keys[i].toUpperCase()
693      value = options[keys[i]]
694      strValues = []
695
696      if (value === null || value instanceof Array && !value.length) continue
697
698      switch (key) {
699        case 'FREQ':
700          value = RRule.FREQUENCIES[options.freq]
701          break
702        case 'WKST':
703          value = value.toString()
704          break
705        case 'BYWEEKDAY':
706          /*
707          NOTE: BYWEEKDAY is a special case.
708          RRule() deconstructs the rule.options.byweekday array
709          into an array of Weekday arguments.
710          On the other hand, rule.origOptions is an array of Weekdays.
711          We need to handle both cases here.
712          It might be worth change RRule to keep the Weekdays.
713
714          Also, BYWEEKDAY (used by RRule) vs. BYDAY (RFC)
715
716          */
717          key = 'BYDAY'
718          if (!(value instanceof Array)) value = [value]
719
720          for (var wday, j = 0; j < value.length; j++) {
721            wday = value[j]
722            if (wday instanceof Weekday) {
723              // good
724            } else if (wday instanceof Array) {
725              wday = new Weekday(wday[0], wday[1])
726            } else {
727              wday = new Weekday(wday)
728            }
729            strValues[j] = wday.toString()
730          }
731          value = strValues
732          break
733        case 'DTSTART':
734        case 'UNTIL':
735          value = dateutil.timeToUntilString(value)
736          break
737        default:
738          if (value instanceof Array) {
739            for (j = 0; j < value.length; j++) strValues[j] = String(value[j])
740            value = strValues
741          } else {
742            value = String(value)
743          }
744
745      }
746      pairs.push([key, value])
747    }
748
749    var strings = []
750    for (i = 0; i < pairs.length; i++) {
751      var attr = pairs[i]
752      strings.push(attr[0] + '=' + attr[1].toString())
753    }
754    return strings.join(';')
755  }
756
757  RRule.prototype = {
758    constructor: RRule,
759
760    /**
761     * @param {Function} iterator - optional function that will be called
762     *                   on each date that is added. It can return false
763     *                   to stop the iteration.
764     * @return Array containing all recurrences.
765     */
766    all: function (iterator) {
767      if (iterator) {
768        return this._iter(new CallbackIterResult('all', {}, iterator))
769      } else {
770        var result = this._cacheGet('all')
771        if (result === false) {
772          result = this._iter(new IterResult('all', {}))
773          this._cacheAdd('all', result)
774        }
775        return result
776      }
777    },
778
779    /**
780     * Returns all the occurrences of the rrule between after and before.
781     * The inc keyword defines what happens if after and/or before are
782     * themselves occurrences. With inc == True, they will be included in the
783     * list, if they are found in the recurrence set.
784     * @return Array
785     */
786    between: function (after, before, inc, iterator) {
787      var args = {
788        before: before,
789        after: after,
790        inc: inc
791      }
792
793      if (iterator) {
794        return this._iter(new CallbackIterResult('between', args, iterator))
795      }
796      var result = this._cacheGet('between', args)
797      if (result === false) {
798        result = this._iter(new IterResult('between', args))
799        this._cacheAdd('between', result, args)
800      }
801      return result
802    },
803
804    /**
805     * Returns the last recurrence before the given datetime instance.
806     * The inc keyword defines what happens if dt is an occurrence.
807     * With inc == True, if dt itself is an occurrence, it will be returned.
808     * @return Date or null
809     */
810    before: function (dt, inc) {
811      var args = {dt: dt, inc: inc}
812      var result = this._cacheGet('before', args)
813      if (result === false) {
814        result = this._iter(new IterResult('before', args))
815        this._cacheAdd('before', result, args)
816      }
817      return result
818    },
819
820    /**
821     * Returns the first recurrence after the given datetime instance.
822     * The inc keyword defines what happens if dt is an occurrence.
823     * With inc == True, if dt itself is an occurrence, it will be returned.
824     * @return Date or null
825     */
826    after: function (dt, inc) {
827      var args = {dt: dt, inc: inc}
828      var result = this._cacheGet('after', args)
829      if (result === false) {
830        result = this._iter(new IterResult('after', args))
831        this._cacheAdd('after', result, args)
832      }
833      return result
834    },
835
836    /**
837     * Returns the number of recurrences in this set. It will have go trough
838     * the whole recurrence, if this hasn't been done before.
839     */
840    count: function () {
841      return this.all().length
842    },
843
844    /**
845     * Converts the rrule into its string representation
846     * @see <http://www.ietf.org/rfc/rfc2445.txt>
847     * @return String
848     */
849    toString: function () {
850      return RRule.optionsToString(this.origOptions)
851    },
852
853    /**
854    * Will convert all rules described in nlp:ToText
855    * to text.
856    */
857    toText: function (gettext, language) {
858      return getnlp().toText(this, gettext, language)
859    },
860
861    isFullyConvertibleToText: function () {
862      return getnlp().isFullyConvertible(this)
863    },
864
865    /**
866     * @param {String} what - all/before/after/between
867     * @param {Array,Date} value - an array of dates, one date, or null
868     * @param {Object?} args - _iter arguments
869     */
870    _cacheAdd: function (what, value, args) {
871      if (!this._cache) return
872
873      if (value) {
874        value = (value instanceof Date)
875          ? dateutil.clone(value) : dateutil.cloneDates(value)
876      }
877
878      if (what === 'all') {
879        this._cache.all = value
880      } else {
881        args._value = value
882        this._cache[what].push(args)
883      }
884    },
885
886    /**
887     * @return false - not in the cache
888     *         null  - cached, but zero occurrences (before/after)
889     *         Date  - cached (before/after)
890     *         []    - cached, but zero occurrences (all/between)
891     *         [Date1, DateN] - cached (all/between)
892     */
893    _cacheGet: function (what, args) {
894      if (!this._cache) return false
895
896      var cached = false
897      var argsKeys = args ? Object.keys(args) : []
898      var findCacheDiff = function (item) {
899        for (var key, i = 0; i < argsKeys.length; i++) {
900          key = argsKeys[i]
901          if (String(args[key]) !== String(item[key])) return true
902        }
903        return false
904      }
905
906      if (what === 'all') {
907        cached = this._cache.all
908      } else {
909        // Let's see whether we've already called the
910        // 'what' method with the same 'args'
911        for (var item, i = 0; i < this._cache[what].length; i++) {
912          item = this._cache[what][i]
913          if (argsKeys.length && findCacheDiff(item)) continue
914          cached = item._value
915          break
916        }
917      }
918
919      if (!cached && this._cache.all) {
920        // Not in the cache, but we already know all the occurrences,
921        // so we can find the correct dates from the cached ones.
922        var iterResult = new IterResult(what, args)
923        for (i = 0; i < this._cache.all.length; i++) {
924          if (!iterResult.accept(this._cache.all[i])) break
925        }
926        cached = iterResult.getValue()
927        this._cacheAdd(what, cached, args)
928      }
929
930      return cached instanceof Array
931        ? dateutil.cloneDates(cached)
932        : (cached instanceof Date ? dateutil.clone(cached) : cached)
933    },
934
935    /**
936     * @return a RRule instance with the same freq and options
937     *          as this one (cache is not cloned)
938     */
939    clone: function () {
940      return new RRule(this.origOptions)
941    },
942
943    _iter: function (iterResult) {
944      /* Since JavaScript doesn't have the python's yield operator (<1.7),
945         we use the IterResult object that tells us when to stop iterating.
946
947      */
948
949      var dtstart = this.options.dtstart
950      var dtstartMillisecondModulo = this.options.dtstart % 1000
951
952      var year = dtstart.getFullYear()
953      var month = dtstart.getMonth() + 1
954      var day = dtstart.getDate()
955      var hour = dtstart.getHours()
956      var minute = dtstart.getMinutes()
957      var second = dtstart.getSeconds()
958      var weekday = dateutil.getWeekday(dtstart)
959
960      // Some local variables to speed things up a bit
961      var freq = this.options.freq
962      var interval = this.options.interval
963      var wkst = this.options.wkst
964      var until = this.options.until
965      var bymonth = this.options.bymonth
966      var byweekno = this.options.byweekno
967      var byyearday = this.options.byyearday
968      var byweekday = this.options.byweekday
969      var byeaster = this.options.byeaster
970      var bymonthday = this.options.bymonthday
971      var bynmonthday = this.options.bynmonthday
972      var bysetpos = this.options.bysetpos
973      var byhour = this.options.byhour
974      var byminute = this.options.byminute
975      var bysecond = this.options.bysecond
976
977      var ii = new Iterinfo(this)
978      ii.rebuild(year, month)
979
980      var getdayset = {}
981      getdayset[RRule.YEARLY] = ii.ydayset
982      getdayset[RRule.MONTHLY] = ii.mdayset
983      getdayset[RRule.WEEKLY] = ii.wdayset
984      getdayset[RRule.DAILY] = ii.ddayset
985      getdayset[RRule.HOURLY] = ii.ddayset
986      getdayset[RRule.MINUTELY] = ii.ddayset
987      getdayset[RRule.SECONDLY] = ii.ddayset
988
989      getdayset = getdayset[freq]
990
991      var timeset
992      if (freq < RRule.HOURLY) {
993        timeset = this.timeset
994      } else {
995        var gettimeset = {}
996        gettimeset[RRule.HOURLY] = ii.htimeset
997        gettimeset[RRule.MINUTELY] = ii.mtimeset
998        gettimeset[RRule.SECONDLY] = ii.stimeset
999        gettimeset = gettimeset[freq]
1000        if ((freq >= RRule.HOURLY && plb(byhour) && !contains(byhour, hour)) ||
1001          (freq >= RRule.MINUTELY && plb(byminute) && !contains(byminute, minute)) ||
1002          (freq >= RRule.SECONDLY && plb(bysecond) && !contains(bysecond, second))) {
1003          timeset = []
1004        } else {
1005          timeset = gettimeset.call(ii, hour, minute, second, dtstartMillisecondModulo)
1006        }
1007      }
1008
1009      var total = 0
1010      var count = this.options.count
1011      var i, j, k, dm, div, mod, tmp, pos, dayset, start, end, fixday, filtered
1012
1013      while (true) {
1014        // Get dayset with the right frequency
1015        tmp = getdayset.call(ii, year, month, day)
1016        dayset = tmp[0]
1017        start = tmp[1]
1018        end = tmp[2]
1019
1020        // Do the "hard" work ;-)
1021        filtered = false
1022        for (j = start; j < end; j++) {
1023          i = dayset[j]
1024
1025          filtered = (plb(bymonth) && !contains(bymonth, ii.mmask[i])) ||
1026            (plb(byweekno) && !ii.wnomask[i]) ||
1027            (plb(byweekday) && !contains(byweekday, ii.wdaymask[i])) ||
1028            (plb(ii.nwdaymask) && !ii.nwdaymask[i]) ||
1029            (byeaster !== null && !contains(ii.eastermask, i)) ||
1030            ((plb(bymonthday) || plb(bynmonthday)) &&
1031              !contains(bymonthday, ii.mdaymask[i]) &&
1032              !contains(bynmonthday, ii.nmdaymask[i])) ||
1033            (plb(byyearday) &&
1034              ((i < ii.yearlen &&
1035                !contains(byyearday, i + 1) &&
1036                !contains(byyearday, -ii.yearlen + i)) ||
1037              (i >= ii.yearlen &&
1038                !contains(byyearday, i + 1 - ii.yearlen) &&
1039                !contains(byyearday, -ii.nextyearlen + i - ii.yearlen)
1039)))
1040
1041          if (filtered) dayset[i] = null
1042        }
1043
1044        // Output results
1045        if (plb(bysetpos) && plb(timeset)) {
1046          var daypos, timepos
1047          var poslist = []
1048
1049          for (i, j = 0; j < bysetpos.length; j++) {
1050            pos = bysetpos[j]
1051
1052            if (pos < 0) {
1053              daypos = Math.floor(pos / timeset.length)
1054              timepos = pymod(pos, timeset.length)
1055            } else {
1056              daypos = Math.floor((pos - 1) / timeset.length)
1057              timepos = pymod((pos - 1), timeset.length)
1058            }
1059
1060            try {
1061              tmp = []
1062              for (k = start; k < end; k++) {
1063                var val = dayset[k]
1064                if (val === null) continue
1065                tmp.push(val)
1066              }
1067              if (daypos < 0) {
1068                // we're trying to emulate python's aList[-n]
1069                i = tmp.slice(daypos)[0]
1070              } else {
1071                i = tmp[daypos]
1072              }
1073
1074              var time = timeset[timepos]
1075              var date = dateutil.fromOrdinal(ii.yearordinal + i)
1076              var res = dateutil.combine(date, time)
1077              // XXX: can this ever be in the array?
1078              // - compare the actual date instead?
1079              if (!contains(poslist, res)) poslist.push(res)
1080            } catch (e) {}
1081          }
1082
1083          dateutil.sort(poslist)
1084          for (j = 0; j < poslist.length; j++) {
1085            res = poslist[j]
1086            if (until && res > until) {
1087              this._len = total
1088              return iterResult.getValue()
1089            } else if (res >= dtstart) {
1090              ++total
1091              if (!iterResult.accept(res)) return iterResult.getValue()
1092              if (count) {
1093                --count
1094                if (!count) {
1095                  this._len = total
1096                  return iterResult.getValue()
1097                }
1098              }
1099            }
1100          }
1101        } else {
1102          for (j = start; j < end; j++) {
1103            i = dayset[j]
1104            if (i !== null) {
1105              date = dateutil.fromOrdinal(ii.yearordinal + i)
1106              for (k = 0; k < timeset.length; k++) {
1107                time = timeset[k]
1108                res = dateutil.combine(date, time)
1109                if (until && res > until) {
1110                  this._len = total
1111                  return iterResult.getValue()
1112                } else if (res >= dtstart) {
1113                  ++total
1114                  if (!iterResult.accept(res)) return iterResult.getValue()
1115                  if (count) {
1116                    --count
1117                    if (!count) {
1118                      this._len = total
1119                      return iterResult.getValue()
1120                    }
1121                  }
1122                }
1123              }
1124            }
1125          }
1126        }
1127
1128        // Handle frequency and interval
1129        fixday = false
1130        if (freq === RRule.YEARLY) {
1131          year += interval
1132          if (year > dateutil.MAXYEAR) {
1133            this._len = total
1134            return iterResult.getValue()
1135          }
1136          ii.rebuild(year, month)
1137        } else if (freq === RRule.MONTHLY) {
1138          month += interval
1139          if (month > 12) {
1140            div = Math.floor(month / 12)
1141            mod = pymod(month, 12)
1142            month = mod
1143            year += div
1144            if (month === 0) {
1145              month = 12
1146              --year
1147            }
1148            if (year > dateutil.MAXYEAR) {
1149              this._len = total
1150              return iterResult.getValue()
1151            }
1152          }
1153          ii.rebuild(year, month)
1154        } else if (freq === RRule.WEEKLY) {
1155          if (wkst > weekday) {
1156            day += -(weekday + 1 + (6 - wkst)) + interval * 7
1157          } else {
1158            day += -(weekday - wkst) + interval * 7
1159          }
1160          weekday = wkst
1161          fixday = true
1162        } else if (freq === RRule.DAILY) {
1163          day += interval
1164          fixday = true
1165        } else if (freq === RRule.HOURLY) {
1166          if (filtered) {
1167            // Jump to one iteration before next day
1168            hour += Math.floor((23 - hour) / interval) * interval
1169          }
1170          while (true) {
1171            hour += interval
1172            dm = divmod(hour, 24)
1173            div = dm.div
1174            mod = dm.mod
1175            if (div) {
1176              hour = mod
1177              day += div
1178              fixday = true
1179            }
1180            if (!plb(byhour) || contains(byhour, hour)) break
1181          }
1182          timeset = gettimeset.call(ii, hour, minute, second)
1183        } else if (freq === RRule.MINUTELY) {
1184          if (filtered) {
1185            // Jump to one iteration before next day
1186            minute += Math.floor(
1187              (1439 - (hour * 60 + minute)) / interval) * interval
1188          }
1189
1190          while (true) {
1191            minute += interval
1192            dm = divmod(minute, 60)
1193            div = dm.div
1194            mod = dm.mod
1195            if (div) {
1196              minute = mod
1197              hour += div
1198              dm = divmod(hour, 24)
1199              div = dm.div
1200              mod = dm.mod
1201              if (div) {
1202                hour = mod
1203                day += div
1204                fixday = true
1205                filtered = false
1206              }
1207            }
1208            if ((!plb(byhour) || contains(byhour, hour)) &&
1209              (!plb(byminute) || contains(byminute, minute))) {
1210              break
1211            }
1212          }
1213          timeset = gettimeset.call(ii, hour, minute, second)
1214        } else if (freq === RRule.SECONDLY) {
1215          if (filtered) {
1216            // Jump to one iteration before next day
1217            second += Math.floor(
1218                (86399 - (hour * 3600 + minute * 60 + second)) / interval) * interval
1219          }
1220          while (true) {
1221            second += interval
1222            dm = divmod(second, 60)
1223            div = dm.div
1224            mod = dm.mod
1225            if (div) {
1226              second = mod
1227              minute += div
1228              dm = divmod(minute, 60)
1229              div = dm.div
1230              mod = dm.mod
1231              if (div) {
1232                minute = mod
1233                hour += div
1234                dm = divmod(hour, 24)
1235                div = dm.div
1236                mod = dm.mod
1237                if (div) {
1238                  hour = mod
1239                  day += div
1240                  fixday = true
1241                }
1242              }
1243            }
1244            if ((!plb(byhour) || contains(byhour, hour)) &&
1245              (!plb(byminute) || contains(byminute, minute)) &&
1246              (!plb(bysecond) || contains(bysecond, second))) {
1247              break
1248            }
1249          }
1250          timeset = gettimeset.call(ii, hour, minute, second)
1251        }
1252
1253        if (fixday && day > 28) {
1254          var daysinmonth = dateutil.monthRange(year, month - 1)[1]
1255          if (day > daysinmonth) {
1256            while (day > daysinmonth) {
1257              day -= daysinmonth
1258              ++month
1259              if (month === 13) {
1260                month = 1
1261                ++year
1262                if (year > dateutil.MAXYEAR) {
1263                  this._len = total
1264                  return iterResult.getValue()
1265                }
1266              }
1267              daysinmonth = dateutil.monthRange(year, month - 1)[1]
1268            }
1269            ii.rebuild(year, month)
1270          }
1271        }
1272      }
1273    }
1274
1275  }
1276
1277  RRule.parseString = function (rfcString) {
1278    rfcString = rfcString.replace(/^\s+|\s+$/, '')
1279    if (!rfcString.length) return null
1280
1281    var i, j, key, value, attr
1282    var attrs = rfcString.split(';')
1283    var options = {}
1284
1285    for (i = 0; i < attrs.length; i++) {
1286      attr = attrs[i].split('=')
1287      key = attr[0]
1288      value = attr[1]
1289      switch (key) {
1290        case 'FREQ':
1291          options.freq = RRule[value]
1292          break
1293        case 'WKST':
1294          options.wkst = RRule[value]
1295          break
1296        case 'COUNT':
1297        case 'INTERVAL':
1298        case 'BYSETPOS':
1299        case 'BYMONTH':
1300        case 'BYMONTHDAY':
1301        case 'BYYEARDAY':
1302        case 'BYWEEKNO':
1303        case 'BYHOUR':
1304        case 'BYMINUTE':
1305        case 'BYSECOND':
1306          if (value.indexOf(',') !== -1) {
1307            value = value.split(',')
1308            for (j = 0; j < value.length; j++) {
1309              if (/^[+-]?\d+$/.test(value[j])) value[j] = Number(value[j])
1310            }
1311          } else if (/^[+-]?\d+$/.test(value)) {
1312            value = Number(value)
1313          }
1314          key = key.toLowerCase()
1315          options[key] = value
1316          break
1317        case 'BYDAY': // => byweekday
1318          var n, wday, day
1319          var days = value.split(',')
1320
1321          options.byweekday = []
1322          for (j = 0; j < days.length; j++) {
1323            day = days[j]
1324            if (day.length === 2) { // MO, TU, ...
1325              wday = RRule[day] // wday instanceof Weekday
1326              options.byweekday.push(wday)
1327            } else { // -1MO, +3FR, 1SO, ...
1328              day = day.match(/^([+-]?\d)([A-Z]{2})$/)
1329              n = Number(day[1])
1330              wday = day[2]
1331              wday = RRule[wday].weekday
1332              options.byweekday.push(new Weekday(wday, n))
1333            }
1334          }
1335          break
1336        case 'DTSTART':
1337          options.dtstart = dateutil.untilStringToDate(value)
1338          break
1339        case 'UNTIL':
1340          options.until = dateutil.untilStringToDate(value)
1341          break
1342        case 'BYEASTER':
1343          options.byeaster = Number(value)
1344          break
1345        default:
1346          throw new Error("Unknown RRULE property '" + key + "'")
1347      }
1348    }
1349    return options
1350  }
1351
1352  RRule.fromString = function (string) {
1353    return new RRule(RRule.parseString(string))
1354  }
1355
1356  // =============================================================================
1357  // Iterinfo
1358  // =============================================================================
1359
1360  var Iterinfo = function (rrule) {
1361    this.rrule = rrule
1362    this.lastyear = null
1363    this.lastmonth = null
1364    this.yearlen = null
1365    this.nextyearlen = null
1366    this.yearordinal = null
1367    this.yearweekday = null
1368    this.mmask = null
1369    this.mrange = null
1370    this.mdaymask = null
1371    this.nmdaymask = null
1372    this.wdaymask = null
1373    this.wnomask = null
1374    this.nwdaymask = null
1375    this.eastermask = null
1376  }
1377
1378  Iterinfo.prototype.easter = function (y, offset) {
1379    offset = offset || 0
1380
1381    var a = y % 19
1382    var b = Math.floor(y / 100)
1383    var c = y % 100
1384    var d = Math.floor(b / 4)
1385    var e = b % 4
1386    var f = Math.floor((b + 8) / 25)
1387    var g = Math.floor((b - f + 1) / 3)
1388    var h = Math.floor(19 * a + b - d - g + 15) % 30
1389    var i = Math.floor(c / 4)
1390    var k = c % 4
1391    var l = Math.floor(32 + 2 * e + 2 * i - h - k) % 7
1392    var m = Math.floor((a + 11 * h + 22 * l) / 451)
1393    var month = Math.floor((h + l - 7 * m + 114) / 31)
1394    var day = (h + l - 7 * m + 114) % 31 + 1
1395    var date = Date.UTC(y, month - 1, day + offset)
1396    var yearStart = Date.UTC(y, 0, 1)
1397
1398    return [Math.ceil((date - yearStart) / (1000 * 60 * 60 * 24))]
1399  }
1400
1401  Iterinfo.prototype.rebuild = function (year, month) {
1402    var rr = this.rrule
1403
1404    if (year !== this.lastyear) {
1405      this.yearlen = dateutil.isLeapYear(year) ? 366 : 365
1406      this.nextyearlen = dateutil.isLeapYear(year + 1) ? 366 : 365
1407      var firstyday = new Date(year, 0, 1)
1408
1409      this.yearordinal = dateutil.toOrdinal(firstyday)
1410      this.yearweekday = dateutil.getWeekday(firstyday)
1411
1412      var wday = dateutil.getWeekday(new Date(year, 0, 1))
1413
1414      if (this.yearlen === 365) {
1415        this.mmask = [].concat(M365MASK)
1416        this.mdaymask = [].concat(MDAY365MASK)
1417        this.nmdaymask = [].concat(NMDAY365MASK)
1418        this.wdaymask = WDAYMASK.slice(wday)
1419        this.mrange = [].concat(M365RANGE)
1420      } else {
1421        this.mmask = [].concat(M366MASK)
1422        this.mdaymask = [].concat(MDAY366MASK)
1423        this.nmdaymask = [].concat(NMDAY366MASK)
1424        this.wdaymask = WDAYMASK.slice(wday)
1425        this.mrange = [].concat(M366RANGE)
1426      }
1427
1428      if (!plb(rr.options.byweekno)) {
1429        this.wnomask = null
1430      } else {
1431        this.wnomask = repeat(0, this.yearlen + 7)
1432        var no1wkst, firstwkst, wyearlen
1433        no1wkst = firstwkst = pymod(7 - this.yearweekday + rr.options.wkst, 7)
1434        if (no1wkst >= 4) {
1435          no1wkst = 0
1436          // Number of days in the year, plus the days we got
1437          // from last year.
1438          wyearlen = this.yearlen + pymod(this.yearweekday - rr.options.wkst, 7)
1439        } else {
1440          // Number of days in the year, minus the days we
1441          // left in last year.
1442          wyearlen = this.yearlen - no1wkst
1443        }
1444        var div = Math.floor(wyearlen / 7)
1445        var mod = pymod(wyearlen, 7)
1446        var numweeks = Math.floor(div + (mod / 4))
1447        for (var n, i, j = 0; j < rr.options.byweekno.length; j++) {
1448          n = rr.options.byweekno[j]
1449          if (n < 0) {
1450            n += numweeks + 1
1451          } if (!(n > 0 && n <= numweeks)) {
1452            continue
1453          } if (n > 1) {
1454            i = no1wkst + (n - 1) * 7
1455            if (no1wkst !== firstwkst) {
1456              i -= 7 - firstwkst
1457            }
1458          } else {
1459            i = no1wkst
1460          }
1461          for (var k = 0; k < 7; k++) {
1462            this.wnomask[i] = 1
1463            i++
1464            if (this.wdaymask[i] === rr.options.wkst) break
1465          }
1466        }
1467
1468        if (contains(rr.options.byweekno, 1)) {
1469          // Check week number 1 of next year as well
1470          // orig-TODO : Check -numweeks for next year.
1471          i = no1wkst + numweeks * 7
1472          if (no1wkst !== firstwkst) i -= 7 - firstwkst
1473          if (i < this.yearlen) {
1474            // If week starts in next year, we
1475            // don't care about it.
1476            for (j = 0; j < 7; j++) {
1477              this.wnomask[i] = 1
1478              i += 1
1479              if (this.wdaymask[i] === rr.options.wkst) break
1480            }
1481          }
1482        }
1483
1484        if (no1wkst) {
1485          // Check last week number of last year as
1486          // well. If no1wkst is 0, either the year
1487          // started on week start, or week number 1
1488          // got days from last year, so there are no
1489          // days from last year's last week number in
1490          // this year.
1491          var lnumweeks
1492          if (!contains(rr.options.byweekno, -1)) {
1493            var lyearweekday = dateutil.getWeekday(new Date(year - 1, 0, 1))
1494            var lno1wkst = pymod(7 - lyearweekday + rr.options.wkst, 7)
1495            var lyearlen = dateutil.isLeapYear(year - 1) ? 366 : 365
1496            if (lno1wkst >= 4) {
1497              lno1wkst = 0
1498              lnumweeks = Math.floor(52 +
1499                pymod(lyearlen + pymod(lyearweekday - rr.options.wkst, 7), 7) / 4)
1500            } else {
1501              lnumweeks = Math.floor(52 + pymod(this.yearlen - no1wkst, 7) / 4)
1502            }
1503          } else {
1504            lnumweeks = -1
1505          }
1506          if (contains(rr.options.byweekno, lnumweeks)) {
1507            for (i = 0; i < no1wkst; i++) this.wnomask[i] = 1
1508          }
1509        }
1510      }
1511    }
1512
1513    if (plb(rr.options.bynweekday) && (month !== this.lastmonth || year !== this.lastyear)) {
1514      var ranges = []
1515      if (rr.options.freq === RRule.YEARLY) {
1516        if (plb(rr.options.bymonth)) {
1517          for (j = 0; j < rr.options.bymonth.length; j++) {
1518            month = rr.options.bymonth[j]
1519            ranges.push(this.mrange.slice(month - 1, month + 1))
1520          }
1521        } else {
1522          ranges = [[0, this.yearlen]]
1523        }
1524      } else if (rr.options.freq === RRule.MONTHLY) {
1525        ranges = [this.mrange.slice(month - 1, month + 1)]
1526      }
1527      if (plb(ranges)) {
1528        // Weekly frequency won't get here, so we may not
1529        // care about cross-year weekly periods.
1530        this.nwdaymask = repeat(0, this.yearlen)
1531
1532        for (j = 0; j < ranges.length; j++) {
1533          var rang = ranges[j]
1534          var first = rang[0]
1535          var last = rang[1]
1536          last -= 1
1537          for (k = 0; k < rr.options.bynweekday.length; k++) {
1538            wday = rr.options.bynweekday[k][0]
1539            n = rr.options.bynweekday[k][1]
1540            if (n < 0) {
1541              i = last + (n + 1) * 7
1542              i -= pymod(this.wdaymask[i] - wday, 7)
1543            } else {
1544              i = first + (n - 1) * 7
1545              i += pymod(7 - this.wdaymask[i] + wday, 7)
1546            }
1547            if (first <= i && i <= last) this.nwdaymask[i] = 1
1548          }
1549        }
1550      }
1551
1552      this.lastyear = year
1553      this.lastmonth = month
1554    }
1555
1556    if (rr.options.byeaster !== null) {
1557      this.eastermask = this.easter(year, rr.options.byeaster)
1558    }
1559  }
1560
1561  Iterinfo.prototype.ydayset = function (year, month, day) {
1562    return [range(this.yearlen), 0, this.yearlen]
1563  }
1564
1565  Iterinfo.prototype.mdayset = function (year, month, day) {
1566    var set = repeat(null, this.yearlen)
1567    var start = this.mrange[month - 1]
1568    var end = this.mrange[month]
1569    for (var i = start; i < end; i++) set[i] = i
1570    return [set, start, end]
1571  }
1572
1573  Iterinfo.prototype.wdayset = function (year, month, day) {
1574    // We need to handle cross-year weeks here.
1575    var set = repeat(null, this.yearlen + 7)
1576    var i = dateutil.toOrdinal(new Date(year, month - 1, day)) - this.yearordinal
1577    var start = i
1578    for (var j = 0; j < 7; j++) {
1579      set[i] = i
1580      ++i
1581      if (this.wdaymask[i] === this.rrule.options.wkst) break
1582    }
1583    return [set, start, i]
1584  }
1585
1586  Iterinfo.prototype.ddayset = function (year, month, day) {
1587    var set = repeat(null, this.yearlen)
1588    var i = dateutil.toOrdinal(new Date(year, month - 1, day)) - this.yearordinal
1589    set[i] = i
1590    return [set, i, i + 1]
1591  }
1592
1593  Iterinfo.prototype.htimeset = function (hour, minute, second, millisecond) {
1594    var set = []
1595    var rr = this.rrule
1596    for (var i = 0; i < rr.options.byminute.length; i++) {
1597      minute = rr.options.byminute[i]
1598      for (var j = 0; j < rr.options.bysecond.length; j++) {
1599        second = rr.options.bysecond[j]
1600        set.push(new dateutil.Time(hour, minute, second, millisecond))
1601      }
1602    }
1603    dateutil.sort(set)
1604    return set
1605  }
1606
1607  Iterinfo.prototype.mtimeset = function (hour, minute, second, millisecond) {
1608    var set = []
1609    var rr = this.rrule
1610    for (var j = 0; j < rr.options.bysecond.length; j++) {
1611      second = rr.options.bysecond[j]
1612      set.push(new dateutil.Time(hour, minute, second, millisecond))
1613    }
1614    dateutil.sort(set)
1615    return set
1616  }
1617
1618  Iterinfo.prototype.stimeset = function (hour, minute, second, millisecond) {
1619    return [new dateutil.Time(hour, minute, second, millisecond)]
1620  }
1621
1622  // =============================================================================
1623  // Results
1624  // =============================================================================
1625
1626  /**
1627   * This class helps us to emulate python's generators, sorta.
1628   */
1629  var IterResult = function (method, args) {
1630    this.init(method, args)
1631  }
1632
1633  IterResult.prototype = {
1634    constructor: IterResult,
1635    init: function (method, args) {
1636      this.method = method
1637      this.args = args
1638      this.minDate = null
1639      this.maxDate = null
1640      this._result = []
1641
1642      if (method === 'between') {
1643        this.maxDate = args.inc
1644          ? args.before : new Date(args.before.getTime() - 1)
1645        this.minDate = args.inc
1646          ? args.after : new Date(args.after.getTime() + 1)
1647      } else if (method === 'before') {
1648        this.maxDate = args.inc ? args.dt : new Date(args.dt.getTime() - 1)
1649      } else if (method === 'after') {
1650        this.minDate = args.inc ? args.dt : new Date(args.dt.getTime() + 1)
1651      }
1652    },
1653
1654    /**
1655     * Possibly adds a date into the result.
1656     *
1657     * @param {Date} date - the date isn't necessarly added to the result
1658     *                      list (if it is too late/too early)
1659     * @return {Boolean} true if it makes sense to continue the iteration
1660     *                   false if we're done.
1661     */
1662    accept: function (date) {
1663      var tooEarly = this.minDate && date < this.minDate
1664      var tooLate = this.maxDate && date > this.maxDate
1665
1666      if (this.method === 'between') {
1667        if (tooEarly) return true
1668        if (tooLate) return false
1669      } else if (this.method === 'before') {
1670        if (tooLate) return false
1671      } else if (this.method === 'after') {
1672        if (tooEarly) return true
1673        this.add(date)
1674        return false
1675      }
1676
1677      return this.add(date)
1678    },
1679
1680    /**
1681     *
1682     * @param {Date} date that is part of the result.
1683     * @return {Boolean} whether we are interested in more values.
1684     */
1685    add: function (date) {
1686      this._result.push(date)
1687      return true
1688    },
1689
1690    /**
1691     * 'before' and 'after' return only one date, whereas 'all'
1692     * and 'between' an array.
1693     * @return {Date,Array?}
1694     */
1695    getValue: function () {
1696      var res = this._result
1697      switch (this.method) {
1698        case 'all':
1699        case 'between':
1700          return res
1701        case 'before':
1702        case 'after':
1703          return res.length ? res[res.length - 1] : null
1704      }
1705    },
1706
1707    clone: function () {
1708      return new IterResult(this.method, this.args)
1709    }
1710  }
1711
1712  /**
1713   * IterResult subclass that calls a callback function on each add,
1714   * and stops iterating when the callback returns false.
1715   */
1716  var CallbackIterResult = function (method, args, iterator) {
1717    var allowedMethods = ['all', 'between']
1718    if (!contains(allowedMethods, method)) {
1719      throw new Error('Invalid method "' + method +
1720        '". Only all and between works with iterator.')
1721    }
1722    this.add = function (date) {
1723      if (iterator(date, this._result.length)) {
1724        this._result.push(date)
1725        return true
1726      }
1727      return false
1728    }
1729
1730    this.init(method, args)
1731  }
1732  CallbackIterResult.prototype = IterResult.prototype
1733
1734  /**
1735   *
1736   * @param {Boolean?} noCache
1737   *  The same stratagy as RRule on cache, default to false
1738   * @constructor
1739   */
1740
1741  var RRuleSet = function (noCache) {
1742    // Let RRuleSet cacheable
1743    this._cache = noCache ? null : {
1744      all: false,
1745      before: [],
1746      after: [],
1747      between: []
1748    }
1749    this._rrule = []
1750    this._rdate = []
1751    this._exrule = []
1752    this._exdate = []
1753  }
1754
1755  RRuleSet.prototype = {
1756    constructor: RRuleSet,
1757
1758    /**
1759    * @param {RRule}
1760    */
1761    rrule: function (rrule) {
1762      if (!(rrule instanceof RRule)) {
1763        throw new TypeError(String(rrule) + ' is not RRule instance')
1764      }
1765      if (!contains(this._rrule.map(String), String(rrule))) {
1766        this._rrule.push(rrule)
1767      }
1768    },
1769
1770    /**
1771    * @param {Date}
1772    */
1773    rdate: function (date) {
1774      if (!(date instanceof Date)) {
1775        throw new TypeError(String(date) + ' is not Date instance')
1776      }
1777      if (!contains(this._rdate.map(Number), Number(date))) {
1778        this._rdate.push(date)
1779        dateutil.sort(this._rdate)
1780      }
1781    },
1782
1783    /**
1784    * @param {RRule}
1785    */
1786    exrule: function (rrule) {
1787      if (!(rrule instanceof RRule)) {
1788        throw new TypeError(String(rrule) + ' is not RRule instance')
1789      }
1790      if (!contains(this._exrule.map(String), String(rrule))) {
1791        this._exrule.push(rrule)
1792      }
1793    },
1794
1795    /**
1796    * @param {Date}
1797    */
1798    exdate: function (date) {
1799      if (!(date instanceof Date)) {
1800        throw new TypeError(String(date) + ' is not Date instance')
1801      }
1802      if (!contains(this._exdate.map(Number), Number(date))) {
1803        this._exdate.push(date)
1804        dateutil.sort(this._exdate)
1805      }
1806    },
1807
1808    valueOf: function () {
1809      var result = []
1810      if (this._rrule.length) {
1811        this._rrule.forEach(function (rrule) {
1812          result.push('RRULE:' + rrule)
1813        })
1814      }
1815      if (this._rdate.length) {
1816        result.push('RDATE:' + this._rdate.map(function (rdate) {
1817          return dateutil.timeToUntilString(rdate)
1818        }).join(','))
1819      }
1820      if (this._exrule.length) {
1821        this._exrule.forEach(function (exrule) {
1822          result.push('EXRULE:' + exrule)
1823        })
1824      }
1825      if (this._exdate.length) {
1826        result.push('EXDATE:' + this._exdate.map(function (exdate) {
1827          return dateutil.timeToUntilString(exdate)
1828        }).join(','))
1829      }
1830      return result
1831    },
1832
1833    /**
1834    * to generate recurrence field sush as:
1835    *   ["RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU;DTSTART=19970902T010000Z","RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH;DTSTART=19970902T010000Z"]
1836    */
1837    toString: function () {
1838      return JSON.stringify(this.valueOf())
1839    },
1840
1841    _iter: function (iterResult) {
1842      var _exdateHash = {}
1843      var _exrule = this._exrule
1844      var _accept = iterResult.accept
1845
1846      function evalExdate (after, before) {
1847        _exrule.forEach(function (rrule) {
1848          rrule.between(after, before, true).forEach(function (date) {
1849            _exdateHash[Number(date)] = true
1850          })
1851        })
1852      }
1853
1854      this._exdate.forEach(function (date) {
1855        _exdateHash[Number(date)] = true
1856      })
1857
1858      iterResult.accept = function (date) {
1859        var dt = Number(date)
1860        if (!_exdateHash[dt]) {
1861          evalExdate(new Date(dt - 1), new Date(dt + 1))
1862          if (!_exdateHash[dt]) {
1863            _exdateHash[dt] = true
1864            return _accept.call(this, date)
1865          }
1866        }
1867        return true
1868      }
1869
1870      if (iterResult.method === 'between') {
1871        evalExdate(iterResult.args.after, iterResult.args.before)
1872        iterResult.accept = function (date) {
1873          var dt = Number(date)
1874          if (!_exdateHash[dt]) {
1875            _exdateHash[dt] = true
1876            return _accept.call(this, date)
1877          }
1878          return true
1879        }
1880      }
1881
1882      for (var i = 0; i < this._rdate.length; i++) {
1883        if (!iterResult.accept(new Date(this._rdate[i]))) break
1884      }
1885
1886      this._rrule.forEach(function (rrule) {
1887        rrule._iter(iterResult)
1888      })
1889
1890      var res = iterResult._result
1891      dateutil.sort(res)
1892      switch (iterResult.method) {
1893        case 'all':
1894        case 'between':
1895          return res
1896        case 'before':
1897          return (res.length && res[res.length - 1]) || null
1898        case 'after':
1899          return (res.length && res[0]) || null
1900        default:
1901          return null
1902      }
1903    },
1904
1905    /**
1906    * Create a new RRuleSet Object completely base on current instance
1907    */
1908    clone: function () {
1909      var rrs = new RRuleSet(!!this._cache)
1910      var i
1911      for (i = 0; i < this._rrule.length; i++) {
1912        rrs.rrule(this._rrule[i].clone())
1913      }
1914      for (i = 0; i < this._rdate.length; i++) {
1915        rrs.rdate(new Date(this._rdate[i]))
1916      }
1917      for (i = 0; i < this._exrule.length; i++) {
1918        rrs.exrule(this._exrule[i].clone())
1919      }
1920      for (i = 0; i < this._exdate.length; i++) {
1921        rrs.exdate(new Date(this._exdate[i]))
1922      }
1923      return rrs
1924    }
1925  }
1926
1927  /**
1928   * Inherts method from RRule
1929   *  add Read interface and set RRuleSet cacheable
1930   */
1931  var RRuleSetMethods = ['all', 'between', 'before', 'after', 'count', '_cacheAdd', '_cacheGet']
1932  RRuleSetMethods.forEach(function (method) {
1933    RRuleSet.prototype[method] = RRule.prototype[method]
1934  })
1935
1936  /**
1937   * RRuleStr
1938   *  To parse a set of rrule strings
1939   */
1940
1941  var RRuleStr = function () {}
1942
1943  RRuleStr.DEFAULT_OPTIONS = {
1944    dtstart: null,
1945    cache: false,
1946    unfold: false,
1947    forceset: false,
1948    compatible: false,
1949    ignoretz: false,
1950    tzinfos: null
1951  }
1952
1953  RRuleStr._freq_map = {
1954    'YEARLY': RRule.YEARLY,
1955    'MONTHLY': RRule.MONTHLY,
1956    'WEEKLY': RRule.WEEKLY,
1957    'DAILY': RRule.DAILY,
1958    'HOURLY': RRule.HOURLY,
1959    'MINUTELY': RRule.MINUTELY,
1960    'SECONDLY': RRule.SECONDLY
1961  }
1962
1963  RRuleStr._weekday_map = {
1964    'MO': 0,
1965    'TU': 1,
1966    'WE': 2,
1967    'TH': 3,
1968    'FR': 4,
1969    'SA': 5,
1970    'SU': 6
1971  }
1972
1973  RRuleStr.prototype = {
1974    constructor: RRuleStr,
1975
1976    _handle_int: function (rrkwargs, name, value, options) {
1977      rrkwargs[name.toLowerCase()] = parseInt(value, 10)
1978    },
1979
1980    _handle_int_list: function (rrkwargs, name, value, options) {
1981      rrkwargs[name.toLowerCase()] = value.split(',').map(function (x) {
1982        return parseInt(x, 10)
1983      })
1984    },
1985
1986    _handle_FREQ: function (rrkwargs, name, value, options) {
1987      rrkwargs['freq'] = RRuleStr._freq_map[value]
1988    },
1989
1990    _handle_UNTIL: function (rrkwargs, name, value, options) {
1991      try {
1992        rrkwargs['until'] = dateutil.untilStringToDate(value)
1993      } catch (error) {
1994        throw new Error('invalid until date')
1995      }
1996    },
1997
1998    _handle_WKST: function (rrkwargs, name, value, options) {
1999      rrkwargs['wkst'] = RRuleStr._weekday_map[value]
2000    },
2001
2002    _handle_BYWEEKDAY: function (rrkwargs, name, value, options) {
2003      // Two ways to specify this: +1MO or MO(+1)
2004      var splt, i, j, n, w, wday
2005      var l = []
2006      var wdays = value.split(',')
2007
2008      for (i = 0; i < wdays.length; i++) {
2009        wday = wdays[i]
2010        if (wday.indexOf('(') > -1) {
2011          // If it's of the form TH(+1), etc.
2012          splt = wday.split('(')
2013          w = splt[0]
2014          n = parseInt(splt.slice(1, -1), 10)
2015        } else {
2016          // # If it's of the form +1MO
2017          for (j = 0; j < wday.length; j++) {
2018            if ('+-0123456789'.indexOf(wday[j]) === -1) break
2019          }
2020          n = wday.slice(0, j) || null
2021          w = wday.slice(j)
2022
2023          if (n) n = parseInt(n, 10)
2024        }
2025
2026        var weekday = new Weekday(RRuleStr._weekday_map[w], n)
2027        l.push(weekday)
2028      }
2029      rrkwargs['byweekday'] = l
2030    },
2031
2032    _parseRfcRRule: function (line, options) {
2033      options = options || {}
2034      options.dtstart = options.dtstart || null
2035      options.cache = options.cache || false
2036      options.ignoretz = options.ignoretz || false
2037      options.tzinfos = options.tzinfos || null
2038
2039      var name, value, parts
2040      if (line.indexOf(':') !== -1) {
2041        parts = line.split(':')
2042        name = parts[0]
2043        value = parts[1]
2044
2045        if (name !== 'RRULE') throw new Error('unknown parameter name')
2046      } else {
2047        value = line
2048      }
2049
2050      var i
2051      var rrkwargs = {}
2052      var pairs = value.split(';')
2053
2054      for (i = 0; i < pairs.length; i++) {
2055        parts = pairs[i].split('=')
2056        name = parts[0].toUpperCase()
2057        value = parts[1].toUpperCase()
2058
2059        try {
2060          this['_handle_' + name](rrkwargs, name, value, {
2061            ignoretz: options.ignoretz,
2062            tzinfos: options.tzinfos
2063          })
2064        } catch (error) {
2065          throw new Error("unknown parameter '" + name + "':" + value)
2066        }
2067      }
2068      rrkwargs.dtstart = rrkwargs.dtstart || options.dtstart
2069      return new RRule(rrkwargs, !options.cache)
2070    },
2071
2072    _parseRfc: function (s, options) {
2073      if (options.compatible) {
2074        options.forceset = true
2075        options.unfold = true
2076      }
2077
2078      s = s && s.toUpperCase().trim()
2079      if (!s) throw new Error('Invalid empty string')
2080
2081      var i = 0
2082      var line, lines
2083
2084      // More info about 'unfold' option
2085      // Go head to http://www.ietf.org/rfc/rfc2445.txt
2086      if (options.unfold) {
2087        lines = s.split('\n')
2088        while (i < lines.length) {
2089          // TODO
2090          line = lines[i] = lines[i].replace(/\s+$/g, '')
2091          if (!line) {
2092            lines.splice(i, 1)
2093          } else if (i > 0 && line[0] === ' ') {
2094            lines[i - 1] += line.slice(1)
2095            lines.splice(i, 1)
2096          } else {
2097            i += 1
2098          }
2099        }
2100      } else {
2101        lines = s.split(/\s/)
2102      }
2103
2104      var rrulevals = []
2105      var rdatevals = []
2106      var exrulevals = []
2107      var exdatevals = []
2108      var name, value, parts, parms, parm, dtstart, rset, j, k, datestrs, datestr
2109
2110      if (!options.forceset && lines.length === 1 && (s.indexOf(':') === -1 ||
2111        s.indexOf('RRULE:') === 0)) {
2112        return this._parseRfcRRule(lines[0], {
2113          cache: options.cache,
2114          dtstart: options.dtstart,
2115          ignoretz: options.ignoretz,
2116          tzinfos: options.tzinfos
2117        })
2118      } else {
2119        for (i = 0; i < lines.length; i++) {
2120          line = lines[i]
2121          if (!line) continue
2122          if (line.indexOf(':') === -1) {
2123            name = 'RRULE'
2124            value = line
2125          } else {
2126            parts = split(line, ':', 1)
2127            name = parts[0]
2128            value = parts[1]
2129          }
2130          parms = name.split(';')
2131          if (!parms) throw new Error('empty property name')
2132          name = parms[0]
2133          parms = parms.slice(1)
2134
2135          if (name === 'RRULE') {
2136            for (j = 0; j < parms.length; j++) {
2137              parm = parms[j]
2138              throw new Error('unsupported RRULE parm: ' + parm)
2139            }
2140            rrulevals.push(value)
2141          } else if (name === 'RDATE') {
2142            for (j = 0; j < parms.length; j++) {
2143              parm = parms[j]
2144              if (parm !== 'VALUE=DATE-TIME' && parm !== 'VALUE=DATE') {
2145                throw new Error('unsupported RDATE parm: ' + parm)
2146              }
2147            }
2148            rdatevals.push(value)
2149          } else if (name === 'EXRULE') {
2150            for (j = 0; j < parms.length; j++) {
2151              parm = parms[j]
2152              throw new Error('unsupported EXRULE parm: ' + parm)
2153            }
2154            exrulevals.push(value)
2155          } else if (name === 'EXDATE') {
2156            for (j = 0; j < parms.length; j++) {
2157              parm = parms[j]
2158              if (parm !== 'VALUE=DATE-TIME' && parm !== 'VALUE=DATE') {
2159                throw new Error('unsupported RDATE parm: ' + parm)
2160              }
2161            }
2162            exdatevals.push(value)
2163          } else if (name === 'DTSTART') {
2164            dtstart = dateutil.untilStringToDate(value)
2165          } else {
2166            throw new Error('unsupported property: ' + name)
2167          }
2168        }
2169
2170        if (options.forceset || rrulevals.length > 1 || rdatevals.length ||
2171          exrulevals.length || exdatevals.length) {
2172          rset = new RRuleSet(!options.cache)
2173          for (j = 0; j < rrulevals.length; j++) {
2174            rset.rrule(this._parseRfcRRule(rrulevals[j], {
2175              dtstart: options.dtstart || dtstart,
2176              ignoretz: options.ignoretz,
2177              tzinfos: options.tzinfos
2178            }))
2179          }
2180          for (j = 0; j < rdatevals.length; j++) {
2181            datestrs = rdatevals[j].split(',')
2182            for (k = 0; k < datestrs.length; k++) {
2183              datestr = datestrs[k]
2184              rset.rdate(dateutil.untilStringToDate(datestr))
2185            }
2186          }
2187          for (j = 0; j < exrulevals.length; j++) {
2188            rset.exrule(this._parseRfcRRule(exrulevals[j], {
2189              dtstart: options.dtstart || dtstart,
2190              ignoretz: options.ignoretz,
2191              tzinfos: options.tzinfos
2192            }))
2193          }
2194          for (j = 0; j < exdatevals.length; j++) {
2195            datestrs = exdatevals[j].split(',')
2196            for (k = 0; k < datestrs.length; k++) {
2197              datestr = datestrs[k]
2198              rset.exdate(dateutil.untilStringToDate(datestr))
2199            }
2200          }
2201
2202          if (options.campatiable && options.dtstart) rset.rdate(dtstart)
2203          return rset
2204        } else {
2205          return this._parseRfcRRule(rrulevals[0], {
2206            dtstart: options.dtstart || dtstart,
2207            cache: options.cache,
2208            ignoretz: options.ignoretz,
2209            tzinfos: options.tzinfos
2210          })
2211        }
2212      }
2213    },
2214
2215    parse: function (s, options) {
2216      options = options || {}
2217
2218      var invalid = []
2219      var keys = Object.keys(options)
2220      var defaultKeys = Object.keys(RRuleStr.DEFAULT_OPTIONS)
2221
2222      keys.forEach(function (key) {
2223        if (!contains(defaultKeys, key)) invalid.push(key)
2224      }, this)
2225
2226      if (invalid.length) throw new Error('Invalid options: ' + invalid.join(', '))
2227
2228      // Merge in default options
2229      defaultKeys.forEach(function (key) {
2230        if (!contains(keys, key)) options[key] = RRuleStr.DEFAULT_OPTIONS[key]
2231      })
2232
2233      return this._parseRfc(s, options)
2234    }
2235  }
2236
2237  RRuleStr.prototype._handle_DTSTART = function (rrkwargs, name, value, options) {
2238    rrkwargs[name.toLowerCase()] = dateutil.untilStringToDate(value)
2239  }
2240
2241  RRuleStr.prototype._handle_BYDAY = RRuleStr.prototype._handle_BYWEEKDAY
2242  RRuleStr.prototype._handle_INTERVAL = RRuleStr.prototype._handle_int
2243  RRuleStr.prototype._handle_COUNT = RRuleStr.prototype._handle_int
2244
2245  ;[
2246    '_handle_BYSETPOS', '_handle_BYMONTH', '_handle_BYMONTHDAY',
2247    '_handle_BYYEARDAY', '_handle_BYEASTER', '_handle_BYWEEKNO',
2248    '_handle_BYHOUR', '_handle_BYMINUTE', '_handle_BYSECOND'
2249  ].forEach(function (method) {
2250    RRuleStr.prototype[method] = RRuleStr.prototype._handle_int_list
2251  })
2252
2253  // =============================================================================
2254  // Export
2255  // =============================================================================
2256
2257  // Only one RRuleStr instance for all rrule string parsing work.
2258  var rruleStr = new RRuleStr()
2259  var rrulestr = function () {
2260    return rruleStr.parse.apply(rruleStr, arguments)
2261  }
2262
2263  RRule.RRule = RRule
2264  RRule.RRuleSet = RRuleSet
2265  RRule.rrulestr = rrulestr
2266  return RRule
2267
2268  function getnlp () {
2269    // Lazy, runtime import to avoid circular refs.
2270    if (!getnlp._nlp) {
2271      if (root && root._getRRuleNLP) {
2272        getnlp._nlp = root._getRRuleNLP(RRule)
2273      } else if (typeof require === 'function') {
2274        getnlp._nlp = require('./nlp')(RRule)
2275      } else {
2276        throw new Error('You need to include rrule/nlp.js for fromText/toText to work.')
2277      }
2278    }
2279    return getnlp._nlp
2280  }
2281}))

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.