1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287 |
- /*!
- * rrule.js - Library for working with recurrence rules for calendar dates.
- * https://github.com/jakubroztocil/rrule
- *
- * Copyright 2010, Jakub Roztocil and Lars Schoning
- * Licenced under the BSD licence.
- * https://github.com/jakubroztocil/rrule/blob/master/LICENCE
- *
- * Based on:
- * python-dateutil - Extensions to the standard Python datetime module.
- * Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
- * Copyright (c) 2012 - Tomi Pieviläinen <tomi.pievilainen@iki.fi>
- * https://github.com/jakubroztocil/rrule/blob/master/LICENCE
- *
- */
- /* global module, define */
- ;(function (root, factory) {
- if (typeof module === 'object' && module.exports) {
- module.exports = factory()
- } else if (typeof define === 'function' && define.amd) {
- define([], factory)
- } else {
- root.RRule = factory(root)
- root.RRuleSet = root.RRule.RRuleSet
- root.rrulestr = root.RRule.rrulestr
- }
- }(typeof window === 'object' ? window : this, function (root) {
- // =============================================================================
- // Date utilities
- // =============================================================================
- /**
- * General date-related utilities.
- * Also handles several incompatibilities between JavaScript and Python
- *
- */
- var dateutil = {
- MONTH_DAYS: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
- /**
- * Number of milliseconds of one day
- */
- ONE_DAY: 1000 * 60 * 60 * 24,
- /**
- * @see: <http://docs.python.org/library/datetime.html#datetime.MAXYEAR>
- */
- MAXYEAR: 9999,
- /**
- * Python uses 1-Jan-1 as the base for calculating ordinals but we don't
- * want to confuse the JS engine with milliseconds > Number.MAX_NUMBER,
- * therefore we use 1-Jan-1900 instead
- */
- ORDINAL_BASE: new Date(1900, 0, 1),
- /**
- * Python: MO-SU: 0 - 6
- * JS: SU-SAT 0 - 6
- */
- PY_WEEKDAYS: [6, 0, 1, 2, 3, 4, 5],
- /**
- * py_date.timetuple()[7]
- */
- getYearDay: function (date) {
- var dateNoTime = new Date(
- date.getFullYear(), date.getMonth(), date.getDate())
- return Math.ceil(
- (dateNoTime - new Date(date.getFullYear(), 0, 1)) / dateutil.ONE_DAY) + 1
- },
- isLeapYear: function (year) {
- return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)
- },
- /**
- * @return {Number} the date's timezone offset in ms
- */
- tzOffset: function (date) {
- return date.getTimezoneOffset() * 60 * 1000
- },
- /**
- * @see: <http://www.mcfedries.com/JavaScript/DaysBetween.asp>
- */
- daysBetween: function (date1, date2) {
- // The number of milliseconds in one day
- // Convert both dates to milliseconds
- var date1ms = date1.getTime() - dateutil.tzOffset(date1)
- var date2ms = date2.getTime() - dateutil.tzOffset(date2)
- // Calculate the difference in milliseconds
- var differencems = date1ms - date2ms
- // Convert back to days and return
- return Math.round(differencems / dateutil.ONE_DAY)
- },
- /**
- * @see: <http://docs.python.org/library/datetime.html#datetime.date.toordinal>
- */
- toOrdinal: function (date) {
- if (date < dateutil.ORDINAL_BASE) {
- throw new Error('dates lower than ' + dateutil.ORDINAL_BASE + ' are not supported')
- }
- return dateutil.daysBetween(date, dateutil.ORDINAL_BASE)
- },
- /**
- * @see - <http://docs.python.org/library/datetime.html#datetime.date.fromordinal>
- */
- fromOrdinal: function (ordinal) {
- var millisecsFromBase = ordinal * dateutil.ONE_DAY
- return new Date(dateutil.ORDINAL_BASE.getTime() -
- dateutil.tzOffset(dateutil.ORDINAL_BASE) +
- millisecsFromBase +
- dateutil.tzOffset(new Date(millisecsFromBase)))
- },
- /**
- * @see: <http://docs.python.org/library/calendar.html#calendar.monthrange>
- */
- monthRange: function (year, month) {
- var date = new Date(year, month, 1)
- return [dateutil.getWeekday(date), dateutil.getMonthDays(date)]
- },
- getMonthDays: function (date) {
- var month = date.getMonth()
- return month === 1 && dateutil.isLeapYear(date.getFullYear())
- ? 29 : dateutil.MONTH_DAYS[month]
- },
- /**
- * @return {Number} python-like weekday
- */
- getWeekday: function (date) {
- return dateutil.PY_WEEKDAYS[date.getDay()]
- },
- /**
- * @see: <http://docs.python.org/library/datetime.html#datetime.datetime.combine>
- */
- combine: function (date, time) {
- time = time || date
- return new Date(
- date.getFullYear(), date.getMonth(), date.getDate(),
- time.getHours(), time.getMinutes(), time.getSeconds(),
- time.getMilliseconds())
- },
- clone: function (date) {
- var dolly = new Date(date.getTime())
- return dolly
- },
- cloneDates: function (dates) {
- var clones = []
- for (var i = 0; i < dates.length; i++) {
- clones.push(dateutil.clone(dates[i]))
- }
- return clones
- },
- /**
- * Sorts an array of Date or dateutil.Time objects
- */
- sort: function (dates) {
- dates.sort(function (a, b) {
- return a.getTime() - b.getTime()
- })
- },
- timeToUntilString: function (time) {
- var comp
- var date = new Date(time)
- var comps = [
- date.getUTCFullYear(),
- date.getUTCMonth() + 1,
- date.getUTCDate(),
- 'T',
- date.getUTCHours(),
- date.getUTCMinutes(),
- date.getUTCSeconds(),
- 'Z'
- ]
- for (var i = 0; i < comps.length; i++) {
- comp = comps[i]
- if (!/[TZ]/.test(comp) && comp < 10) comps[i] = '0' + String(comp)
- }
- return comps.join('')
- },
- untilStringToDate: function (until) {
- var re = /^(\d{4})(\d{2})(\d{2})(T(\d{2})(\d{2})(\d{2})Z?)?$/
- var bits = re.exec(until)
- if (!bits) throw new Error('Invalid UNTIL value: ' + until)
- return new Date(Date.UTC(
- bits[1],
- bits[2] - 1,
- bits[3],
- bits[5] || 0,
- bits[6] || 0,
- bits[7] || 0))
- }
- }
- dateutil.Time = function (hour, minute, second, millisecond) {
- this.hour = hour
- this.minute = minute
- this.second = second
- this.millisecond = millisecond || 0
- }
- dateutil.Time.prototype = {
- constructor: dateutil.Time,
- getHours: function () {
- return this.hour
- },
- getMinutes: function () {
- return this.minute
- },
- getSeconds: function () {
- return this.second
- },
- getMilliseconds: function () {
- return this.millisecond
- },
- getTime: function () {
- return ((this.hour * 60 * 60) + (this.minute * 60) + this.second) * 1000 +
- this.millisecond
- }
- }
- // =============================================================================
- // Helper functions
- // =============================================================================
- /**
- * Simplified version of python's range()
- */
- var range = function (start, end) {
- if (arguments.length === 1) {
- end = start
- start = 0
- }
- var rang = []
- for (var i = start; i < end; i++) rang.push(i)
- return rang
- }
- var repeat = function (value, times) {
- var i = 0
- var array = []
- if (value instanceof Array) {
- for (; i < times; i++) array[i] = [].concat(value)
- } else {
- for (; i < times; i++) array[i] = value
- }
- return array
- }
- /**
- * Python like split
- */
- var split = function (str, sep, num) {
- var splits = str.split(sep)
- return num
- ? splits.slice(0, num).concat([splits.slice(num).join(sep)]) : splits
- }
- /**
- * closure/goog/math/math.js:modulo
- * Copyright 2006 The Closure Library Authors.
- * The % operator in JavaScript returns the remainder of a / b, but differs from
- * some other languages in that the result will have the same sign as the
- * dividend. For example, -1 % 8 == -1, whereas in some other languages
- * (such as Python) the result would be 7. This function emulates the more
- * correct modulo behavior, which is useful for certain applications such as
- * calculating an offset index in a circular list.
- *
- * @param {number} a The dividend.
- * @param {number} b The divisor.
- * @return {number} a % b where the result is between 0 and b (either 0 <= x < b
- * or b < x <= 0, depending on the sign of b).
- */
- var pymod = function (a, b) {
- var r = a % b
- // If r and b differ in sign, add b to wrap the result to the correct sign.
- return (r * b < 0) ? r + b : r
- }
- /**
- * @see: <http://docs.python.org/library/functions.html#divmod>
- */
- var divmod = function (a, b) {
- return {div: Math.floor(a / b), mod: pymod(a, b)}
- }
- /**
- * Python-like boolean
- * @return {Boolean} value of an object/primitive, taking into account
- * the fact that in Python an empty list's/tuple's
- * boolean value is False, whereas in JS it's true
- */
- var plb = function (obj) {
- return (obj instanceof Array && obj.length === 0)
- ? false : Boolean(obj)
- }
- /**
- * Return true if a value is in an array
- */
- var contains = function (arr, val) {
- return arr.indexOf(val) !== -1
- }
- // =============================================================================
- // Date masks
- // =============================================================================
- // Every mask is 7 days longer to handle cross-year weekly periods.
- var M365MASK = [].concat(
- repeat(1, 31), repeat(2, 28), repeat(3, 31),
- repeat(4, 30), repeat(5, 31), repeat(6, 30),
- repeat(7, 31), repeat(8, 31), repeat(9, 30),
- repeat(10, 31), repeat(11, 30), repeat(12, 31),
- repeat(1, 7))
- var M366MASK = [].concat(
- repeat(1, 31), repeat(2, 29), repeat(3, 31),
- repeat(4, 30), repeat(5, 31), repeat(6, 30),
- repeat(7, 31), repeat(8, 31), repeat(9, 30),
- repeat(10, 31), repeat(11, 30), repeat(12, 31),
- repeat(1, 7))
- var M28 = range(1, 29)
- var M29 = range(1, 30)
- var M30 = range(1, 31)
- var M31 = range(1, 32)
- var MDAY366MASK = [].concat(
- M31, M29, M31,
- M30, M31, M30,
- M31, M31, M30,
- M31, M30, M31,
- M31.slice(0, 7))
- var MDAY365MASK = [].concat(
- M31, M28, M31,
- M30, M31, M30,
- M31, M31, M30,
- M31, M30, M31,
- M31.slice(0, 7))
- M28 = range(-28, 0)
- M29 = range(-29, 0)
- M30 = range(-30, 0)
- M31 = range(-31, 0)
- var NMDAY366MASK = [].concat(
- M31, M29, M31,
- M30, M31, M30,
- M31, M31, M30,
- M31, M30, M31,
- M31.slice(0, 7))
- var NMDAY365MASK = [].concat(
- M31, M28, M31,
- M30, M31, M30,
- M31, M31, M30,
- M31, M30, M31,
- M31.slice(0, 7))
- var M366RANGE = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
- var M365RANGE = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]
- var WDAYMASK = (function () {
- for (var wdaymask = [], i = 0; i < 55; i++) wdaymask = wdaymask.concat(range(7))
- return wdaymask
- }())
- var WDAYS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']
- // =============================================================================
- // Weekday
- // =============================================================================
- var Weekday = function (weekday, n) {
- if (n === 0) throw new Error("Can't create weekday with n == 0")
- this.weekday = weekday
- this.n = n
- }
- Weekday.prototype = {
- constructor: Weekday,
- // __call__ - Cannot call the object directly, do it through
- // e.g. RRule.TH.nth(-1) instead,
- nth: function (n) {
- return this.n === n ? this : new Weekday(this.weekday, n)
- },
- // __eq__
- equals: function (other) {
- return this.weekday === other.weekday && this.n === other.n
- },
- // __repr__
- toString: function () {
- var s = WDAYS[this.weekday]
- if (this.n) s = (this.n > 0 ? '+' : '') + String(this.n) + s
- return s
- },
- getJsWeekday: function () {
- return this.weekday === 6 ? 0 : this.weekday + 1
- }
- }
- // =============================================================================
- // RRule
- // =============================================================================
- /**
- *
- * @param {Object?} options - see <http://labix.org/python-dateutil/#head-cf004ee9a75592797e076752b2a889c10f445418>
- * The only required option is `freq`, one of RRule.YEARLY, RRule.MONTHLY, ...
- * @constructor
- */
- var RRule = function (options, noCache) {
- options = options || {}
- // RFC string
- this._string = null
- this._cache = noCache ? null : {
- all: false,
- before: [],
- after: [],
- between: []
- }
- // used by toString()
- this.origOptions = {}
- this.options = {}
- var invalid = []
- var keys = Object.keys(options)
- var defaultKeys = Object.keys(RRule.DEFAULT_OPTIONS)
- // Shallow copy for options and origOptions and check for invalid
- keys.forEach(function (key) {
- this.origOptions[key] = options[key]
- this.options[key] = options[key]
- if (!contains(defaultKeys, key)) invalid.push(key)
- }, this)
- if (invalid.length) throw new Error('Invalid options: ' + invalid.join(', '))
- if (!RRule.FREQUENCIES[options.freq] && options.byeaster === null) {
- throw new Error('Invalid frequency: ' + String(options.freq))
- }
- // Merge in default options
- defaultKeys.forEach(function (key) {
- if (!contains(keys, key)) this.options[key] = RRule.DEFAULT_OPTIONS[key]
- }, this)
- var opts = this.options
- if (opts.byeaster !== null) opts.freq = RRule.YEARLY
- if (!opts.dtstart) opts.dtstart = new Date(new Date().setMilliseconds(0))
- var millisecondModulo = opts.dtstart.getTime() % 1000
- if (opts.wkst === null) {
- opts.wkst = RRule.MO.weekday
- } else if (typeof opts.wkst === 'number') {
- // cool, just keep it like that
- } else {
- opts.wkst = opts.wkst.weekday
- }
- if (opts.bysetpos !== null) {
- if (typeof opts.bysetpos === 'number') opts.bysetpos = [opts.bysetpos]
- for (var i = 0; i < opts.bysetpos.length; i++) {
- var v = opts.bysetpos[i]
- if (v === 0 || !(v >= -366 && v <= 366)) {
- throw new Error('bysetpos must be between 1 and 366,' +
- ' or between -366 and -1')
- }
- }
- }
- if (!(plb(opts.byweekno) || plb(opts.byyearday) || plb(opts.bymonthday) ||
- opts.byweekday !== null || opts.byeaster !== null)) {
- switch (opts.freq) {
- case RRule.YEARLY:
- if (!opts.bymonth) opts.bymonth = opts.dtstart.getMonth() + 1
- opts.bymonthday = opts.dtstart.getDate()
- break
- case RRule.MONTHLY:
- opts.bymonthday = opts.dtstart.getDate()
- break
- case RRule.WEEKLY:
- opts.byweekday = dateutil.getWeekday(opts.dtstart)
- break
- }
- }
- // bymonth
- if (opts.bymonth !== null && !(opts.bymonth instanceof Array)) {
- opts.bymonth = [opts.bymonth]
- }
- // byyearday
- if (opts.byyearday !== null && !(opts.byyearday instanceof Array)) {
- opts.byyearday = [opts.byyearday]
- }
- // bymonthday
- if (opts.bymonthday === null) {
- opts.bymonthday = []
- opts.bynmonthday = []
- } else if (opts.bymonthday instanceof Array) {
- var bymonthday = []
- var bynmonthday = []
- for (i = 0; i < opts.bymonthday.length; i++) {
- v = opts.bymonthday[i]
- if (v > 0) {
- bymonthday.push(v)
- } else if (v < 0) {
- bynmonthday.push(v)
- }
- }
- opts.bymonthday = bymonthday
- opts.bynmonthday = bynmonthday
- } else {
- if (opts.bymonthday < 0) {
- opts.bynmonthday = [opts.bymonthday]
- opts.bymonthday = []
- } else {
- opts.bynmonthday = []
- opts.bymonthday = [opts.bymonthday]
- }
- }
- // byweekno
- if (opts.byweekno !== null && !(opts.byweekno instanceof Array)) {
- opts.byweekno = [opts.byweekno]
- }
- // byweekday / bynweekday
- if (opts.byweekday === null) {
- opts.bynweekday = null
- } else if (typeof opts.byweekday === 'number') {
- opts.byweekday = [opts.byweekday]
- opts.bynweekday = null
- } else if (opts.byweekday instanceof Weekday) {
- if (!opts.byweekday.n || opts.freq > RRule.MONTHLY) {
- opts.byweekday = [opts.byweekday.weekday]
- opts.bynweekday = null
- } else {
- opts.bynweekday = [
- [opts.byweekday.weekday, opts.byweekday.n]
- ]
- opts.byweekday = null
- }
- } else {
- var byweekday = []
- var bynweekday = []
- for (i = 0; i < opts.byweekday.length; i++) {
- var wday = opts.byweekday[i]
- if (typeof wday === 'number') {
- byweekday.push(wday)
- } else if (!wday.n || opts.freq > RRule.MONTHLY) {
- byweekday.push(wday.weekday)
- } else {
- bynweekday.push([wday.weekday, wday.n])
- }
- }
- opts.byweekday = plb(byweekday) ? byweekday : null
- opts.bynweekday = plb(bynweekday) ? bynweekday : null
- }
- // byhour
- if (opts.byhour === null) {
- opts.byhour = (opts.freq < RRule.HOURLY) ? [opts.dtstart.getHours()] : null
- } else if (typeof opts.byhour === 'number') {
- opts.byhour = [opts.byhour]
- }
- // byminute
- if (opts.byminute === null) {
- opts.byminute = (opts.freq < RRule.MINUTELY)
- ? [opts.dtstart.getMinutes()] : null
- } else if (typeof opts.byminute === 'number') {
- opts.byminute = [opts.byminute]
- }
- // bysecond
- if (opts.bysecond === null) {
- opts.bysecond = (opts.freq < RRule.SECONDLY)
- ? [opts.dtstart.getSeconds()] : null
- } else if (typeof opts.bysecond === 'number') {
- opts.bysecond = [opts.bysecond]
- }
- if (opts.freq >= RRule.HOURLY) {
- this.timeset = null
- } else {
- this.timeset = []
- for (i = 0; i < opts.byhour.length; i++) {
- var hour = opts.byhour[i]
- for (var j = 0; j < opts.byminute.length; j++) {
- var minute = opts.byminute[j]
- for (var k = 0; k < opts.bysecond.length; k++) {
- var second = opts.bysecond[k]
- // python:
- // datetime.time(hour, minute, second,
- // tzinfo=self._tzinfo))
- this.timeset.push(new dateutil.Time(hour, minute, second, millisecondModulo))
- }
- }
- }
- dateutil.sort(this.timeset)
- }
- }
- // RRule class 'constants'
- RRule.FREQUENCIES = [
- 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY',
- 'HOURLY', 'MINUTELY', 'SECONDLY'
- ]
- RRule.YEARLY = 0
- RRule.MONTHLY = 1
- RRule.WEEKLY = 2
- RRule.DAILY = 3
- RRule.HOURLY = 4
- RRule.MINUTELY = 5
- RRule.SECONDLY = 6
- RRule.MO = new Weekday(0)
- RRule.TU = new Weekday(1)
- RRule.WE = new Weekday(2)
- RRule.TH = new Weekday(3)
- RRule.FR = new Weekday(4)
- RRule.SA = new Weekday(5)
- RRule.SU = new Weekday(6)
- RRule.DEFAULT_OPTIONS = {
- freq: null,
- dtstart: null,
- interval: 1,
- wkst: RRule.MO,
- count: null,
- until: null,
- bysetpos: null,
- bymonth: null,
- bymonthday: null,
- bynmonthday: null,
- byyearday: null,
- byweekno: null,
- byweekday: null,
- bynweekday: null,
- byhour: null,
- byminute: null,
- bysecond: null,
- byeaster: null
- }
- RRule.parseText = function (text, language) {
- return getnlp().parseText(text, language)
- }
- RRule.fromText = function (text, language) {
- return getnlp().fromText(text, language)
- }
- RRule.optionsToString = function (options) {
- var key, value, strValues
- var pairs = []
- var keys = Object.keys(options)
- var defaultKeys = Object.keys(RRule.DEFAULT_OPTIONS)
- for (var i = 0; i < keys.length; i++) {
- if (!contains(defaultKeys, keys[i])) continue
- key = keys[i].toUpperCase()
- value = options[keys[i]]
- strValues = []
- if (value === null || value instanceof Array && !value.length) continue
- switch (key) {
- case 'FREQ':
- value = RRule.FREQUENCIES[options.freq]
- break
- case 'WKST':
- if (!(value instanceof Weekday)) {
- value = new Weekday(value)
- }
- break
- case 'BYWEEKDAY':
- /*
- NOTE: BYWEEKDAY is a special case.
- RRule() deconstructs the rule.options.byweekday array
- into an array of Weekday arguments.
- On the other hand, rule.origOptions is an array of Weekdays.
- We need to handle both cases here.
- It might be worth change RRule to keep the Weekdays.
- Also, BYWEEKDAY (used by RRule) vs. BYDAY (RFC)
- */
- key = 'BYDAY'
- if (!(value instanceof Array)) value = [value]
- for (var wday, j = 0; j < value.length; j++) {
- wday = value[j]
- if (wday instanceof Weekday) {
- // good
- } else if (wday instanceof Array) {
- wday = new Weekday(wday[0], wday[1])
- } else {
- wday = new Weekday(wday)
- }
- strValues[j] = wday.toString()
- }
- value = strValues
- break
- case 'DTSTART':
- case 'UNTIL':
- value = dateutil.timeToUntilString(value)
- break
- default:
- if (value instanceof Array) {
- for (j = 0; j < value.length; j++) strValues[j] = String(value[j])
- value = strValues
- } else {
- value = String(value)
- }
- }
- pairs.push([key, value])
- }
- var strings = []
- for (i = 0; i < pairs.length; i++) {
- var attr = pairs[i]
- strings.push(attr[0] + '=' + attr[1].toString())
- }
- return strings.join(';')
- }
- RRule.prototype = {
- constructor: RRule,
- /**
- * @param {Function} iterator - optional function that will be called
- * on each date that is added. It can return false
- * to stop the iteration.
- * @return Array containing all recurrences.
- */
- all: function (iterator) {
- if (iterator) {
- return this._iter(new CallbackIterResult('all', {}, iterator))
- } else {
- var result = this._cacheGet('all')
- if (result === false) {
- result = this._iter(new IterResult('all', {}))
- this._cacheAdd('all', result)
- }
- return result
- }
- },
- /**
- * Returns all the occurrences of the rrule between after and before.
- * The inc keyword defines what happens if after and/or before are
- * themselves occurrences. With inc == True, they will be included in the
- * list, if they are found in the recurrence set.
- * @return Array
- */
- between: function (after, before, inc, iterator) {
- var args = {
- before: before,
- after: after,
- inc: inc
- }
- if (iterator) {
- return this._iter(new CallbackIterResult('between', args, iterator))
- }
- var result = this._cacheGet('between', args)
- if (result === false) {
- result = this._iter(new IterResult('between', args))
- this._cacheAdd('between', result, args)
- }
- return result
- },
- /**
- * Returns the last recurrence before the given datetime instance.
- * The inc keyword defines what happens if dt is an occurrence.
- * With inc == True, if dt itself is an occurrence, it will be returned.
- * @return Date or null
- */
- before: function (dt, inc) {
- var args = {dt: dt, inc: inc}
- var result = this._cacheGet('before', args)
- if (result === false) {
- result = this._iter(new IterResult('before', args))
- this._cacheAdd('before', result, args)
- }
- return result
- },
- /**
- * Returns the first recurrence after the given datetime instance.
- * The inc keyword defines what happens if dt is an occurrence.
- * With inc == True, if dt itself is an occurrence, it will be returned.
- * @return Date or null
- */
- after: function (dt, inc) {
- var args = {dt: dt, inc: inc}
- var result = this._cacheGet('after', args)
- if (result === false) {
- result = this._iter(new IterResult('after', args))
- this._cacheAdd('after', result, args)
- }
- return result
- },
- /**
- * Returns the number of recurrences in this set. It will have go trough
- * the whole recurrence, if this hasn't been done before.
- */
- count: function () {
- return this.all().length
- },
- /**
- * Converts the rrule into its string representation
- * @see <http://www.ietf.org/rfc/rfc2445.txt>
- * @return String
- */
- toString: function () {
- return RRule.optionsToString(this.origOptions)
- },
- /**
- * Will convert all rules described in nlp:ToText
- * to text.
- */
- toText: function (gettext, language) {
- return getnlp().toText(this, gettext, language)
- },
- isFullyConvertibleToText: function () {
- return getnlp().isFullyConvertible(this)
- },
- /**
- * @param {String} what - all/before/after/between
- * @param {Array,Date} value - an array of dates, one date, or null
- * @param {Object?} args - _iter arguments
- */
- _cacheAdd: function (what, value, args) {
- if (!this._cache) return
- if (value) {
- value = (value instanceof Date)
- ? dateutil.clone(value) : dateutil.cloneDates(value)
- }
- if (what === 'all') {
- this._cache.all = value
- } else {
- args._value = value
- this._cache[what].push(args)
- }
- },
- /**
- * @return false - not in the cache
- * null - cached, but zero occurrences (before/after)
- * Date - cached (before/after)
- * [] - cached, but zero occurrences (all/between)
- * [Date1, DateN] - cached (all/between)
- */
- _cacheGet: function (what, args) {
- if (!this._cache) return false
- var cached = false
- var argsKeys = args ? Object.keys(args) : []
- var findCacheDiff = function (item) {
- for (var key, i = 0; i < argsKeys.length; i++) {
- key = argsKeys[i]
- if (String(args[key]) !== String(item[key])) return true
- }
- return false
- }
- if (what === 'all') {
- cached = this._cache.all
- } else {
- // Let's see whether we've already called the
- // 'what' method with the same 'args'
- for (var item, i = 0; i < this._cache[what].length; i++) {
- item = this._cache[what][i]
- if (argsKeys.length && findCacheDiff(item)) continue
- cached = item._value
- break
- }
- }
- if (!cached && this._cache.all) {
- // Not in the cache, but we already know all the occurrences,
- // so we can find the correct dates from the cached ones.
- var iterResult = new IterResult(what, args)
- for (i = 0; i < this._cache.all.length; i++) {
- if (!iterResult.accept(this._cache.all[i])) break
- }
- cached = iterResult.getValue()
- this._cacheAdd(what, cached, args)
- }
- return cached instanceof Array
- ? dateutil.cloneDates(cached)
- : (cached instanceof Date ? dateutil.clone(cached) : cached)
- },
- /**
- * @return a RRule instance with the same freq and options
- * as this one (cache is not cloned)
- */
- clone: function () {
- return new RRule(this.origOptions)
- },
- _iter: function (iterResult) {
- /* Since JavaScript doesn't have the python's yield operator (<1.7),
- we use the IterResult object that tells us when to stop iterating.
- */
- var dtstart = this.options.dtstart
- var dtstartMillisecondModulo = this.options.dtstart % 1000
- var year = dtstart.getFullYear()
- var month = dtstart.getMonth() + 1
- var day = dtstart.getDate()
- var hour = dtstart.getHours()
- var minute = dtstart.getMinutes()
- var second = dtstart.getSeconds()
- var weekday = dateutil.getWeekday(dtstart)
- // Some local variables to speed things up a bit
- var freq = this.options.freq
- var interval = this.options.interval
- var wkst = this.options.wkst
- var until = this.options.until
- var bymonth = this.options.bymonth
- var byweekno = this.options.byweekno
- var byyearday = this.options.byyearday
- var byweekday = this.options.byweekday
- var byeaster = this.options.byeaster
- var bymonthday = this.options.bymonthday
- var bynmonthday = this.options.bynmonthday
- var bysetpos = this.options.bysetpos
- var byhour = this.options.byhour
- var byminute = this.options.byminute
- var bysecond = this.options.bysecond
- var ii = new Iterinfo(this)
- ii.rebuild(year, month)
- var getdayset = {}
- getdayset[RRule.YEARLY] = ii.ydayset
- getdayset[RRule.MONTHLY] = ii.mdayset
- getdayset[RRule.WEEKLY] = ii.wdayset
- getdayset[RRule.DAILY] = ii.ddayset
- getdayset[RRule.HOURLY] = ii.ddayset
- getdayset[RRule.MINUTELY] = ii.ddayset
- getdayset[RRule.SECONDLY] = ii.ddayset
- getdayset = getdayset[freq]
- var timeset
- if (freq < RRule.HOURLY) {
- timeset = this.timeset
- } else {
- var gettimeset = {}
- gettimeset[RRule.HOURLY] = ii.htimeset
- gettimeset[RRule.MINUTELY] = ii.mtimeset
- gettimeset[RRule.SECONDLY] = ii.stimeset
- gettimeset = gettimeset[freq]
- if ((freq >= RRule.HOURLY && plb(byhour) && !contains(byhour, hour)) ||
- (freq >= RRule.MINUTELY && plb(byminute) && !contains(byminute, minute)) ||
- (freq >= RRule.SECONDLY && plb(bysecond) && !contains(bysecond, second))) {
- timeset = []
- } else {
- timeset = gettimeset.call(ii, hour, minute, second, dtstartMillisecondModulo)
- }
- }
- var total = 0
- var count = this.options.count
- var i, j, k, dm, div, mod, tmp, pos, dayset, start, end, fixday, filtered
- while (true) {
- // Get dayset with the right frequency
- tmp = getdayset.call(ii, year, month, day)
- dayset = tmp[0]
- start = tmp[1]
- end = tmp[2]
- // Do the "hard" work ;-)
- filtered = false
- for (j = start; j < end; j++) {
- i = dayset[j]
- filtered = (plb(bymonth) && !contains(bymonth, ii.mmask[i])) ||
- (plb(byweekno) && !ii.wnomask[i]) ||
- (plb(byweekday) && !contains(byweekday, ii.wdaymask[i])) ||
- (plb(ii.nwdaymask) && !ii.nwdaymask[i]) ||
- (byeaster !== null && !contains(ii.eastermask, i)) ||
- ((plb(bymonthday) || plb(bynmonthday)) &&
- !contains(bymonthday, ii.mdaymask[i]) &&
- !contains(bynmonthday, ii.nmdaymask[i])) ||
- (plb(byyearday) &&
- ((i < ii.yearlen &&
- !contains(byyearday, i + 1) &&
- !contains(byyearday, -ii.yearlen + i)) ||
- (i >= ii.yearlen &&
- !contains(byyearday, i + 1 - ii.yearlen) &&
- !contains(byyearday, -ii.nextyearlen + i - ii.yearlen))))
- if (filtered) dayset[i] = null
- }
- // Output results
- if (plb(bysetpos) && plb(timeset)) {
- var daypos, timepos
- var poslist = []
- for (i, j = 0; j < bysetpos.length; j++) {
- pos = bysetpos[j]
- if (pos < 0) {
- daypos = Math.floor(pos / timeset.length)
- timepos = pymod(pos, timeset.length)
- } else {
- daypos = Math.floor((pos - 1) / timeset.length)
- timepos = pymod((pos - 1), timeset.length)
- }
- try {
- tmp = []
- for (k = start; k < end; k++) {
- var val = dayset[k]
- if (val === null) continue
- tmp.push(val)
- }
- if (daypos < 0) {
- // we're trying to emulate python's aList[-n]
- i = tmp.slice(daypos)[0]
- } else {
- i = tmp[daypos]
- }
- var time = timeset[timepos]
- var date = dateutil.fromOrdinal(ii.yearordinal + i)
- var res = dateutil.combine(date, time)
- // XXX: can this ever be in the array?
- // - compare the actual date instead?
- if (!contains(poslist, res)) poslist.push(res)
- } catch (e) {}
- }
- dateutil.sort(poslist)
- for (j = 0; j < poslist.length; j++) {
- res = poslist[j]
- if (until && res > until) {
- this._len = total
- return iterResult.getValue()
- } else if (res >= dtstart) {
- ++total
- if (!iterResult.accept(res)) return iterResult.getValue()
- if (count) {
- --count
- if (!count) {
- this._len = total
- return iterResult.getValue()
- }
- }
- }
- }
- } else {
- for (j = start; j < end; j++) {
- i = dayset[j]
- if (i !== null) {
- date = dateutil.fromOrdinal(ii.yearordinal + i)
- for (k = 0; k < timeset.length; k++) {
- time = timeset[k]
- res = dateutil.combine(date, time)
- if (until && res > until) {
- this._len = total
- return iterResult.getValue()
- } else if (res >= dtstart) {
- ++total
- if (!iterResult.accept(res)) return iterResult.getValue()
- if (count) {
- --count
- if (!count) {
- this._len = total
- return iterResult.getValue()
- }
- }
- }
- }
- }
- }
- }
- // Handle frequency and interval
- fixday = false
- if (freq === RRule.YEARLY) {
- year += interval
- if (year > dateutil.MAXYEAR) {
- this._len = total
- return iterResult.getValue()
- }
- ii.rebuild(year, month)
- } else if (freq === RRule.MONTHLY) {
- month += interval
- if (month > 12) {
- div = Math.floor(month / 12)
- mod = pymod(month, 12)
- month = mod
- year += div
- if (month === 0) {
- month = 12
- --year
- }
- if (year > dateutil.MAXYEAR) {
- this._len = total
- return iterResult.getValue()
- }
- }
- ii.rebuild(year, month)
- } else if (freq === RRule.WEEKLY) {
- if (wkst > weekday) {
- day += -(weekday + 1 + (6 - wkst)) + interval * 7
- } else {
- day += -(weekday - wkst) + interval * 7
- }
- weekday = wkst
- fixday = true
- } else if (freq === RRule.DAILY) {
- day += interval
- fixday = true
- } else if (freq === RRule.HOURLY) {
- if (filtered) {
- // Jump to one iteration before next day
- hour += Math.floor((23 - hour) / interval) * interval
- }
- while (true) {
- hour += interval
- dm = divmod(hour, 24)
- div = dm.div
- mod = dm.mod
- if (div) {
- hour = mod
- day += div
- fixday = true
- }
- if (!plb(byhour) || contains(byhour, hour)) break
- }
- timeset = gettimeset.call(ii, hour, minute, second)
- } else if (freq === RRule.MINUTELY) {
- if (filtered) {
- // Jump to one iteration before next day
- minute += Math.floor(
- (1439 - (hour * 60 + minute)) / interval) * interval
- }
- while (true) {
- minute += interval
- dm = divmod(minute, 60)
- div = dm.div
- mod = dm.mod
- if (div) {
- minute = mod
- hour += div
- dm = divmod(hour, 24)
- div = dm.div
- mod = dm.mod
- if (div) {
- hour = mod
- day += div
- fixday = true
- filtered = false
- }
- }
- if ((!plb(byhour) || contains(byhour, hour)) &&
- (!plb(byminute) || contains(byminute, minute))) {
- break
- }
- }
- timeset = gettimeset.call(ii, hour, minute, second)
- } else if (freq === RRule.SECONDLY) {
- if (filtered) {
- // Jump to one iteration before next day
- second += Math.floor(
- (86399 - (hour * 3600 + minute * 60 + second)) / interval) * interval
- }
- while (true) {
- second += interval
- dm = divmod(second, 60)
- div = dm.div
- mod = dm.mod
- if (div) {
- second = mod
- minute += div
- dm = divmod(minute, 60)
- div = dm.div
- mod = dm.mod
- if (div) {
- minute = mod
- hour += div
- dm = divmod(hour, 24)
- div = dm.div
- mod = dm.mod
- if (div) {
- hour = mod
- day += div
- fixday = true
- }
- }
- }
- if ((!plb(byhour) || contains(byhour, hour)) &&
- (!plb(byminute) || contains(byminute, minute)) &&
- (!plb(bysecond) || contains(bysecond, second))) {
- break
- }
- }
- timeset = gettimeset.call(ii, hour, minute, second)
- }
- if (fixday && day > 28) {
- var daysinmonth = dateutil.monthRange(year, month - 1)[1]
- if (day > daysinmonth) {
- while (day > daysinmonth) {
- day -= daysinmonth
- ++month
- if (month === 13) {
- month = 1
- ++year
- if (year > dateutil.MAXYEAR) {
- this._len = total
- return iterResult.getValue()
- }
- }
- daysinmonth = dateutil.monthRange(year, month - 1)[1]
- }
- ii.rebuild(year, month)
- }
- }
- }
- }
- }
- RRule.parseString = function (rfcString) {
- rfcString = rfcString.replace(/^\s+|\s+$/, '')
- if (!rfcString.length) return null
- var i, j, key, value, attr
- var attrs = rfcString.split(';')
- var options = {}
- for (i = 0; i < attrs.length; i++) {
- attr = attrs[i].split('=')
- key = attr[0]
- value = attr[1]
- switch (key) {
- case 'FREQ':
- options.freq = RRule[value]
- break
- case 'WKST':
- options.wkst = RRule[value]
- break
- case 'COUNT':
- case 'INTERVAL':
- case 'BYSETPOS':
- case 'BYMONTH':
- case 'BYMONTHDAY':
- case 'BYYEARDAY':
- case 'BYWEEKNO':
- case 'BYHOUR':
- case 'BYMINUTE':
- case 'BYSECOND':
- if (value.indexOf(',') !== -1) {
- value = value.split(',')
- for (j = 0; j < value.length; j++) {
- if (/^[+-]?\d+$/.test(value[j])) value[j] = Number(value[j])
- }
- } else if (/^[+-]?\d+$/.test(value)) {
- value = Number(value)
- }
- key = key.toLowerCase()
- options[key] = value
- break
- case 'BYDAY': // => byweekday
- var n, wday, day
- var days = value.split(',')
- options.byweekday = []
- for (j = 0; j < days.length; j++) {
- day = days[j]
- if (day.length === 2) { // MO, TU, ...
- wday = RRule[day] // wday instanceof Weekday
- options.byweekday.push(wday)
- } else { // -1MO, +3FR, 1SO, ...
- day = day.match(/^([+-]?\d)([A-Z]{2})$/)
- n = Number(day[1])
- wday = day[2]
- wday = RRule[wday].weekday
- options.byweekday.push(new Weekday(wday, n))
- }
- }
- break
- case 'DTSTART':
- options.dtstart = dateutil.untilStringToDate(value)
- break
- case 'UNTIL':
- options.until = dateutil.untilStringToDate(value)
- break
- case 'BYEASTER':
- options.byeaster = Number(value)
- break
- default:
- throw new Error("Unknown RRULE property '" + key + "'")
- }
- }
- return options
- }
- RRule.fromString = function (string) {
- return new RRule(RRule.parseString(string))
- }
- // =============================================================================
- // Iterinfo
- // =============================================================================
- var Iterinfo = function (rrule) {
- this.rrule = rrule
- this.lastyear = null
- this.lastmonth = null
- this.yearlen = null
- this.nextyearlen = null
- this.yearordinal = null
- this.yearweekday = null
- this.mmask = null
- this.mrange = null
- this.mdaymask = null
- this.nmdaymask = null
- this.wdaymask = null
- this.wnomask = null
- this.nwdaymask = null
- this.eastermask = null
- }
- Iterinfo.prototype.easter = function (y, offset) {
- offset = offset || 0
- var a = y % 19
- var b = Math.floor(y / 100)
- var c = y % 100
- var d = Math.floor(b / 4)
- var e = b % 4
- var f = Math.floor((b + 8) / 25)
- var g = Math.floor((b - f + 1) / 3)
- var h = Math.floor(19 * a + b - d - g + 15) % 30
- var i = Math.floor(c / 4)
- var k = c % 4
- var l = Math.floor(32 + 2 * e + 2 * i - h - k) % 7
- var m = Math.floor((a + 11 * h + 22 * l) / 451)
- var month = Math.floor((h + l - 7 * m + 114) / 31)
- var day = (h + l - 7 * m + 114) % 31 + 1
- var date = Date.UTC(y, month - 1, day + offset)
- var yearStart = Date.UTC(y, 0, 1)
- return [Math.ceil((date - yearStart) / (1000 * 60 * 60 * 24))]
- }
- Iterinfo.prototype.rebuild = function (year, month) {
- var rr = this.rrule
- if (year !== this.lastyear) {
- this.yearlen = dateutil.isLeapYear(year) ? 366 : 365
- this.nextyearlen = dateutil.isLeapYear(year + 1) ? 366 : 365
- var firstyday = new Date(year, 0, 1)
- this.yearordinal = dateutil.toOrdinal(firstyday)
- this.yearweekday = dateutil.getWeekday(firstyday)
- var wday = dateutil.getWeekday(new Date(year, 0, 1))
- if (this.yearlen === 365) {
- this.mmask = [].concat(M365MASK)
- this.mdaymask = [].concat(MDAY365MASK)
- this.nmdaymask = [].concat(NMDAY365MASK)
- this.wdaymask = WDAYMASK.slice(wday)
- this.mrange = [].concat(M365RANGE)
- } else {
- this.mmask = [].concat(M366MASK)
- this.mdaymask = [].concat(MDAY366MASK)
- this.nmdaymask = [].concat(NMDAY366MASK)
- this.wdaymask = WDAYMASK.slice(wday)
- this.mrange = [].concat(M366RANGE)
- }
- if (!plb(rr.options.byweekno)) {
- this.wnomask = null
- } else {
- this.wnomask = repeat(0, this.yearlen + 7)
- var no1wkst, firstwkst, wyearlen
- no1wkst = firstwkst = pymod(7 - this.yearweekday + rr.options.wkst, 7)
- if (no1wkst >= 4) {
- no1wkst = 0
- // Number of days in the year, plus the days we got
- // from last year.
- wyearlen = this.yearlen + pymod(this.yearweekday - rr.options.wkst, 7)
- } else {
- // Number of days in the year, minus the days we
- // left in last year.
- wyearlen = this.yearlen - no1wkst
- }
- var div = Math.floor(wyearlen / 7)
- var mod = pymod(wyearlen, 7)
- var numweeks = Math.floor(div + (mod / 4))
- for (var n, i, j = 0; j < rr.options.byweekno.length; j++) {
- n = rr.options.byweekno[j]
- if (n < 0) {
- n += numweeks + 1
- } if (!(n > 0 && n <= numweeks)) {
- continue
- } if (n > 1) {
- i = no1wkst + (n - 1) * 7
- if (no1wkst !== firstwkst) {
- i -= 7 - firstwkst
- }
- } else {
- i = no1wkst
- }
- for (var k = 0; k < 7; k++) {
- this.wnomask[i] = 1
- i++
- if (this.wdaymask[i] === rr.options.wkst) break
- }
- }
- if (contains(rr.options.byweekno, 1)) {
- // Check week number 1 of next year as well
- // orig-TODO : Check -numweeks for next year.
- i = no1wkst + numweeks * 7
- if (no1wkst !== firstwkst) i -= 7 - firstwkst
- if (i < this.yearlen) {
- // If week starts in next year, we
- // don't care about it.
- for (j = 0; j < 7; j++) {
- this.wnomask[i] = 1
- i += 1
- if (this.wdaymask[i] === rr.options.wkst) break
- }
- }
- }
- if (no1wkst) {
- // Check last week number of last year as
- // well. If no1wkst is 0, either the year
- // started on week start, or week number 1
- // got days from last year, so there are no
- // days from last year's last week number in
- // this year.
- var lnumweeks
- if (!contains(rr.options.byweekno, -1)) {
- var lyearweekday = dateutil.getWeekday(new Date(year - 1, 0, 1))
- var lno1wkst = pymod(7 - lyearweekday + rr.options.wkst, 7)
- var lyearlen = dateutil.isLeapYear(year - 1) ? 366 : 365
- if (lno1wkst >= 4) {
- lno1wkst = 0
- lnumweeks = Math.floor(52 +
- pymod(lyearlen + pymod(lyearweekday - rr.options.wkst, 7), 7) / 4)
- } else {
- lnumweeks = Math.floor(52 + pymod(this.yearlen - no1wkst, 7) / 4)
- }
- } else {
- lnumweeks = -1
- }
- if (contains(rr.options.byweekno, lnumweeks)) {
- for (i = 0; i < no1wkst; i++) this.wnomask[i] = 1
- }
- }
- }
- }
- if (plb(rr.options.bynweekday) && (month !== this.lastmonth || year !== this.lastyear)) {
- var ranges = []
- if (rr.options.freq === RRule.YEARLY) {
- if (plb(rr.options.bymonth)) {
- for (j = 0; j < rr.options.bymonth.length; j++) {
- month = rr.options.bymonth[j]
- ranges.push(this.mrange.slice(month - 1, month + 1))
- }
- } else {
- ranges = [[0, this.yearlen]]
- }
- } else if (rr.options.freq === RRule.MONTHLY) {
- ranges = [this.mrange.slice(month - 1, month + 1)]
- }
- if (plb(ranges)) {
- // Weekly frequency won't get here, so we may not
- // care about cross-year weekly periods.
- this.nwdaymask = repeat(0, this.yearlen)
- for (j = 0; j < ranges.length; j++) {
- var rang = ranges[j]
- var first = rang[0]
- var last = rang[1]
- last -= 1
- for (k = 0; k < rr.options.bynweekday.length; k++) {
- wday = rr.options.bynweekday[k][0]
- n = rr.options.bynweekday[k][1]
- if (n < 0) {
- i = last + (n + 1) * 7
- i -= pymod(this.wdaymask[i] - wday, 7)
- } else {
- i = first + (n - 1) * 7
- i += pymod(7 - this.wdaymask[i] + wday, 7)
- }
- if (first <= i && i <= last) this.nwdaymask[i] = 1
- }
- }
- }
- this.lastyear = year
- this.lastmonth = month
- }
- if (rr.options.byeaster !== null) {
- this.eastermask = this.easter(year, rr.options.byeaster)
- }
- }
- Iterinfo.prototype.ydayset = function (year, month, day) {
- return [range(this.yearlen), 0, this.yearlen]
- }
- Iterinfo.prototype.mdayset = function (year, month, day) {
- var set = repeat(null, this.yearlen)
- var start = this.mrange[month - 1]
- var end = this.mrange[month]
- for (var i = start; i < end; i++) set[i] = i
- return [set, start, end]
- }
- Iterinfo.prototype.wdayset = function (year, month, day) {
- // We need to handle cross-year weeks here.
- var set = repeat(null, this.yearlen + 7)
- var i = dateutil.toOrdinal(new Date(year, month - 1, day)) - this.yearordinal
- var start = i
- for (var j = 0; j < 7; j++) {
- set[i] = i
- ++i
- if (this.wdaymask[i] === this.rrule.options.wkst) break
- }
- return [set, start, i]
- }
- Iterinfo.prototype.ddayset = function (year, month, day) {
- var set = repeat(null, this.yearlen)
- var i = dateutil.toOrdinal(new Date(year, month - 1, day)) - this.yearordinal
- set[i] = i
- return [set, i, i + 1]
- }
- Iterinfo.prototype.htimeset = function (hour, minute, second, millisecond) {
- var set = []
- var rr = this.rrule
- for (var i = 0; i < rr.options.byminute.length; i++) {
- minute = rr.options.byminute[i]
- for (var j = 0; j < rr.options.bysecond.length; j++) {
- second = rr.options.bysecond[j]
- set.push(new dateutil.Time(hour, minute, second, millisecond))
- }
- }
- dateutil.sort(set)
- return set
- }
- Iterinfo.prototype.mtimeset = function (hour, minute, second, millisecond) {
- var set = []
- var rr = this.rrule
- for (var j = 0; j < rr.options.bysecond.length; j++) {
- second = rr.options.bysecond[j]
- set.push(new dateutil.Time(hour, minute, second, millisecond))
- }
- dateutil.sort(set)
- return set
- }
- Iterinfo.prototype.stimeset = function (hour, minute, second, millisecond) {
- return [new dateutil.Time(hour, minute, second, millisecond)]
- }
- // =============================================================================
- // Results
- // =============================================================================
- /**
- * This class helps us to emulate python's generators, sorta.
- */
- var IterResult = function (method, args) {
- this.init(method, args)
- }
- IterResult.prototype = {
- constructor: IterResult,
- init: function (method, args) {
- this.method = method
- this.args = args
- this.minDate = null
- this.maxDate = null
- this._result = []
- if (method === 'between') {
- this.maxDate = args.inc
- ? args.before : new Date(args.before.getTime() - 1)
- this.minDate = args.inc
- ? args.after : new Date(args.after.getTime() + 1)
- } else if (method === 'before') {
- this.maxDate = args.inc ? args.dt : new Date(args.dt.getTime() - 1)
- } else if (method === 'after') {
- this.minDate = args.inc ? args.dt : new Date(args.dt.getTime() + 1)
- }
- },
- /**
- * Possibly adds a date into the result.
- *
- * @param {Date} date - the date isn't necessarly added to the result
- * list (if it is too late/too early)
- * @return {Boolean} true if it makes sense to continue the iteration
- * false if we're done.
- */
- accept: function (date) {
- var tooEarly = this.minDate && date < this.minDate
- var tooLate = this.maxDate && date > this.maxDate
- if (this.method === 'between') {
- if (tooEarly) return true
- if (tooLate) return false
- } else if (this.method === 'before') {
- if (tooLate) return false
- } else if (this.method === 'after') {
- if (tooEarly) return true
- this.add(date)
- return false
- }
- return this.add(date)
- },
- /**
- *
- * @param {Date} date that is part of the result.
- * @return {Boolean} whether we are interested in more values.
- */
- add: function (date) {
- this._result.push(date)
- return true
- },
- /**
- * 'before' and 'after' return only one date, whereas 'all'
- * and 'between' an array.
- * @return {Date,Array?}
- */
- getValue: function () {
- var res = this._result
- switch (this.method) {
- case 'all':
- case 'between':
- return res
- case 'before':
- case 'after':
- return res.length ? res[res.length - 1] : null
- }
- },
- clone: function () {
- return new IterResult(this.method, this.args)
- }
- }
- /**
- * IterResult subclass that calls a callback function on each add,
- * and stops iterating when the callback returns false.
- */
- var CallbackIterResult = function (method, args, iterator) {
- var allowedMethods = ['all', 'between']
- if (!contains(allowedMethods, method)) {
- throw new Error('Invalid method "' + method +
- '". Only all and between works with iterator.')
- }
- this.add = function (date) {
- if (iterator(date, this._result.length)) {
- this._result.push(date)
- return true
- }
- return false
- }
- this.init(method, args)
- }
- CallbackIterResult.prototype = IterResult.prototype
- /**
- *
- * @param {Boolean?} noCache
- * The same stratagy as RRule on cache, default to false
- * @constructor
- */
- var RRuleSet = function (noCache) {
- // Let RRuleSet cacheable
- this._cache = noCache ? null : {
- all: false,
- before: [],
- after: [],
- between: []
- }
- this._rrule = []
- this._rdate = []
- this._exrule = []
- this._exdate = []
- }
- RRuleSet.prototype = {
- constructor: RRuleSet,
- /**
- * @param {RRule}
- */
- rrule: function (rrule) {
- if (!(rrule instanceof RRule)) {
- throw new TypeError(String(rrule) + ' is not RRule instance')
- }
- if (!contains(this._rrule.map(String), String(rrule))) {
- this._rrule.push(rrule)
- }
- },
- /**
- * @param {Date}
- */
- rdate: function (date) {
- if (!(date instanceof Date)) {
- throw new TypeError(String(date) + ' is not Date instance')
- }
- if (!contains(this._rdate.map(Number), Number(date))) {
- this._rdate.push(date)
- dateutil.sort(this._rdate)
- }
- },
- /**
- * @param {RRule}
- */
- exrule: function (rrule) {
- if (!(rrule instanceof RRule)) {
- throw new TypeError(String(rrule) + ' is not RRule instance')
- }
- if (!contains(this._exrule.map(String), String(rrule))) {
- this._exrule.push(rrule)
- }
- },
- /**
- * @param {Date}
- */
- exdate: function (date) {
- if (!(date instanceof Date)) {
- throw new TypeError(String(date) + ' is not Date instance')
- }
- if (!contains(this._exdate.map(Number), Number(date))) {
- this._exdate.push(date)
- dateutil.sort(this._exdate)
- }
- },
- valueOf: function () {
- var result = []
- if (this._rrule.length) {
- this._rrule.forEach(function (rrule) {
- result.push('RRULE:' + rrule)
- })
- }
- if (this._rdate.length) {
- result.push('RDATE:' + this._rdate.map(function (rdate) {
- return dateutil.timeToUntilString(rdate)
- }).join(','))
- }
- if (this._exrule.length) {
- this._exrule.forEach(function (exrule) {
- result.push('EXRULE:' + exrule)
- })
- }
- if (this._exdate.length) {
- result.push('EXDATE:' + this._exdate.map(function (exdate) {
- return dateutil.timeToUntilString(exdate)
- }).join(','))
- }
- return result
- },
- /**
- * to generate recurrence field sush as:
- * ["RRULE:FREQ=YEARLY;COUNT=2;BYDAY=TU;DTSTART=19970902T010000Z","RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TH;DTSTART=19970902T010000Z"]
- */
- toString: function () {
- return JSON.stringify(this.valueOf())
- },
- _iter: function (iterResult) {
- var _exdateHash = {}
- var _exrule = this._exrule
- var _accept = iterResult.accept
- function evalExdate (after, before) {
- _exrule.forEach(function (rrule) {
- rrule.between(after, before, true).forEach(function (date) {
- _exdateHash[Number(date)] = true
- })
- })
- }
- this._exdate.forEach(function (date) {
- _exdateHash[Number(date)] = true
- })
- iterResult.accept = function (date) {
- var dt = Number(date)
- if (!_exdateHash[dt]) {
- evalExdate(new Date(dt - 1), new Date(dt + 1))
- if (!_exdateHash[dt]) {
- _exdateHash[dt] = true
- return _accept.call(this, date)
- }
- }
- return true
- }
- if (iterResult.method === 'between') {
- evalExdate(iterResult.args.after, iterResult.args.before)
- iterResult.accept = function (date) {
- var dt = Number(date)
- if (!_exdateHash[dt]) {
- _exdateHash[dt] = true
- return _accept.call(this, date)
- }
- return true
- }
- }
- for (var i = 0; i < this._rdate.length; i++) {
- if (!iterResult.accept(new Date(this._rdate[i]))) break
- }
- this._rrule.forEach(function (rrule) {
- rrule._iter(iterResult)
- })
- var res = iterResult._result
- dateutil.sort(res)
- switch (iterResult.method) {
- case 'all':
- case 'between':
- return res
- case 'before':
- return (res.length && res[res.length - 1]) || null
- case 'after':
- return (res.length && res[0]) || null
- default:
- return null
- }
- },
- /**
- * Create a new RRuleSet Object completely base on current instance
- */
- clone: function () {
- var rrs = new RRuleSet(!!this._cache)
- var i
- for (i = 0; i < this._rrule.length; i++) {
- rrs.rrule(this._rrule[i].clone())
- }
- for (i = 0; i < this._rdate.length; i++) {
- rrs.rdate(new Date(this._rdate[i]))
- }
- for (i = 0; i < this._exrule.length; i++) {
- rrs.exrule(this._exrule[i].clone())
- }
- for (i = 0; i < this._exdate.length; i++) {
- rrs.exdate(new Date(this._exdate[i]))
- }
- return rrs
- }
- }
- /**
- * Inherts method from RRule
- * add Read interface and set RRuleSet cacheable
- */
- var RRuleSetMethods = ['all', 'between', 'before', 'after', 'count', '_cacheAdd', '_cacheGet']
- RRuleSetMethods.forEach(function (method) {
- RRuleSet.prototype[method] = RRule.prototype[method]
- })
- /**
- * RRuleStr
- * To parse a set of rrule strings
- */
- var RRuleStr = function () {}
- RRuleStr.DEFAULT_OPTIONS = {
- dtstart: null,
- cache: false,
- unfold: false,
- forceset: false,
- compatible: false,
- ignoretz: false,
- tzinfos: null
- }
- RRuleStr._freq_map = {
- 'YEARLY': RRule.YEARLY,
- 'MONTHLY': RRule.MONTHLY,
- 'WEEKLY': RRule.WEEKLY,
- 'DAILY': RRule.DAILY,
- 'HOURLY': RRule.HOURLY,
- 'MINUTELY': RRule.MINUTELY,
- 'SECONDLY': RRule.SECONDLY
- }
- RRuleStr._weekday_map = {
- 'MO': 0,
- 'TU': 1,
- 'WE': 2,
- 'TH': 3,
- 'FR': 4,
- 'SA': 5,
- 'SU': 6
- }
- RRuleStr.prototype = {
- constructor: RRuleStr,
- _handle_int: function (rrkwargs, name, value, options) {
- rrkwargs[name.toLowerCase()] = parseInt(value, 10)
- },
- _handle_int_list: function (rrkwargs, name, value, options) {
- rrkwargs[name.toLowerCase()] = value.split(',').map(function (x) {
- return parseInt(x, 10)
- })
- },
- _handle_FREQ: function (rrkwargs, name, value, options) {
- rrkwargs['freq'] = RRuleStr._freq_map[value]
- },
- _handle_UNTIL: function (rrkwargs, name, value, options) {
- try {
- rrkwargs['until'] = dateutil.untilStringToDate(value)
- } catch (error) {
- throw new Error('invalid until date')
- }
- },
- _handle_WKST: function (rrkwargs, name, value, options) {
- rrkwargs['wkst'] = RRuleStr._weekday_map[value]
- },
- _handle_BYWEEKDAY: function (rrkwargs, name, value, options) {
- // Two ways to specify this: +1MO or MO(+1)
- var splt, i, j, n, w, wday
- var l = []
- var wdays = value.split(',')
- for (i = 0; i < wdays.length; i++) {
- wday = wdays[i]
- if (wday.indexOf('(') > -1) {
- // If it's of the form TH(+1), etc.
- splt = wday.split('(')
- w = splt[0]
- n = parseInt(splt.slice(1, -1), 10)
- } else {
- // # If it's of the form +1MO
- for (j = 0; j < wday.length; j++) {
- if ('+-0123456789'.indexOf(wday[j]) === -1) break
- }
- n = wday.slice(0, j) || null
- w = wday.slice(j)
- if (n) n = parseInt(n, 10)
- }
- var weekday = new Weekday(RRuleStr._weekday_map[w], n)
- l.push(weekday)
- }
- rrkwargs['byweekday'] = l
- },
- _parseRfcRRule: function (line, options) {
- options = options || {}
- options.dtstart = options.dtstart || null
- options.cache = options.cache || false
- options.ignoretz = options.ignoretz || false
- options.tzinfos = options.tzinfos || null
- var name, value, parts
- if (line.indexOf(':') !== -1) {
- parts = line.split(':')
- name = parts[0]
- value = parts[1]
- if (name !== 'RRULE') throw new Error('unknown parameter name')
- } else {
- value = line
- }
- var i
- var rrkwargs = {}
- var pairs = value.split(';')
- for (i = 0; i < pairs.length; i++) {
- parts = pairs[i].split('=')
- name = parts[0].toUpperCase()
- value = parts[1].toUpperCase()
- try {
- this['_handle_' + name](rrkwargs, name, value, {
- ignoretz: options.ignoretz,
- tzinfos: options.tzinfos
- })
- } catch (error) {
- throw new Error("unknown parameter '" + name + "':" + value)
- }
- }
- rrkwargs.dtstart = rrkwargs.dtstart || options.dtstart
- return new RRule(rrkwargs, !options.cache)
- },
- _parseRfc: function (s, options) {
- if (options.compatible) {
- options.forceset = true
- options.unfold = true
- }
- s = s && s.toUpperCase().trim()
- if (!s) throw new Error('Invalid empty string')
- var i = 0
- var line, lines
- // More info about 'unfold' option
- // Go head to http://www.ietf.org/rfc/rfc2445.txt
- if (options.unfold) {
- lines = s.split('\n')
- while (i < lines.length) {
- // TODO
- line = lines[i] = lines[i].replace(/\s+$/g, '')
- if (!line) {
- lines.splice(i, 1)
- } else if (i > 0 && line[0] === ' ') {
- lines[i - 1] += line.slice(1)
- lines.splice(i, 1)
- } else {
- i += 1
- }
- }
- } else {
- lines = s.split(/\s/)
- }
- var rrulevals = []
- var rdatevals = []
- var exrulevals = []
- var exdatevals = []
- var name, value, parts, parms, parm, dtstart, rset, j, k, datestrs, datestr
- if (!options.forceset && lines.length === 1 && (s.indexOf(':') === -1 ||
- s.indexOf('RRULE:') === 0)) {
- return this._parseRfcRRule(lines[0], {
- cache: options.cache,
- dtstart: options.dtstart,
- ignoretz: options.ignoretz,
- tzinfos: options.tzinfos
- })
- } else {
- for (i = 0; i < lines.length; i++) {
- line = lines[i]
- if (!line) continue
- if (line.indexOf(':') === -1) {
- name = 'RRULE'
- value = line
- } else {
- parts = split(line, ':', 1)
- name = parts[0]
- value = parts[1]
- }
- parms = name.split(';')
- if (!parms) throw new Error('empty property name')
- name = parms[0]
- parms = parms.slice(1)
- if (name === 'RRULE') {
- for (j = 0; j < parms.length; j++) {
- parm = parms[j]
- throw new Error('unsupported RRULE parm: ' + parm)
- }
- rrulevals.push(value)
- } else if (name === 'RDATE') {
- for (j = 0; j < parms.length; j++) {
- parm = parms[j]
- if (parm !== 'VALUE=DATE-TIME' && parm !== 'VALUE=DATE') {
- throw new Error('unsupported RDATE parm: ' + parm)
- }
- }
- rdatevals.push(value)
- } else if (name === 'EXRULE') {
- for (j = 0; j < parms.length; j++) {
- parm = parms[j]
- throw new Error('unsupported EXRULE parm: ' + parm)
- }
- exrulevals.push(value)
- } else if (name === 'EXDATE') {
- for (j = 0; j < parms.length; j++) {
- parm = parms[j]
- if (parm !== 'VALUE=DATE-TIME' && parm !== 'VALUE=DATE') {
- throw new Error('unsupported RDATE parm: ' + parm)
- }
- }
- exdatevals.push(value)
- } else if (name === 'DTSTART') {
- dtstart = dateutil.untilStringToDate(value)
- } else {
- throw new Error('unsupported property: ' + name)
- }
- }
- if (options.forceset || rrulevals.length > 1 || rdatevals.length ||
- exrulevals.length || exdatevals.length) {
- rset = new RRuleSet(!options.cache)
- for (j = 0; j < rrulevals.length; j++) {
- rset.rrule(this._parseRfcRRule(rrulevals[j], {
- dtstart: options.dtstart || dtstart,
- ignoretz: options.ignoretz,
- tzinfos: options.tzinfos
- }))
- }
- for (j = 0; j < rdatevals.length; j++) {
- datestrs = rdatevals[j].split(',')
- for (k = 0; k < datestrs.length; k++) {
- datestr = datestrs[k]
- rset.rdate(dateutil.untilStringToDate(datestr))
- }
- }
- for (j = 0; j < exrulevals.length; j++) {
- rset.exrule(this._parseRfcRRule(exrulevals[j], {
- dtstart: options.dtstart || dtstart,
- ignoretz: options.ignoretz,
- tzinfos: options.tzinfos
- }))
- }
- for (j = 0; j < exdatevals.length; j++) {
- datestrs = exdatevals[j].split(',')
- for (k = 0; k < datestrs.length; k++) {
- datestr = datestrs[k]
- rset.exdate(dateutil.untilStringToDate(datestr))
- }
- }
- if (options.campatiable && options.dtstart) rset.rdate(dtstart)
- return rset
- } else {
- return this._parseRfcRRule(rrulevals[0], {
- dtstart: options.dtstart || dtstart,
- cache: options.cache,
- ignoretz: options.ignoretz,
- tzinfos: options.tzinfos
- })
- }
- }
- },
- parse: function (s, options) {
- options = options || {}
- var invalid = []
- var keys = Object.keys(options)
- var defaultKeys = Object.keys(RRuleStr.DEFAULT_OPTIONS)
- keys.forEach(function (key) {
- if (!contains(defaultKeys, key)) invalid.push(key)
- }, this)
- if (invalid.length) throw new Error('Invalid options: ' + invalid.join(', '))
- // Merge in default options
- defaultKeys.forEach(function (key) {
- if (!contains(keys, key)) options[key] = RRuleStr.DEFAULT_OPTIONS[key]
- })
- return this._parseRfc(s, options)
- }
- }
- RRuleStr.prototype._handle_DTSTART = function (rrkwargs, name, value, options) {
- rrkwargs[name.toLowerCase()] = dateutil.untilStringToDate(value)
- }
- RRuleStr.prototype._handle_BYDAY = RRuleStr.prototype._handle_BYWEEKDAY
- RRuleStr.prototype._handle_INTERVAL = RRuleStr.prototype._handle_int
- RRuleStr.prototype._handle_COUNT = RRuleStr.prototype._handle_int
- ;[
- '_handle_BYSETPOS', '_handle_BYMONTH', '_handle_BYMONTHDAY',
- '_handle_BYYEARDAY', '_handle_BYEASTER', '_handle_BYWEEKNO',
- '_handle_BYHOUR', '_handle_BYMINUTE', '_handle_BYSECOND'
- ].forEach(function (method) {
- RRuleStr.prototype[method] = RRuleStr.prototype._handle_int_list
- })
- // =============================================================================
- // Export
- // =============================================================================
- // Only one RRuleStr instance for all rrule string parsing work.
- var rruleStr = new RRuleStr()
- var rrulestr = function () {
- return rruleStr.parse.apply(rruleStr, arguments)
- }
- RRule.RRule = RRule
- RRule.RRuleSet = RRuleSet
- RRule.rrulestr = rrulestr
- return RRule
- function getnlp () {
- // Lazy, runtime import to avoid circular refs.
- if (!getnlp._nlp) {
- if (root && root._getRRuleNLP) {
- getnlp._nlp = root._getRRuleNLP(RRule)
- } else if (typeof require === 'function') {
- getnlp._nlp = require('./nlp')(RRule)
- } else {
- throw new Error('You need to include rrule/nlp.js for fromText/toText to work.')
- }
- }
- return getnlp._nlp
- }
- })); // eslint-disable-line
|