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.