diff options
Diffstat (limited to 'radicale_web/web/infcloud/lib/rrule.js')
-rw-r--r-- | radicale_web/web/infcloud/lib/rrule.js | 1910 |
1 files changed, 1910 insertions, 0 deletions
diff --git a/radicale_web/web/infcloud/lib/rrule.js b/radicale_web/web/infcloud/lib/rrule.js new file mode 100644 index 0000000..ed2689e --- /dev/null +++ b/radicale_web/web/infcloud/lib/rrule.js @@ -0,0 +1,1910 @@ +/*! + * 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 + * + */ +(function(root){ + +var serverSide = typeof module !== 'undefined' && module.exports; + + +var getnlp = function() { + if (!getnlp._nlp) { + if (serverSide) { + // Lazy, runtime import to avoid circular refs. + getnlp._nlp = require('./nlp') + } else if (!(getnlp._nlp = root._RRuleNLP)) { + throw new Error( + 'You need to include rrule/nlp.js for fromText/toText to work.' + ) + } + } + return getnlp._nlp; +}; + + +//============================================================================= +// 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-1970 instead + */ + ORDINAL_BASE: new Date(1970, 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) { + if (year instanceof Date) { + year = year.getFullYear(); + } + 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 date1_ms = date1.getTime() - dateutil.tzOffset(date1); + var date2_ms = date2.getTime() - dateutil.tzOffset(date2); + // Calculate the difference in milliseconds + var difference_ms = Math.abs(date1_ms - date2_ms); + // Convert back to days and return + return Math.round(difference_ms / dateutil.ONE_DAY); + }, + + /** + * @see: <http://docs.python.org/library/datetime.html#datetime.date.toordinal> + */ + toOrdinal: function(date) { + 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) + ? 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() + ); + }, + + clone: function(date) { + var dolly = new Date(date.getTime()); + dolly.setMilliseconds(0); + 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 date = new Date(time); + var comp, 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) { + this.hour = hour; + this.minute = minute; + this.second = second; +}; + +dateutil.Time.prototype = { + getHours: function() { + return this.hour; + }, + getMinutes: function() { + return this.minute; + }, + getSeconds: function() { + return this.second; + }, + getTime: function() { + return ((this.hour * 60 * 60) + + (this.minute * 60) + + this.second) + * 1000; + } +}; + + +//============================================================================= +// 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, array = []; + if (value instanceof Array) { + for (; i < times; i++) { + array[i] = [].concat(value); + } + } else { + for (; i < times; i++) { + array[i] = value; + } + } + return array; +}; + + +/** + * 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), + M29 = range(1, 30), + M30 = range(1, 31), + 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; +}()); + + +//============================================================================= +// 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 = { + + // __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 = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'][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) { + + // RFC string + this._string = null; + + options = options || {}; + + this._cache = noCache ? null : { + all: false, + before: [], + after: [], + between: [] + }; + + // used by toString() + this.origOptions = {}; + + var invalid = [], + keys = Object.keys(options), + defaultKeys = Object.keys(RRule.DEFAULT_OPTIONS); + + // Shallow copy for origOptions and check for invalid + keys.forEach(function(key) { + this.origOptions[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)) options[key] = RRule.DEFAULT_OPTIONS[key]; + }); + + var opts = this.options = options; + + if (opts.byeaster !== null) { + opts.freq = RRule.YEARLY; + } + + if (!opts.dtstart) { + opts.dtstart = new Date(); + opts.dtstart.setMilliseconds(0); + } + + 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 || !(-366 <= v && 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 = [], bynmonthday = []; + + for (i = 0; i < opts.bymonthday.length; i++) { + var 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 = [], 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)); + } + } + } + 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, + byyearday: null, + byweekno: null, + byweekday: 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, keys, defaultKeys, value, strValues, pairs = []; + + keys = Object.keys(options); + 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': + value = value.toString(); + 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 (var j = 0; j < value.length; j++) { + strValues[j] = String(value[j]); + } + value = strValues; + } else { + value = String(value); + } + + } + pairs.push([key, value]); + } + + var strings = []; + for (var i = 0; i < pairs.length; i++) { + var attr = pairs[i]; + strings.push(attr[0] + '=' + attr[1].toString()); + } + return strings.join(';'); + +}; + +RRule.prototype = { + + /** + * @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)); + } else { + 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 + }, + 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 + }, + 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; + + if (what == 'all') { + cached = this._cache.all; + } else { + // Let's see whether we've already called the + // 'what' method with the same 'args' + loopItems: + for (var item, i = 0; i < this._cache[what].length; i++) { + item = this._cache[what][i]; + for (var k in args) { + if (args.hasOwnProperty(k) + && String(args[k]) != String(item[k])) { + continue loopItems; + } + } + 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 (var 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 + year = dtstart.getFullYear(), + month = dtstart.getMonth() + 1, + day = dtstart.getDate(), + hour = dtstart.getHours(), + minute = dtstart.getMinutes(), + second = dtstart.getSeconds(), + weekday = dateutil.getWeekday(dtstart), + yearday = dateutil.getYearDay(dtstart); + + // Some local variables to speed things up a bit + var + freq = this.options.freq, + interval = this.options.interval, + wkst = this.options.wkst, + until = this.options.until, + bymonth = this.options.bymonth, + byweekno = this.options.byweekno, + byyearday = this.options.byyearday, + byweekday = this.options.byweekday, + byeaster = this.options.byeaster, + bymonthday = this.options.bymonthday, + bynmonthday = this.options.bynmonthday, + bysetpos = this.options.bysetpos, + byhour = this.options.byhour, + byminute = this.options.byminute, + 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, minute))) + { + timeset = []; + } else { + timeset = gettimeset.call(ii, hour, minute, second); + } + } + + var filtered, total = 0, count = this.options.count; + + var iterNo = 0; + + var i, j, k, dm, div, mod, tmp, pos, dayset, start, end, fixday; + + 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]; + + if ((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) + ) + ) + ) + ) + { + dayset[i] = null; + filtered = true; + } + } + + // Output results + if (plb(bysetpos) && plb(timeset)) { + + var daypos, timepos, poslist = []; + + for (i, j = 0; j < bysetpos.length; j++) { + var 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++) { + var 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) { + var date = dateutil.fromOrdinal(ii.yearordinal + i); + for (k = 0; k < timeset.length; k++) { + var time = timeset[k]; + var 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, + attrs = rfcString.split(';'), + 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, 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, + b = Math.floor(y / 100), + c = y % 100, + d = Math.floor(b / 4), + e = b % 4, + f = Math.floor((b + 8) / 25), + g = Math.floor((b - f + 1) / 3), + h = Math.floor(19 * a + b - d - g + 15) % 30, + i = Math.floor(c / 4), + k = c % 4, + l = Math.floor(32 + 2 * e + 2 * i - h - k) % 7, + m = Math.floor((a + 11 * h + 22 * l) / 451), + month = Math.floor((h + l - 7 * m + 114) / 31), + day = (h + l - 7 * m + 114) % 31 + 1, + date = Date.UTC(y, month - 1, day + offset), + 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 (!(0 < n && 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. + var 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 (var 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 (var 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 (var j = 0; j < ranges.length; j++) { + var rang = ranges[j]; + var first = rang[0], last = rang[1]; + last -= 1; + for (var k = 0; k < rr.options.bynweekday.length; k++) { + var 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) { + var set = [], 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)); + } + } + dateutil.sort(set); + return set; +}; + +Iterinfo.prototype.mtimeset = function(hour, minute, second) { + var set = [], 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)); + } + dateutil.sort(set); + return set; +}; + +Iterinfo.prototype.stimeset = function(hour, minute, second) { + return [new dateutil.Time(hour, minute, second)]; +}; + + +//============================================================================= +// Results +//============================================================================= + +/** + * This class helps us to emulate python's generators, sorta. + */ +var IterResult = function(method, args) { + this.init(method, args) +}; + +IterResult.prototype = { + + init: function(method, args) { + this.method = method; + this.args = args; + + this._result = []; + + this.minDate = null; + this.maxDate = null; + + 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, + 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() { + switch (this.method) { + case 'all': + case 'between': + return this._result; + case 'before': + case 'after': + return this._result.length + ? this._result[this._result.length - 1] + : null; + } + } + +}; + + +/** + * 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; + + +//============================================================================= +// Export +//============================================================================= + +if (serverSide) { + module.exports = { + RRule: RRule + // rruleset: rruleset + } +} +if (typeof ender === 'undefined') { + root['RRule'] = RRule; + // root['rruleset'] = rruleset; +} + +if (typeof define === "function" && define.amd) { + /*global define:false */ + define("rrule", [], function () { + return RRule; + }); +} + +}(this)); |