Comparing version 2.1.0 to 2.2.0
1543
lib/nlp.js
@@ -20,983 +20,912 @@ /*! | ||
*/ | ||
(function (root){ | ||
/* 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._getRRuleNLP = factory() | ||
} | ||
}(typeof window === 'object' ? window : this, function () { | ||
// ============================================================================= | ||
// Helper functions | ||
// ============================================================================= | ||
var serverSide = typeof module !== 'undefined' && module.exports; | ||
var RRule; | ||
/** | ||
* Return true if a value is in an array | ||
*/ | ||
var contains = function (arr, val) { | ||
return arr.indexOf(val) !== -1 | ||
} | ||
return function (RRule) { | ||
// ============================================================================= | ||
// ToText | ||
// ============================================================================= | ||
if (serverSide) { | ||
RRule = require('./rrule').RRule; | ||
} else if (root.RRule) { | ||
RRule = root.RRule; | ||
} else if (typeof require !== 'undefined') { | ||
if (!RRule) {RRule = require('rrule');} | ||
} else { | ||
throw new Error('rrule.js is required for rrule/nlp.js to work') | ||
} | ||
/** | ||
* | ||
* @param {RRule} rrule | ||
* Optional: | ||
* @param {Function} gettext function | ||
* @param {Object} language definition | ||
* @constructor | ||
*/ | ||
var ToText = function (rrule, gettext, language) { | ||
this.text = '' | ||
this.language = language || ENGLISH | ||
this.gettext = gettext || function (id) { | ||
return id | ||
} | ||
this.rrule = rrule | ||
this.freq = rrule.options.freq | ||
this.options = rrule.options | ||
this.origOptions = rrule.origOptions | ||
//============================================================================= | ||
// Helper functions | ||
//============================================================================= | ||
if (this.origOptions.bymonthday) { | ||
var bymonthday = [].concat(this.options.bymonthday) | ||
var bynmonthday = [].concat(this.options.bynmonthday) | ||
/** | ||
* Return true if a value is in an array | ||
*/ | ||
var contains = function(arr, val) { | ||
return arr.indexOf(val) != -1; | ||
}; | ||
//============================================================================= | ||
// ToText | ||
//============================================================================= | ||
/** | ||
* | ||
* @param {RRule} rrule | ||
* Optional: | ||
* @param {Function} gettext function | ||
* @param {Object} language definition | ||
* @constructor | ||
*/ | ||
var ToText = function(rrule, gettext, language) { | ||
this.gettext = gettext || function(id) {return id}; | ||
this.language = language || ENGLISH; | ||
this.text = ''; | ||
this.rrule = rrule; | ||
this.freq = rrule.options.freq; | ||
this.options = rrule.options; | ||
this.origOptions = rrule.origOptions; | ||
if (this.origOptions.bymonthday) { | ||
var bymonthday = [].concat(this.options.bymonthday); | ||
var bynmonthday = [].concat(this.options.bynmonthday); | ||
bymonthday.sort(); | ||
bynmonthday.sort(); | ||
bynmonthday.reverse(); | ||
bymonthday.sort() | ||
bynmonthday.sort() | ||
bynmonthday.reverse() | ||
// 1, 2, 3, .., -5, -4, -3, .. | ||
this.bymonthday = bymonthday.concat(bynmonthday); | ||
if (!this.bymonthday.length) { | ||
this.bymonthday = null; | ||
} | ||
} | ||
this.bymonthday = bymonthday.concat(bynmonthday) | ||
if (!this.bymonthday.length) this.bymonthday = null | ||
} | ||
if (this.origOptions.byweekday) { | ||
if (this.origOptions.byweekday) { | ||
var byweekday = !(this.origOptions.byweekday instanceof Array) | ||
? [this.origOptions.byweekday] | ||
: this.origOptions.byweekday; | ||
var days = String(byweekday); | ||
? [this.origOptions.byweekday] : this.origOptions.byweekday | ||
var days = String(byweekday) | ||
this.byweekday = { | ||
allWeeks:byweekday.filter(function (weekday) { | ||
return !Boolean(weekday.n); | ||
}), | ||
someWeeks:byweekday.filter(function (weekday) { | ||
return Boolean(weekday.n); | ||
}), | ||
isWeekdays:( | ||
days.indexOf('MO') != -1 && | ||
days.indexOf('TU') != -1 && | ||
days.indexOf('WE') != -1 && | ||
days.indexOf('TH') != -1 && | ||
days.indexOf('FR') != -1 && | ||
days.indexOf('SA') == -1 && | ||
days.indexOf('SU') == -1 | ||
) | ||
}; | ||
allWeeks: byweekday.filter(function (weekday) { | ||
return !Boolean(weekday.n) | ||
}), | ||
someWeeks: byweekday.filter(function (weekday) { | ||
return Boolean(weekday.n) | ||
}), | ||
isWeekdays: ( | ||
days.indexOf('MO') !== -1 && | ||
days.indexOf('TU') !== -1 && | ||
days.indexOf('WE') !== -1 && | ||
days.indexOf('TH') !== -1 && | ||
days.indexOf('FR') !== -1 && | ||
days.indexOf('SA') === -1 && | ||
days.indexOf('SU') === -1 | ||
) | ||
} | ||
var sortWeekDays = function (a, b) { | ||
return a.weekday - b.weekday | ||
} | ||
var sortWeekDays = function(a, b) { | ||
return a.weekday - b.weekday; | ||
}; | ||
this.byweekday.allWeeks.sort(sortWeekDays) | ||
this.byweekday.someWeeks.sort(sortWeekDays) | ||
this.byweekday.allWeeks.sort(sortWeekDays); | ||
this.byweekday.someWeeks.sort(sortWeekDays); | ||
if (!this.byweekday.allWeeks.length) { | ||
this.byweekday.allWeeks = null; | ||
} | ||
if (!this.byweekday.someWeeks.length) { | ||
this.byweekday.someWeeks = null; | ||
} | ||
if (!this.byweekday.allWeeks.length) this.byweekday.allWeeks = null | ||
if (!this.byweekday.someWeeks.length) this.byweekday.someWeeks = null | ||
} else { | ||
this.byweekday = null | ||
} | ||
} | ||
else { | ||
this.byweekday = null; | ||
} | ||
}; | ||
var common = [ | ||
'count', 'until', 'interval', | ||
'byweekday', 'bymonthday', 'bymonth' | ||
] | ||
ToText.IMPLEMENTED = [] | ||
ToText.IMPLEMENTED[RRule.HOURLY] = common | ||
ToText.IMPLEMENTED[RRule.DAILY] = ['byhour'].concat(common) | ||
ToText.IMPLEMENTED[RRule.WEEKLY] = common | ||
ToText.IMPLEMENTED[RRule.MONTHLY] = common | ||
ToText.IMPLEMENTED[RRule.YEARLY] = ['byweekno', 'byyearday'].concat(common) | ||
/** | ||
* Test whether the rrule can be fully converted to text. | ||
* @param {RRule} rrule | ||
* @return {Boolean} | ||
*/ | ||
ToText.isFullyConvertible = function (rrule) { | ||
var canConvert = true | ||
ToText.IMPLEMENTED = []; | ||
var common = [ | ||
'count', 'until', 'interval', | ||
'byweekday', 'bymonthday', 'bymonth' | ||
]; | ||
ToText.IMPLEMENTED[RRule.DAILY] = common; | ||
ToText.IMPLEMENTED[RRule.WEEKLY] = common; | ||
ToText.IMPLEMENTED[RRule.MONTHLY] = common; | ||
ToText.IMPLEMENTED[RRule.YEARLY] = ['byweekno', 'byyearday'].concat(common); | ||
if (!(rrule.options.freq in ToText.IMPLEMENTED)) return false | ||
if (rrule.origOptions.until && rrule.origOptions.count) return false | ||
/** | ||
* Test whether the rrule can be fully converted to text. | ||
* @param {RRule} rrule | ||
* @return {Boolean} | ||
*/ | ||
ToText.isFullyConvertible = function(rrule) { | ||
var canConvert = true; | ||
for (var key in rrule.origOptions) { | ||
if (contains(['dtstart', 'wkst', 'freq'], key)) return true | ||
if (!contains(ToText.IMPLEMENTED[rrule.options.freq], key)) return false | ||
} | ||
if (!(rrule.options.freq in ToText.IMPLEMENTED)) { | ||
return false; | ||
return canConvert | ||
} | ||
if (rrule.origOptions.until && rrule.origOptions.count) { | ||
return false; | ||
} | ||
for (var key in rrule.origOptions) { | ||
if (contains(['dtstart', 'wkst', 'freq'], key)) { | ||
return true; | ||
} | ||
if (!contains(ToText.IMPLEMENTED[rrule.options.freq], key)) { | ||
canConvert = false; | ||
return false; | ||
} | ||
} | ||
return canConvert; | ||
}; | ||
ToText.prototype = { | ||
constructor: ToText, | ||
isFullyConvertible: function () { | ||
return ToText.isFullyConvertible(this.rrule) | ||
}, | ||
ToText.prototype = { | ||
/** | ||
* Perform the conversion. Only some of the frequencies are supported. | ||
* If some of the rrule's options aren't supported, they'll | ||
* be omitted from the output an "(~ approximate)" will be appended. | ||
* @return {*} | ||
*/ | ||
toString: function () { | ||
var gettext = this.gettext | ||
if (!(this.options.freq in ToText.IMPLEMENTED)) { | ||
return gettext('RRule error: Unable to fully convert this rrule to text') | ||
} | ||
isFullyConvertible: function() { | ||
return ToText.isFullyConvertible(this.rrule); | ||
}, | ||
this.text = [gettext('every')] | ||
this[RRule.FREQUENCIES[this.options.freq]]() | ||
if (this.options.until) { | ||
this.add(gettext('until')) | ||
var until = this.options.until | ||
this.add(this.language.monthNames[until.getMonth()]) | ||
.add(until.getDate() + ',') | ||
.add(until.getFullYear()) | ||
} else if (this.options.count) { | ||
this.add(gettext('for')) | ||
.add(this.options.count) | ||
.add(this.plural(this.options.count) | ||
? gettext('times') : gettext('time')) | ||
} | ||
/** | ||
* Perform the conversion. Only some of the frequencies are supported. | ||
* If some of the rrule's options aren't supported, they'll | ||
* be omitted from the output an "(~ approximate)" will be appended. | ||
* @return {*} | ||
*/ | ||
toString: function() { | ||
if (!this.isFullyConvertible()) this.add(gettext('(~ approximate)')) | ||
var gettext = this.gettext; | ||
return this.text.join('') | ||
}, | ||
if (!(this.options.freq in ToText.IMPLEMENTED)) { | ||
return gettext( | ||
'RRule error: Unable to fully convert this rrule to text'); | ||
} | ||
HOURLY: function () { | ||
var gettext = this.gettext | ||
this.text = [gettext('every')]; | ||
if (this.options.interval !== 1) this.add(this.options.interval) | ||
this[RRule.FREQUENCIES[this.options.freq]](); | ||
this.add(this.plural(this.options.interval) | ||
? gettext('hours') : gettext('hour')) | ||
}, | ||
if (this.options.until) { | ||
this.add(gettext('until')); | ||
var until = this.options.until; | ||
this.add(this.language.monthNames[until.getMonth()]) | ||
.add(until.getDate() + ',') | ||
.add(until.getFullYear()); | ||
} else if (this.options.count) { | ||
this.add(gettext('for')) | ||
.add(this.options.count) | ||
.add(this.plural(this.options.count) | ||
? gettext('times') | ||
: gettext('time')); | ||
} | ||
DAILY: function () { | ||
var gettext = this.gettext | ||
if (!this.isFullyConvertible()) { | ||
this.add(gettext('(~ approximate)')); | ||
} | ||
return this.text.join(''); | ||
}, | ||
if (this.options.interval !== 1) this.add(this.options.interval) | ||
DAILY: function() { | ||
var gettext = this.gettext; | ||
if (this.options.interval != 1) { | ||
this.add(this.options.interval); | ||
} | ||
if (this.byweekday && this.byweekday.isWeekdays) { | ||
this.add(this.plural(this.options.interval) | ||
? gettext('weekdays') | ||
: gettext('weekday')); | ||
this.add(this.plural(this.options.interval) | ||
? gettext('weekdays') : gettext('weekday')) | ||
} else { | ||
this.add(this.plural(this.options.interval) | ||
? gettext('days') : gettext('day')); | ||
this.add(this.plural(this.options.interval) | ||
? gettext('days') : gettext('day')) | ||
} | ||
if (this.origOptions.bymonth) { | ||
this.add(gettext('in')); | ||
this._bymonth(); | ||
this.add(gettext('in')) | ||
this._bymonth() | ||
} | ||
if (this.bymonthday) { | ||
this._bymonthday(); | ||
this._bymonthday() | ||
} else if (this.byweekday) { | ||
this._byweekday(); | ||
this._byweekday() | ||
} else if (this.origOptions.byhour) { | ||
this._byhour() | ||
} | ||
}, | ||
}, | ||
WEEKLY: function () { | ||
var gettext = this.gettext | ||
WEEKLY: function() { | ||
var gettext = this.gettext; | ||
if (this.options.interval != 1) { | ||
this.add(this.options.interval).add( | ||
this.plural(this.options.interval) | ||
? gettext('weeks') | ||
: gettext('week')); | ||
if (this.options.interval !== 1) { | ||
this.add(this.options.interval) | ||
.add(this.plural(this.options.interval) | ||
? gettext('weeks') : gettext('week')) | ||
} | ||
if (this.byweekday && this.byweekday.isWeekdays) { | ||
if (this.options.interval == 1) { | ||
this.add(this.plural(this.options.interval) | ||
? gettext('weekdays') | ||
: gettext('weekday')); | ||
} else { | ||
this.add(gettext('on')).add(gettext('weekdays')); | ||
} | ||
if (this.options.interval === 1) { | ||
this.add(this.plural(this.options.interval) | ||
? gettext('weekdays') : gettext('weekday')) | ||
} else { | ||
this.add(gettext('on')).add(gettext('weekdays')) | ||
} | ||
} else { | ||
if (this.options.interval === 1) this.add(gettext('week')) | ||
if (this.options.interval == 1) { | ||
this.add(gettext('week')) | ||
} | ||
if (this.origOptions.bymonth) { | ||
this.add(gettext('in')) | ||
this._bymonth() | ||
} | ||
if (this.origOptions.bymonth) { | ||
this.add(gettext('in')); | ||
this._bymonth(); | ||
} | ||
if (this.bymonthday) { | ||
this._bymonthday(); | ||
} else if (this.byweekday) { | ||
this._byweekday(); | ||
} | ||
if (this.bymonthday) { | ||
this._bymonthday() | ||
} else if (this.byweekday) { | ||
this._byweekday() | ||
} | ||
} | ||
}, | ||
}, | ||
MONTHLY: function () { | ||
var gettext = this.gettext | ||
MONTHLY: function() { | ||
var gettext = this.gettext; | ||
if (this.origOptions.bymonth) { | ||
if (this.options.interval != 1) { | ||
this.add(this.options.interval).add(gettext('months')); | ||
if (this.plural(this.options.interval)) { | ||
this.add(gettext('in')); | ||
} | ||
} else { | ||
//this.add(gettext('MONTH')); | ||
} | ||
this._bymonth(); | ||
if (this.options.interval !== 1) { | ||
this.add(this.options.interval).add(gettext('months')) | ||
if (this.plural(this.options.interval)) this.add(gettext('in')) | ||
} else { | ||
// this.add(gettext('MONTH')) | ||
} | ||
this._bymonth() | ||
} else { | ||
if (this.options.interval != 1) { | ||
this.add(this.options.interval); | ||
} | ||
this.add(this.plural(this.options.interval) | ||
? gettext('months') | ||
: gettext('month')); | ||
if (this.options.interval !== 1) this.add(this.options.interval) | ||
this.add(this.plural(this.options.interval) | ||
? gettext('months') : gettext('month')) | ||
} | ||
if (this.bymonthday) { | ||
this._bymonthday(); | ||
this._bymonthday() | ||
} else if (this.byweekday && this.byweekday.isWeekdays) { | ||
this.add(gettext('on')).add(gettext('weekdays')); | ||
this.add(gettext('on')).add(gettext('weekdays')) | ||
} else if (this.byweekday) { | ||
this._byweekday(); | ||
this._byweekday() | ||
} | ||
}, | ||
}, | ||
YEARLY: function() { | ||
var gettext = this.gettext; | ||
YEARLY: function () { | ||
var gettext = this.gettext | ||
if (this.origOptions.bymonth) { | ||
if (this.options.interval != 1) { | ||
this.add(this.options.interval); | ||
this.add(gettext('years')); | ||
} else { | ||
// this.add(gettext('YEAR')); | ||
} | ||
this._bymonth(); | ||
if (this.options.interval !== 1) { | ||
this.add(this.options.interval) | ||
this.add(gettext('years')) | ||
} else { | ||
// this.add(gettext('YEAR')) | ||
} | ||
this._bymonth() | ||
} else { | ||
if (this.options.interval != 1) { | ||
this.add(this.options.interval); | ||
} | ||
this.add(this.plural(this.options.interval) | ||
? gettext('years') | ||
: gettext('year')); | ||
if (this.options.interval !== 1) this.add(this.options.interval) | ||
this.add(this.plural(this.options.interval) | ||
? gettext('years') : gettext('year')) | ||
} | ||
if (this.bymonthday) { | ||
this._bymonthday(); | ||
this._bymonthday() | ||
} else if (this.byweekday) { | ||
this._byweekday(); | ||
this._byweekday() | ||
} | ||
if (this.options.byyearday) { | ||
this.add(gettext('on the')) | ||
.add(this.list(this.options.byyearday, | ||
this.nth, gettext('and'))) | ||
.add(gettext('day')); | ||
this.add(gettext('on the')) | ||
.add(this.list(this.options.byyearday, this.nth, gettext('and'))) | ||
.add(gettext('day')) | ||
} | ||
if (this.options.byweekno) { | ||
this.add(gettext('in')) | ||
.add(this.plural(this.options.byweekno.length) | ||
? gettext('weeks') : gettext('week')) | ||
.add(this.list(this.options.byweekno, null, gettext('and'))); | ||
this.add(gettext('in')) | ||
.add(this.plural(this.options.byweekno.length) ? gettext('weeks') : gettext('week')) | ||
.add(this.list(this.options.byweekno, null, gettext('and'))) | ||
} | ||
}, | ||
}, | ||
_bymonthday: function() { | ||
var gettext = this.gettext; | ||
_bymonthday: function () { | ||
var gettext = this.gettext | ||
if (this.byweekday && this.byweekday.allWeeks) { | ||
this.add(gettext('on')) | ||
.add(this.list(this.byweekday.allWeeks, | ||
this.weekdaytext, gettext('or'))) | ||
.add(gettext('the')) | ||
.add(this.list(this.bymonthday, this.nth, gettext('or'))); | ||
this.add(gettext('on')) | ||
.add(this.list(this.byweekday.allWeeks, this.weekdaytext, gettext('or'))) | ||
.add(gettext('the')) | ||
.add(this.list(this.bymonthday, this.nth, gettext('or'))) | ||
} else { | ||
this.add(gettext('on the')) | ||
.add(this.list(this.bymonthday, this.nth, gettext('and'))); | ||
this.add(gettext('on the')) | ||
.add(this.list(this.bymonthday, this.nth, gettext('and'))) | ||
} | ||
//this.add(gettext('DAY')); | ||
}, | ||
// this.add(gettext('DAY')) | ||
}, | ||
_byweekday: function() { | ||
var gettext = this.gettext; | ||
_byweekday: function () { | ||
var gettext = this.gettext | ||
if (this.byweekday.allWeeks && !this.byweekday.isWeekdays) { | ||
this.add(gettext('on')) | ||
.add(this.list(this.byweekday.allWeeks, this.weekdaytext)); | ||
this.add(gettext('on')) | ||
.add(this.list(this.byweekday.allWeeks, this.weekdaytext)) | ||
} | ||
if (this.byweekday.someWeeks) { | ||
if (this.byweekday.allWeeks) this.add(gettext('and')) | ||
if (this.byweekday.allWeeks) { | ||
this.add(gettext('and')); | ||
} | ||
this.add(gettext('on the')) | ||
.add(this.list(this.byweekday.someWeeks, | ||
this.weekdaytext, | ||
gettext('and'))); | ||
this.add(gettext('on the')) | ||
.add(this.list(this.byweekday.someWeeks, this.weekdaytext, gettext('and'))) | ||
} | ||
}, | ||
}, | ||
_bymonth: function() { | ||
this.add(this.list(this.options.bymonth, | ||
this.monthtext, | ||
this.gettext('and'))); | ||
}, | ||
_byhour: function () { | ||
var gettext = this.gettext | ||
nth: function(n) { | ||
var nth, npos, gettext = this.gettext; | ||
this.add(gettext('at')) | ||
.add(this.list(this.origOptions.byhour, null, gettext('and'))) | ||
}, | ||
if (n == -1) { | ||
return gettext('last'); | ||
} | ||
_bymonth: function () { | ||
this.add(this.list(this.options.bymonth, this.monthtext, this.gettext('and'))) | ||
}, | ||
npos = Math.abs(n); | ||
nth: function (n) { | ||
var nth, npos | ||
var gettext = this.gettext | ||
switch(npos) { | ||
case 1: | ||
case 21: | ||
case 31: | ||
nth = npos + gettext('st'); | ||
break; | ||
case 2: | ||
case 22: | ||
nth = npos + gettext('nd'); | ||
break; | ||
case 3: | ||
case 23: | ||
nth = npos + gettext('rd'); | ||
break; | ||
default: | ||
nth = npos + gettext('th'); | ||
if (n === -1) return gettext('last') | ||
npos = Math.abs(n) | ||
switch (npos) { | ||
case 1: | ||
case 21: | ||
case 31: | ||
nth = npos + gettext('st') | ||
break | ||
case 2: | ||
case 22: | ||
nth = npos + gettext('nd') | ||
break | ||
case 3: | ||
case 23: | ||
nth = npos + gettext('rd') | ||
break | ||
default: | ||
nth = npos + gettext('th') | ||
} | ||
return n < 0 ? nth + ' ' + gettext('last') : nth; | ||
return n < 0 ? nth + ' ' + gettext('last') : nth | ||
}, | ||
}, | ||
monthtext: function (m) { | ||
return this.language.monthNames[m - 1] | ||
}, | ||
monthtext: function(m) { | ||
return this.language.monthNames[m - 1]; | ||
}, | ||
weekdaytext: function (wday) { | ||
var weekday = typeof wday === 'number' ? wday : wday.getJsWeekday() | ||
return (wday.n ? this.nth(wday.n) + ' ' : '') + | ||
this.language.dayNames[weekday] | ||
}, | ||
weekdaytext: function(wday) { | ||
return (wday.n ? this.nth(wday.n) + ' ' : '') | ||
+ this.language.dayNames[wday.getJsWeekday()]; | ||
}, | ||
plural: function (n) { | ||
return n % 100 !== 1 | ||
}, | ||
plural: function(n) { | ||
return n % 100 != 1; | ||
}, | ||
add: function (s) { | ||
this.text.push(' ') | ||
this.text.push(s) | ||
return this | ||
}, | ||
add: function(s) { | ||
this.text.push(' '); | ||
this.text.push(s); | ||
return this; | ||
}, | ||
list: function (arr, callback, finalDelim, delim) { | ||
var delimJoin = function (array, delimiter, finalDelimiter) { | ||
var list = '' | ||
list: function(arr, callback, finalDelim, delim) { | ||
var delimJoin = function (array, delimiter, finalDelimiter) { | ||
var list = ''; | ||
for(var i = 0; i < array.length; i++) { | ||
if (i != 0) { | ||
if (i == array.length - 1) { | ||
list += ' ' + finalDelimiter + ' '; | ||
} else { | ||
list += delimiter + ' '; | ||
} | ||
} | ||
list += array[i]; | ||
for (var i = 0; i < array.length; i++) { | ||
if (i !== 0) { | ||
if (i === array.length - 1) { | ||
list += ' ' + finalDelimiter + ' ' | ||
} else { | ||
list += delimiter + ' ' | ||
} | ||
} | ||
return list; | ||
}; | ||
list += array[i] | ||
} | ||
return list | ||
} | ||
delim = delim || ','; | ||
callback = callback || (function(o){return o;}); | ||
var self = this; | ||
var realCallback = function(arg) { | ||
return callback.call(self, arg); | ||
}; | ||
delim = delim || ',' | ||
callback = callback || function (o) { | ||
return o | ||
} | ||
var self = this | ||
var realCallback = function (arg) { | ||
return callback.call(self, arg) | ||
} | ||
if (finalDelim) { | ||
return delimJoin(arr.map(realCallback), delim, finalDelim); | ||
return delimJoin(arr.map(realCallback), delim, finalDelim) | ||
} else { | ||
return arr.map(realCallback).join(delim + ' '); | ||
return arr.map(realCallback).join(delim + ' ') | ||
} | ||
} | ||
} | ||
}; | ||
//============================================================================= | ||
// fromText | ||
//============================================================================= | ||
/** | ||
* Will be able to convert some of the below described rules from | ||
* text format to a rule object. | ||
* | ||
* | ||
* RULES | ||
* | ||
* Every ([n]) | ||
* day(s) | ||
* | [weekday], ..., (and) [weekday] | ||
* | weekday(s) | ||
* | week(s) | ||
* | month(s) | ||
* | [month], ..., (and) [month] | ||
* | year(s) | ||
* | ||
* | ||
* Plus 0, 1, or multiple of these: | ||
* | ||
* on [weekday], ..., (or) [weekday] the [monthday], [monthday], ... (or) [monthday] | ||
* | ||
* on [weekday], ..., (and) [weekday] | ||
* | ||
* on the [monthday], [monthday], ... (and) [monthday] (day of the month) | ||
* | ||
* on the [nth-weekday], ..., (and) [nth-weekday] (of the month/year) | ||
* | ||
* | ||
* Plus 0 or 1 of these: | ||
* | ||
* for [n] time(s) | ||
* | ||
* until [date] | ||
* | ||
* Plus (.) | ||
* | ||
* | ||
* Definitely no supported for parsing: | ||
* | ||
* (for year): | ||
* in week(s) [n], ..., (and) [n] | ||
* | ||
* on the [yearday], ..., (and) [n] day of the year | ||
* on day [yearday], ..., (and) [n] | ||
* | ||
* | ||
* NON-TERMINALS | ||
* | ||
* [n]: 1, 2 ..., one, two, three .. | ||
* [month]: January, February, March, April, May, ... December | ||
* [weekday]: Monday, ... Sunday | ||
* [nth-weekday]: first [weekday], 2nd [weekday], ... last [weekday], ... | ||
* [monthday]: first, 1., 2., 1st, 2nd, second, ... 31st, last day, 2nd last day, .. | ||
* [date]: | ||
* [month] (0-31(,) ([year])), | ||
* (the) 0-31.(1-12.([year])), | ||
* (the) 0-31/(1-12/([year])), | ||
* [weekday] | ||
* | ||
* [year]: 0000, 0001, ... 01, 02, .. | ||
* | ||
* Definitely not supported for parsing: | ||
* | ||
* [yearday]: first, 1., 2., 1st, 2nd, second, ... 366th, last day, 2nd last day, .. | ||
* | ||
* @param {String} text | ||
* @return {Object, Boolean} the rule, or null. | ||
*/ | ||
var fromText = function(text, language) { | ||
return new RRule(parseText(text, language)) | ||
}; | ||
var parseText = function(text, language) { | ||
var ttr = new Parser((language || ENGLISH).tokens); | ||
if(!ttr.start(text)) { | ||
return null; | ||
// ============================================================================= | ||
// fromText | ||
// ============================================================================= | ||
/** | ||
* Will be able to convert some of the below described rules from | ||
* text format to a rule object. | ||
* | ||
* | ||
* RULES | ||
* | ||
* Every ([n]) | ||
* day(s) | ||
* | [weekday], ..., (and) [weekday] | ||
* | weekday(s) | ||
* | week(s) | ||
* | month(s) | ||
* | [month], ..., (and) [month] | ||
* | year(s) | ||
* | ||
* | ||
* Plus 0, 1, or multiple of these: | ||
* | ||
* on [weekday], ..., (or) [weekday] the [monthday], [monthday], ... (or) [monthday] | ||
* | ||
* on [weekday], ..., (and) [weekday] | ||
* | ||
* on the [monthday], [monthday], ... (and) [monthday] (day of the month) | ||
* | ||
* on the [nth-weekday], ..., (and) [nth-weekday] (of the month/year) | ||
* | ||
* | ||
* Plus 0 or 1 of these: | ||
* | ||
* for [n] time(s) | ||
* | ||
* until [date] | ||
* | ||
* Plus (.) | ||
* | ||
* | ||
* Definitely no supported for parsing: | ||
* | ||
* (for year): | ||
* in week(s) [n], ..., (and) [n] | ||
* | ||
* on the [yearday], ..., (and) [n] day of the year | ||
* on day [yearday], ..., (and) [n] | ||
* | ||
* | ||
* NON-TERMINALS | ||
* | ||
* [n]: 1, 2 ..., one, two, three .. | ||
* [month]: January, February, March, April, May, ... December | ||
* [weekday]: Monday, ... Sunday | ||
* [nth-weekday]: first [weekday], 2nd [weekday], ... last [weekday], ... | ||
* [monthday]: first, 1., 2., 1st, 2nd, second, ... 31st, last day, 2nd last day, .. | ||
* [date]: | ||
* [month] (0-31(,) ([year])), | ||
* (the) 0-31.(1-12.([year])), | ||
* (the) 0-31/(1-12/([year])), | ||
* [weekday] | ||
* | ||
* [year]: 0000, 0001, ... 01, 02, .. | ||
* | ||
* Definitely not supported for parsing: | ||
* | ||
* [yearday]: first, 1., 2., 1st, 2nd, second, ... 366th, last day, 2nd last day, .. | ||
* | ||
* @param {String} text | ||
* @return {Object, Boolean} the rule, or null. | ||
*/ | ||
var fromText = function (text, language) { | ||
return new RRule(parseText(text, language)) | ||
} | ||
var options = {}; | ||
var parseText = function (text, language) { | ||
var options = {} | ||
var ttr = new Parser((language || ENGLISH).tokens) | ||
S(); | ||
return options; | ||
if (!ttr.start(text)) return null | ||
function S() { | ||
ttr.expect('every'); | ||
S() | ||
return options | ||
function S () { | ||
// every [n] | ||
var n; | ||
if(n = ttr.accept('number')) | ||
options.interval = parseInt(n[0]); | ||
var n | ||
if(ttr.isDone()) | ||
throw new Error('Unexpected end'); | ||
ttr.expect('every') | ||
if ((n = ttr.accept('number'))) options.interval = parseInt(n[0], 10) | ||
if (ttr.isDone()) throw new Error('Unexpected end') | ||
switch(ttr.symbol) { | ||
case 'day(s)': | ||
options.freq = RRule.DAILY; | ||
switch (ttr.symbol) { | ||
case 'day(s)': | ||
options.freq = RRule.DAILY | ||
if (ttr.nextSymbol()) { | ||
ON(); | ||
F(); | ||
AT() | ||
F() | ||
} | ||
break; | ||
break | ||
// FIXME Note: every 2 weekdays != every two weeks on weekdays. | ||
// DAILY on weekdays is not a valid rule | ||
case 'weekday(s)': | ||
options.freq = RRule.WEEKLY; | ||
// FIXME Note: every 2 weekdays != every two weeks on weekdays. | ||
// DAILY on weekdays is not a valid rule | ||
case 'weekday(s)': | ||
options.freq = RRule.WEEKLY | ||
options.byweekday = [ | ||
RRule.MO, | ||
RRule.TU, | ||
RRule.WE, | ||
RRule.TH, | ||
RRule.FR | ||
]; | ||
ttr.nextSymbol(); | ||
F(); | ||
break; | ||
RRule.MO, | ||
RRule.TU, | ||
RRule.WE, | ||
RRule.TH, | ||
RRule.FR | ||
] | ||
ttr.nextSymbol() | ||
F() | ||
break | ||
case 'week(s)': | ||
options.freq = RRule.WEEKLY; | ||
case 'week(s)': | ||
options.freq = RRule.WEEKLY | ||
if (ttr.nextSymbol()) { | ||
ON(); | ||
F(); | ||
ON() | ||
F() | ||
} | ||
break; | ||
break | ||
case 'month(s)': | ||
options.freq = RRule.MONTHLY; | ||
case 'hour(s)': | ||
options.freq = RRule.HOURLY | ||
if (ttr.nextSymbol()) { | ||
ON(); | ||
F(); | ||
ON() | ||
F() | ||
} | ||
break; | ||
break | ||
case 'year(s)': | ||
options.freq = RRule.YEARLY; | ||
case 'month(s)': | ||
options.freq = RRule.MONTHLY | ||
if (ttr.nextSymbol()) { | ||
ON(); | ||
F(); | ||
ON() | ||
F() | ||
} | ||
break; | ||
break | ||
case 'monday': | ||
case 'tuesday': | ||
case 'wednesday': | ||
case 'thursday': | ||
case 'friday': | ||
case 'saturday': | ||
case 'sunday': | ||
options.freq = RRule.WEEKLY; | ||
options.byweekday = [RRule[ttr.symbol.substr(0, 2).toUpperCase()]]; | ||
case 'year(s)': | ||
options.freq = RRule.YEARLY | ||
if (ttr.nextSymbol()) { | ||
ON() | ||
F() | ||
} | ||
break | ||
if(!ttr.nextSymbol()) | ||
return; | ||
case 'monday': | ||
case 'tuesday': | ||
case 'wednesday': | ||
case 'thursday': | ||
case 'friday': | ||
case 'saturday': | ||
case 'sunday': | ||
options.freq = RRule.WEEKLY | ||
options.byweekday = [RRule[ttr.symbol.substr(0, 2).toUpperCase()]] | ||
if (!ttr.nextSymbol()) return | ||
// TODO check for duplicates | ||
while (ttr.accept('comma')) { | ||
if(ttr.isDone()) | ||
throw new Error('Unexpected end'); | ||
if (ttr.isDone()) throw new Error('Unexpected end') | ||
var wkd; | ||
if(!(wkd = decodeWKD())) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol | ||
+ ', expected weekday'); | ||
} | ||
var wkd | ||
if (!(wkd = decodeWKD())) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol + ', expected weekday') | ||
} | ||
options.byweekday.push(RRule[wkd]); | ||
ttr.nextSymbol(); | ||
options.byweekday.push(RRule[wkd]) | ||
ttr.nextSymbol() | ||
} | ||
MDAYs(); | ||
F(); | ||
break; | ||
MDAYs() | ||
F() | ||
break | ||
case 'january': | ||
case 'february': | ||
case 'march': | ||
case 'april': | ||
case 'may': | ||
case 'june': | ||
case 'july': | ||
case 'august': | ||
case 'september': | ||
case 'october': | ||
case 'november': | ||
case 'december': | ||
options.freq = RRule.YEARLY; | ||
options.bymonth = [decodeM()]; | ||
case 'january': | ||
case 'february': | ||
case 'march': | ||
case 'april': | ||
case 'may': | ||
case 'june': | ||
case 'july': | ||
case 'august': | ||
case 'september': | ||
case 'october': | ||
case 'november': | ||
case 'december': | ||
options.freq = RRule.YEARLY | ||
options.bymonth = [decodeM()] | ||
if(!ttr.nextSymbol()) | ||
return; | ||
if (!ttr.nextSymbol()) return | ||
// TODO check for duplicates | ||
while (ttr.accept('comma')) { | ||
if(ttr.isDone()) | ||
throw new Error('Unexpected end'); | ||
if (ttr.isDone()) throw new Error('Unexpected end') | ||
var m; | ||
if(!(m = decodeM())) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol | ||
+ ', expected month'); | ||
} | ||
var m | ||
if (!(m = decodeM())) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol + ', expected month') | ||
} | ||
options.bymonth.push(m); | ||
ttr.nextSymbol(); | ||
options.bymonth.push(m) | ||
ttr.nextSymbol() | ||
} | ||
ON(); | ||
F(); | ||
break; | ||
ON() | ||
F() | ||
break | ||
default: | ||
throw new Error('Unknown symbol'); | ||
default: | ||
throw new Error('Unknown symbol') | ||
} | ||
} | ||
} | ||
function ON() { | ||
function ON () { | ||
var on = ttr.accept('on') | ||
var the = ttr.accept('the') | ||
if (!(on || the)) return | ||
var on = ttr.accept('on'); | ||
var the = ttr.accept('the'); | ||
if(!(on || the)) { | ||
return; | ||
} | ||
do { | ||
var nth, wkd, m | ||
var nth, wkd, m; | ||
// nth <weekday> | <weekday> | ||
if ((nth = decodeNTH())) { | ||
// ttr.nextSymbol() | ||
// nth <weekday> | <weekday> | ||
if(nth = decodeNTH()) { | ||
//ttr.nextSymbol(); | ||
if (wkd = decodeWKD()) { | ||
ttr.nextSymbol(); | ||
if (!options.byweekday) { | ||
options.byweekday = []; | ||
} | ||
options.byweekday.push(RRule[wkd].nth(nth)); | ||
} else { | ||
if(!options.bymonthday) { | ||
options.bymonthday = []; | ||
} | ||
options.bymonthday.push(nth); | ||
ttr.accept('day(s)'); | ||
} | ||
// <weekday> | ||
} else if(wkd = decodeWKD()) { | ||
ttr.nextSymbol(); | ||
if(!options.byweekday) | ||
options.byweekday = []; | ||
options.byweekday.push(RRule[wkd]); | ||
} else if(ttr.symbol == 'weekday(s)') { | ||
ttr.nextSymbol(); | ||
if(!options.byweekday) | ||
options.byweekday = []; | ||
options.byweekday.push(RRule.MO); | ||
options.byweekday.push(RRule.TU); | ||
options.byweekday.push(RRule.WE); | ||
options.byweekday.push(RRule.TH); | ||
options.byweekday.push(RRule.FR); | ||
} else if(ttr.symbol == 'week(s)') { | ||
ttr.nextSymbol(); | ||
var n; | ||
if(!(n = ttr.accept('number'))) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol | ||
+ ', expected week number'); | ||
} | ||
options.byweekno = [n[0]]; | ||
while(ttr.accept('comma')) { | ||
if(!(n = ttr.accept('number'))) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol | ||
+ '; expected monthday'); | ||
} | ||
options.byweekno.push(n[0]); | ||
} | ||
} else if(m = decodeM()) { | ||
ttr.nextSymbol(); | ||
if(!options.bymonth) | ||
options.bymonth = []; | ||
options.bymonth.push(m); | ||
if ((wkd = decodeWKD())) { | ||
ttr.nextSymbol() | ||
if (!options.byweekday) options.byweekday = [] | ||
options.byweekday.push(RRule[wkd].nth(nth)) | ||
} else { | ||
return; | ||
if (!options.bymonthday) options.bymonthday = [] | ||
options.bymonthday.push(nth) | ||
ttr.accept('day(s)') | ||
} | ||
// <weekday> | ||
} else if ((wkd = decodeWKD())) { | ||
ttr.nextSymbol() | ||
if (!options.byweekday) options.byweekday = [] | ||
options.byweekday.push(RRule[wkd]) | ||
} else if (ttr.symbol === 'weekday(s)') { | ||
ttr.nextSymbol() | ||
if (!options.byweekday) options.byweekday = [] | ||
options.byweekday.push(RRule.MO) | ||
options.byweekday.push(RRule.TU) | ||
options.byweekday.push(RRule.WE) | ||
options.byweekday.push(RRule.TH) | ||
options.byweekday.push(RRule.FR) | ||
} else if (ttr.symbol === 'week(s)') { | ||
ttr.nextSymbol() | ||
var n | ||
if (!(n = ttr.accept('number'))) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol + ', expected week number') | ||
} | ||
options.byweekno = [n[0]] | ||
while (ttr.accept('comma')) { | ||
if (!(n = ttr.accept('number'))) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol + '; expected monthday') | ||
} | ||
options.byweekno.push(n[0]) | ||
} | ||
} else if ((m = decodeM())) { | ||
ttr.nextSymbol() | ||
if (!options.bymonth) options.bymonth = [] | ||
options.bymonth.push(m) | ||
} else { | ||
return | ||
} | ||
} while (ttr.accept('comma') || ttr.accept('the') || ttr.accept('on')) | ||
} | ||
} while (ttr.accept('comma') || ttr.accept('the') || ttr.accept('on')); | ||
} | ||
function AT () { | ||
var at = ttr.accept('at') | ||
if (!at) return | ||
function decodeM() { | ||
switch(ttr.symbol) { | ||
case 'january': | ||
return 1; | ||
case 'february': | ||
return 2; | ||
case 'march': | ||
return 3; | ||
case 'april': | ||
return 4; | ||
case 'may': | ||
return 5; | ||
case 'june': | ||
return 6; | ||
case 'july': | ||
return 7; | ||
case 'august': | ||
return 8; | ||
case 'september': | ||
return 9; | ||
case 'october': | ||
return 10; | ||
case 'november': | ||
return 11; | ||
case 'december': | ||
return 12; | ||
default: | ||
return false; | ||
} | ||
} | ||
do { | ||
var n | ||
if (!(n = ttr.accept('number'))) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol + ', expected hour') | ||
} | ||
options.byhour = [n[0]] | ||
while (ttr.accept('comma')) { | ||
if (!(n = ttr.accept('number'))) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol + '; expected hour') | ||
} | ||
options.byhour.push(n[0]) | ||
} | ||
} while (ttr.accept('comma') || ttr.accept('at')) | ||
} | ||
function decodeWKD() { | ||
switch(ttr.symbol) { | ||
case 'monday': | ||
case 'tuesday': | ||
case 'wednesday': | ||
case 'thursday': | ||
case 'friday': | ||
case 'saturday': | ||
case 'sunday': | ||
return ttr.symbol.substr(0, 2).toUpperCase(); | ||
break; | ||
default: | ||
return false; | ||
function decodeM () { | ||
switch (ttr.symbol) { | ||
case 'january': | ||
return 1 | ||
case 'february': | ||
return 2 | ||
case 'march': | ||
return 3 | ||
case 'april': | ||
return 4 | ||
case 'may': | ||
return 5 | ||
case 'june': | ||
return 6 | ||
case 'july': | ||
return 7 | ||
case 'august': | ||
return 8 | ||
case 'september': | ||
return 9 | ||
case 'october': | ||
return 10 | ||
case 'november': | ||
return 11 | ||
case 'december': | ||
return 12 | ||
default: | ||
return false | ||
} | ||
} | ||
} | ||
function decodeNTH() { | ||
switch(ttr.symbol) { | ||
case 'last': | ||
ttr.nextSymbol(); | ||
return -1; | ||
case 'first': | ||
ttr.nextSymbol(); | ||
return 1; | ||
case 'second': | ||
ttr.nextSymbol(); | ||
return ttr.accept('last') ? -2 : 2; | ||
case 'third': | ||
ttr.nextSymbol(); | ||
return ttr.accept('last') ? -3 : 3; | ||
case 'nth': | ||
var v = parseInt(ttr.value[1]); | ||
if(v < -366 || v > 366) | ||
throw new Error('Nth out of range: ' + v); | ||
ttr.nextSymbol(); | ||
return ttr.accept('last') ? -v : v; | ||
default: | ||
return false; | ||
function decodeWKD () { | ||
switch (ttr.symbol) { | ||
case 'monday': | ||
case 'tuesday': | ||
case 'wednesday': | ||
case 'thursday': | ||
case 'friday': | ||
case 'saturday': | ||
case 'sunday': | ||
return ttr.symbol.substr(0, 2).toUpperCase() | ||
default: | ||
return false | ||
} | ||
} | ||
} | ||
function MDAYs() { | ||
function decodeNTH () { | ||
switch (ttr.symbol) { | ||
case 'last': | ||
ttr.nextSymbol() | ||
return -1 | ||
case 'first': | ||
ttr.nextSymbol() | ||
return 1 | ||
case 'second': | ||
ttr.nextSymbol() | ||
return ttr.accept('last') ? -2 : 2 | ||
case 'third': | ||
ttr.nextSymbol() | ||
return ttr.accept('last') ? -3 : 3 | ||
case 'nth': | ||
var v = parseInt(ttr.value[1], 10) | ||
if (v < -366 || v > 366) throw new Error('Nth out of range: ' + v) | ||
ttr.accept('on'); | ||
ttr.accept('the'); | ||
ttr.nextSymbol() | ||
return ttr.accept('last') ? -v : v | ||
var nth; | ||
if(!(nth = decodeNTH())) { | ||
return; | ||
default: | ||
return false | ||
} | ||
} | ||
options.bymonthday = [nth]; | ||
ttr.nextSymbol(); | ||
function MDAYs () { | ||
ttr.accept('on') | ||
ttr.accept('the') | ||
while(ttr.accept('comma')) { | ||
var nth | ||
if (!(nth = decodeNTH())) return | ||
if (!(nth = decodeNTH())) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol | ||
+ '; expected monthday'); | ||
} | ||
options.bymonthday = [nth] | ||
ttr.nextSymbol() | ||
options.bymonthday.push(nth); | ||
while (ttr.accept('comma')) { | ||
if (!(nth = decodeNTH())) { | ||
throw new Error('Unexpected symbol ' + ttr.symbol + '; expected monthday') | ||
} | ||
ttr.nextSymbol(); | ||
options.bymonthday.push(nth) | ||
ttr.nextSymbol() | ||
} | ||
} | ||
} | ||
function F() { | ||
function F () { | ||
if (ttr.symbol === 'until') { | ||
var date = Date.parse(ttr.text) | ||
if(ttr.symbol == 'until') { | ||
var date = Date.parse(ttr.text); | ||
if (!date) { | ||
throw new Error('Cannot parse until date:' + ttr.text); | ||
} | ||
options.until = new Date(date); | ||
} else if(ttr.accept('for')){ | ||
options.count = ttr.value[0]; | ||
ttr.expect('number'); | ||
/* ttr.expect('times') */ | ||
if (!date) throw new Error('Cannot parse until date:' + ttr.text) | ||
options.until = new Date(date) | ||
} else if (ttr.accept('for')) { | ||
options.count = ttr.value[0] | ||
ttr.expect('number') | ||
// ttr.expect('times') | ||
} | ||
} | ||
} | ||
}; | ||
// ============================================================================= | ||
// Parser | ||
// ============================================================================= | ||
//============================================================================= | ||
// Parser | ||
//============================================================================= | ||
var Parser = function (rules) { | ||
this.rules = rules | ||
} | ||
var Parser = function(rules) { | ||
this.rules = rules; | ||
}; | ||
Parser.prototype.start = function (text) { | ||
this.text = text | ||
this.done = false | ||
return this.nextSymbol() | ||
} | ||
Parser.prototype.start = function(text) { | ||
this.text = text; | ||
this.done = false; | ||
return this.nextSymbol(); | ||
}; | ||
Parser.prototype.isDone = function () { | ||
return this.done && this.symbol == null | ||
} | ||
Parser.prototype.isDone = function() { | ||
return this.done && this.symbol == null; | ||
}; | ||
Parser.prototype.nextSymbol = function () { | ||
var best, bestSymbol | ||
var p = this | ||
Parser.prototype.nextSymbol = function() { | ||
var p = this, best, bestSymbol; | ||
this.symbol = null | ||
this.value = null | ||
do { | ||
if (this.done) return false | ||
this.symbol = null; | ||
this.value = null; | ||
do { | ||
if(this.done) { | ||
return false; | ||
} | ||
var match, rule | ||
best = null | ||
for (var name in this.rules) { | ||
rule = this.rules[name] | ||
if ((match = rule.exec(p.text))) { | ||
if (best == null || match[0].length > best[0].length) { | ||
best = match | ||
bestSymbol = name | ||
} | ||
} | ||
} | ||
best = null; | ||
if (best != null) { | ||
this.text = this.text.substr(best[0].length) | ||
var match, rule; | ||
for (var name in this.rules) { | ||
rule = this.rules[name]; | ||
if(match = rule.exec(p.text)) { | ||
if(best == null || match[0].length > best[0].length) { | ||
best = match; | ||
bestSymbol = name; | ||
} | ||
} | ||
if (this.text === '') this.done = true | ||
} | ||
} | ||
if (best == null) { | ||
this.done = true | ||
this.symbol = null | ||
this.value = null | ||
return | ||
} | ||
} while (bestSymbol === 'SKIP') | ||
if(best != null) { | ||
this.text = this.text.substr(best[0].length); | ||
this.symbol = bestSymbol | ||
this.value = best | ||
return true | ||
} | ||
if(this.text == '') { | ||
this.done = true; | ||
} | ||
} | ||
Parser.prototype.accept = function (name) { | ||
if (this.symbol === name) { | ||
if (this.value) { | ||
var v = this.value | ||
this.nextSymbol() | ||
return v | ||
} | ||
if(best == null) { | ||
this.done = true; | ||
this.symbol = null; | ||
this.value = null; | ||
return; | ||
} | ||
} while(bestSymbol == 'SKIP'); | ||
this.nextSymbol() | ||
return true | ||
} | ||
this.symbol = bestSymbol; | ||
this.value = best; | ||
return true; | ||
}; | ||
return false | ||
} | ||
Parser.prototype.accept = function(name) { | ||
if(this.symbol == name) { | ||
if(this.value) { | ||
var v = this.value; | ||
this.nextSymbol(); | ||
return v; | ||
} | ||
Parser.prototype.expect = function (name) { | ||
if (this.accept(name)) return true | ||
this.nextSymbol(); | ||
return true; | ||
} | ||
throw new Error('expected ' + name + ' but found ' + this.symbol) | ||
} | ||
return false; | ||
}; | ||
// ============================================================================= | ||
// i18n | ||
// ============================================================================= | ||
Parser.prototype.expect = function(name) { | ||
if(this.accept(name)) { | ||
return true; | ||
} | ||
throw new Error('expected ' + name + ' but found ' + this.symbol); | ||
}; | ||
//============================================================================= | ||
// i18n | ||
//============================================================================= | ||
var ENGLISH = { | ||
dayNames: [ | ||
"Sunday", "Monday", "Tuesday", "Wednesday", | ||
"Thursday", "Friday", "Saturday" | ||
], | ||
monthNames: [ | ||
"January", "February", "March", "April", "May", | ||
"June", "July", "August", "September", "October", | ||
"November", "December" | ||
], | ||
tokens: { | ||
var ENGLISH = { | ||
dayNames: [ | ||
'Sunday', 'Monday', 'Tuesday', 'Wednesday', | ||
'Thursday', 'Friday', 'Saturday' | ||
], | ||
monthNames: [ | ||
'January', 'February', 'March', 'April', 'May', | ||
'June', 'July', 'August', 'September', 'October', | ||
'November', 'December' | ||
], | ||
tokens: { | ||
'SKIP': /^[ \r\n\t]+|^\.$/, | ||
@@ -1009,5 +938,7 @@ 'number': /^[1-9][0-9]*/, | ||
'week(s)': /^weeks?/i, | ||
'hour(s)': /^hours?/i, | ||
'month(s)': /^months?/i, | ||
'year(s)': /^years?/i, | ||
'on': /^(on|in)/i, | ||
'at': /^(at)/i, | ||
'the': /^the/i, | ||
@@ -1042,32 +973,18 @@ 'first': /^first/i, | ||
'comma': /^(,\s*|(and|or)\s*)+/i | ||
} | ||
} | ||
}; | ||
// ============================================================================= | ||
// Export | ||
// ============================================================================= | ||
//============================================================================= | ||
// Export | ||
//============================================================================= | ||
var nlp = { | ||
fromText: fromText, | ||
parseText: parseText, | ||
isFullyConvertible: ToText.isFullyConvertible, | ||
toText: function(rrule, gettext, language) { | ||
return new ToText(rrule, gettext, language).toString(); | ||
return { | ||
fromText: fromText, | ||
parseText: parseText, | ||
isFullyConvertible: ToText.isFullyConvertible, | ||
toText: function (rrule, gettext, language) { | ||
return new ToText(rrule, gettext, language).toString() | ||
} | ||
} | ||
}; | ||
if (serverSide) { | ||
module.exports = nlp | ||
} else { | ||
root['_RRuleNLP'] = nlp; | ||
} | ||
if (typeof define === "function" && define.amd) { | ||
/*global define:false */ | ||
define("rrule", [], function () { | ||
return RRule; | ||
}); | ||
} | ||
})(this); | ||
} | ||
})) |
{ | ||
"name": "rrule", | ||
"version": "2.1.0", | ||
"description": "JavaScript library for working with recurrence rules for calendar dates.", | ||
"homepage": "http://jakubroztocil.github.io/rrule/", | ||
"keywords":[ | ||
"dates", | ||
"recurrences", | ||
"calendar", | ||
"icalendar", | ||
"rfc" | ||
], | ||
"author": "Jakub Roztocil and Lars Schöning", | ||
"main": "lib/rrule", | ||
"repository" :{ | ||
"type": "git", | ||
"url": "git://github.com/jakubroztocil/rrule.git" | ||
}, | ||
"scripts": { | ||
"test": "echo Run tests in the browser at tests/index.html" | ||
} | ||
"name": "rrule", | ||
"version": "2.2.0", | ||
"description": "JavaScript library for working with recurrence rules for calendar dates.", | ||
"homepage": "http://jakubroztocil.github.io/rrule/", | ||
"keywords": [ | ||
"dates", | ||
"recurrences", | ||
"calendar", | ||
"icalendar", | ||
"rfc" | ||
], | ||
"author": "Jakub Roztocil and Lars Schöning", | ||
"main": "lib/rrule", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/jakubroztocil/rrule.git" | ||
}, | ||
"scripts": { | ||
"test": "standard && mocha" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^2.3.4", | ||
"standard": "^5.4.1" | ||
}, | ||
"standard": { | ||
"ignore": [ | ||
"demo" | ||
] | ||
}, | ||
"files": [ | ||
"lib", | ||
"README.md" | ||
] | ||
} |
264
README.md
@@ -6,4 +6,10 @@ rrule.js | ||
[![NPM version][npm-image]][npm-url] | ||
[![Build Status][travis-image]][travis-url] | ||
[![js-standard-style][js-standard-image]][js-standard-url] | ||
[![Downloads][downloads-image]][downloads-url] | ||
rrule.js supports recurrence rules as defined in the [iCalendar | ||
RFC](http://www.ietf.org/rfc/rfc2445.txt). It is a partial port of the | ||
RFC](http://www.ietf.org/rfc/rfc2445.txt), with a few important | ||
[differences](#differences-from-icalendar-rfc). It is a partial port of the | ||
`rrule` module from the excellent | ||
@@ -20,5 +26,3 @@ [python-dateutil](http://labix.org/python-dateutil/) library. On top of | ||
- [Demo app](http://jakubroztocil.github.io/rrule/) | ||
- [Test suite](http://jakubroztocil.github.io/rrule/tests/index.html) | ||
#### Client Side | ||
@@ -49,3 +53,5 @@ | ||
```javascript | ||
var RRule = require('rrule').RRule; | ||
var RRule = require('rrule').RRule | ||
var RRuleSet = require('rrule').RRuleSet | ||
var rrulestr = require('rrule').rrulestr | ||
``` | ||
@@ -55,14 +61,15 @@ | ||
```javascript | ||
**RRule:** | ||
```js | ||
// Create a rule: | ||
var rule = new RRule({ | ||
freq: RRule.WEEKLY, | ||
interval: 5, | ||
byweekday: [RRule.MO, RRule.FR], | ||
dtstart: new Date(2012, 1, 1, 10, 30), | ||
until: new Date(2012, 12, 31) | ||
}); | ||
freq: RRule.WEEKLY, | ||
interval: 5, | ||
byweekday: [RRule.MO, RRule.FR], | ||
dtstart: new Date(2012, 1, 1, 10, 30), | ||
until: new Date(2012, 12, 31) | ||
}) | ||
// Get all occurrence dates (Date instances): | ||
rule.all(); | ||
rule.all() | ||
['Fri Feb 03 2012 10:30:00 GMT+0100 (CET)', | ||
@@ -81,3 +88,3 @@ 'Mon Mar 05 2012 10:30:00 GMT+0100 (CET)', | ||
// The output can be used with RRule.fromString(). | ||
rule.toString(); | ||
rule.toString() | ||
"FREQ=WEEKLY;DTSTART=20120201T093000Z;INTERVAL=5;UNTIL=20130130T230000Z;BYDAY=MO,FR" | ||
@@ -91,5 +98,68 @@ | ||
**RRuleSet:** | ||
```js | ||
var rruleSet = new RRuleSet() | ||
// Add a rrule to rruleSet | ||
rruleSet.rrule(new RRule({ | ||
freq: RRule.MONTHLY, | ||
count: 5, | ||
dtstart: new Date(2012, 1, 1, 10, 30) | ||
})) | ||
// Add a date to rruleSet | ||
rruleSet.rdate(new Date(2012, 6, 1, 10, 30)) | ||
// Add another date to rruleSet | ||
rruleSet.rdate(new Date(2012, 6, 2, 10, 30)) | ||
// Add a exclusion rrule to rruleSet | ||
rruleSet.exrule(new r.RRule({ | ||
freq: RRule.MONTHLY, | ||
count: 2, | ||
dtstart: new Date(2012, 2, 1, 10, 30) | ||
})) | ||
// Add a exclusion date to rruleSet | ||
rruleSet.exdate(new Date(2012, 5, 1, 10, 30)) | ||
// Get all occurrence dates (Date instances): | ||
rruleSet.all() | ||
['Wed Feb 01 2012 10:30:00 GMT+0800 (CST)', | ||
'Tue May 01 2012 10:30:00 GMT+0800 (CST)', | ||
'Sun Jul 01 2012 10:30:00 GMT+0800 (CST)', | ||
'Mon Jul 02 2012 10:30:00 GMT+0800 (CST)'] | ||
// Get a slice: | ||
rruleSet.between(new Date(2012, 2, 1), new Date(2012, 6, 2)) | ||
['Tue May 01 2012 10:30:00 GMT+0800 (CST)', | ||
'Sun Jul 01 2012 10:30:00 GMT+0800 (CST)'] | ||
// To string | ||
rruleSet.valueOf() | ||
['RRULE:FREQ=MONTHLY;COUNT=5;DTSTART=20120201T023000Z', | ||
'RDATE:20120701T023000Z,20120702T023000Z', | ||
'EXRULE:FREQ=MONTHLY;COUNT=2;DTSTART=20120301T023000Z', | ||
'EXDATE:20120601T023000Z'] | ||
// To string | ||
rruleSet.toString() | ||
'["RRULE:FREQ=MONTHLY;COUNT=5;DTSTART=20120201T023000Z","RDATE:20120701T023000Z,20120702T023000Z","EXRULE:FREQ=MONTHLY;COUNT=2;DTSTART=20120301T023000Z","EXDATE:20120601T023000Z"]' | ||
``` | ||
**rrulestr:** | ||
```js | ||
// Parse a RRule string, return a RRule object | ||
rrulestr('RRULE:FREQ=MONTHLY;COUNT=5;DTSTART=20120201T023000Z') | ||
// Parse a RRule string, return a RRuleSet object | ||
rrulestr('RRULE:FREQ=MONTHLY;COUNT=5;DTSTART=20120201T023000Z', {forceset: true}) | ||
// Parse a RRuleSet string, return a RRuleSet object | ||
rrulestr('RRULE:FREQ=MONTHLY;COUNT=5;DTSTART=20120201T023000Z\nRDATE:20120701T023000Z,20120702T023000Z\nEXRULE:FREQ=MONTHLY;COUNT=2;DTSTART=20120301T023000Z\nEXDATE:20120601T023000Z') | ||
``` | ||
For more examples see | ||
[tests/tests.js](https://github.com/jakubroztocil/rrule/blob/master/tests/tests.js) | ||
and [python-dateutil](http://labix.org/python-dateutil/) documentation. | ||
[python-dateutil](http://labix.org/python-dateutil/) documentation. | ||
@@ -314,3 +384,3 @@ ### API | ||
rule.all(function (date, i){return i < 2}); | ||
rule.all(function (date, i){return i < 2}) | ||
['Fri Feb 03 2012 10:30:00 GMT+0100 (CET)', | ||
@@ -362,6 +432,6 @@ 'Mon Mar 05 2012 10:30:00 GMT+0100 (CET)',] | ||
Returns a string representation of the rule as per the iCalendar RFC. | ||
Only properties explicitely specified in `options` are included: | ||
Only properties explicitly specified in `options` are included: | ||
```javascript | ||
rule.toString(); | ||
rule.toString() | ||
"FREQ=WEEKLY;DTSTART=20120201T093000Z;INTERVAL=5;UNTIL=20130130T230000Z;BYDAY=MO,FR" | ||
@@ -385,4 +455,4 @@ | ||
RRule.optionsToString({ | ||
freq: rule.options.freq, | ||
dtstart: rule.options.dtstart, | ||
freq: rule.options.freq, | ||
dtstart: rule.options.dtstart | ||
}) | ||
@@ -464,8 +534,142 @@ "FREQ=WEEKLY;DTSTART=20120201T093000Z" | ||
* * * * * | ||
#### `RRuleSet` Constructor | ||
```javascript | ||
new RRuleSet([noCache=false]) | ||
``` | ||
The RRuleSet instance allows more complex recurrence setups, mixing multiple | ||
rules, dates, exclusion rules, and exclusion dates. | ||
Default `noCache` argument is `false`, caching of results will be enabled, | ||
improving performance of multiple queries considerably. | ||
##### `RRuleSet.prototype.rrule(rrule)` | ||
Include the given rrule instance in the recurrence set generation. | ||
##### `RRuleSet.prototype.rdate(dt)` | ||
Include the given datetime instance in the recurrence set generation. | ||
##### `RRuleSet.prototype.exrule(rrule)` | ||
Include the given rrule instance in the recurrence set exclusion list. Dates | ||
which are part of the given recurrence rules will not be generated, even if | ||
some inclusive rrule or rdate matches them. | ||
##### `RRuleSet.prototype.exdate(dt)` | ||
Include the given datetime instance in the recurrence set exclusion list. Dates | ||
included that way will not be generated, even if some inclusive rrule or | ||
rdate matches them. | ||
##### `RRuleSet.prototype.all([iterator])` | ||
Same as `RRule.prototype.all`. | ||
##### `RRuleSet.prototype.between(after, before, inc=false [, iterator])` | ||
Same as `RRule.prototype.between`. | ||
##### `RRuleSet.prototype.before(dt, inc=false)` | ||
Same as `RRule.prototype.before`. | ||
##### `RRuleSet.prototype.after(dt, inc=false)` | ||
Same as `RRule.prototype.after`. | ||
* * * * * | ||
#### `rrulestr` Function | ||
```js | ||
rrulestr(rruleStr[, options]) | ||
``` | ||
The `rrulestr` function is a parser for RFC-like syntaxes. The string passed | ||
as parameter may be a multiple line string, a single line string, or just the | ||
RRULE property value. | ||
Additionally, it accepts the following keyword arguments: | ||
`cache` | ||
If True, the rruleset or rrule created instance will cache its results. | ||
Default is not to cache. | ||
`dtstart` | ||
If given, it must be a datetime instance that will be used when no DTSTART | ||
property is found in the parsed string. If it is not given, and the property | ||
is not found, datetime.now() will be used instead. | ||
`unfold` | ||
If set to True, lines will be unfolded following the RFC specification. It | ||
defaults to False, meaning that spaces before every line will be stripped. | ||
`forceset` | ||
If set to True a rruleset instance will be returned, even if only a single rule | ||
is found. The default is to return an rrule if possible, and an rruleset if necessary. | ||
`compatible` | ||
If set to True, the parser will operate in RFC-compatible mode. Right now it | ||
means that unfold will be turned on, and if a DTSTART is found, it will be | ||
considered the first recurrence instance, as documented in the RFC. | ||
`ignoretz` | ||
If set to True, the date parser will ignore timezone information available in | ||
the DTSTART property, or the UNTIL attribute. | ||
`tzinfos` | ||
If set, it will be passed to the datetime string parser to resolve unknown | ||
timezone settings. For more information about what could be used here, check | ||
the parser documentation. | ||
* * * * * | ||
### Differences From iCalendar RFC | ||
* `RRule` has no `byday` keyword. The equivalent keyword has been replaced by | ||
the `byweekday` keyword, to remove the ambiguity present in the original | ||
keyword. | ||
* Unlike documented in the RFC, the starting datetime, `dtstart`, is | ||
not the first recurrence instance, unless it does fit in the specified rules. | ||
This is in part due to this project being a port of | ||
[python-dateutil](https://labix.org/python-dateutil#head-a65103993a21b717f6702063f3717e6e75b4ba66), | ||
which has the same non-compliant functionality. Note that you can get the | ||
original behavior by using a `RRuleSet` and adding the `dtstart` as an `rdate`. | ||
```javascript | ||
var rruleSet = new RRuleSet() | ||
var start = new Date(2012, 1, 1, 10, 30) | ||
// Add a rrule to rruleSet | ||
rruleSet.rrule(new RRule({ | ||
freq: RRule.MONTHLY, | ||
count: 5, | ||
dtstart: start | ||
})) | ||
// Add a date to rruleSet | ||
rruleSet.rdate(start) | ||
``` | ||
* Unlike documented in the RFC, every keyword is valid on every frequency (the | ||
RFC documents that `byweekno` is only valid on yearly frequencies, for example). | ||
### Development | ||
rrule.js use [JavaScript Standard Style](https://github.com/feross/standard) coding style. | ||
### Changelog | ||
* 2.2.0-dev | ||
* Added support `RRuleSet`, which allows more complex recurrence setups, | ||
mixing multiple rules, dates, exclusion rules, and exclusion dates. | ||
* Added Millisecond precision | ||
* Millisecond offset extracted from `dtstart` (`dtstart.getTime() % 1000`) | ||
* Each recurrence is returned with the same offset | ||
* Added some NLP support for hourly and byhour. | ||
* Fixed export in nlp.js. | ||
* 2.1.0 | ||
* Removed dependency on Underscore.js (thanks @gsf). | ||
* Removed dependency on Underscore.js (thanks, @gsf). | ||
* Various small bugfixes and improvements. | ||
@@ -484,3 +688,3 @@ * 2.0.1 | ||
(never actually used). | ||
* `rule.toString()` now includes `DTSTART` (if explicitely specified | ||
* `rule.toString()` now includes `DTSTART` (if explicitly specified | ||
in `options`). | ||
@@ -507,3 +711,3 @@ * Day constants `.clone` is now `.nth`, eg. `RRule.FR.nth(-1)` | ||
* [Jakub Roztocil](http://subtleapps.com/) | ||
* [Jakub Roztocil](http://roztocil.co/) | ||
([@jakubroztocil](http://twitter.com/jakubroztocil)) | ||
@@ -517,1 +721,13 @@ * Lars Schöning ([@lyschoening](http://twitter.com/lyschoening)) | ||
more details. | ||
[npm-url]: https://npmjs.org/package/rrule | ||
[npm-image]: http://img.shields.io/npm/v/rrule.svg | ||
[travis-url]: https://travis-ci.org/jakubroztocil/rrule | ||
[travis-image]: http://img.shields.io/travis/jakubroztocil/rrule.svg | ||
[downloads-url]: https://npmjs.org/package/rrule | ||
[downloads-image]: http://img.shields.io/npm/dm/rrule.svg?style=flat-square | ||
[js-standard-url]: https://github.com/feross/standard | ||
[js-standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
0
721
0
123826
2
5
2897