ical.js-one.com
Version:
[](http://travis-ci.org/mozilla-comm/ical.js)
312 lines (257 loc) • 8.26 kB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Portions Copyright (C) Philipp Kewisch, 2011-2012 */
"use strict";
(typeof(ICAL) === 'undefined')? ICAL = {} : '';
(function() {
var DOW_MAP = {
SU: ICAL.Time.SUNDAY,
MO: ICAL.Time.MONDAY,
TU: ICAL.Time.TUESDAY,
WE: ICAL.Time.WEDNESDAY,
TH: ICAL.Time.THURSDAY,
FR: ICAL.Time.FRIDAY,
SA: ICAL.Time.SATURDAY
};
var REVERSE_DOW_MAP = {};
for (var key in DOW_MAP) {
REVERSE_DOW_MAP[DOW_MAP[key]] = key;
}
var COPY_PARTS = ["BYSECOND", "BYMINUTE", "BYHOUR", "BYDAY",
"BYMONTHDAY", "BYYEARDAY", "BYWEEKNO",
"BYMONTH", "BYSETPOS"];
ICAL.Recur = function icalrecur(data) {
this.wrappedJSObject = this;
this.parts = {};
if (typeof(data) === 'object') {
for (var key in data) {
this[key] = data[key];
}
if (this.until && !(this.until instanceof ICAL.Time)) {
this.until = new ICAL.Time(this.until);
}
}
if (!this.parts) {
this.parts = {};
}
};
ICAL.Recur.prototype = {
parts: null,
interval: 1,
wkst: ICAL.Time.MONDAY,
until: null,
count: null,
freq: null,
icalclass: "icalrecur",
icaltype: "recur",
iterator: function(aStart) {
return new ICAL.RecurIterator({
rule: this,
dtstart: aStart
});
},
clone: function clone() {
return new ICAL.Recur(this.toJSON());
},
isFinite: function isfinite() {
return !!(this.count || this.until);
},
isByCount: function isbycount() {
return !!(this.count && !this.until);
},
addComponent: function addPart(aType, aValue) {
if (!(aType in this.parts)) {
this.parts[aType] = [aValue];
} else {
this.parts[aType].push(aValue);
}
},
setComponent: function setComponent(aType, aValues) {
this.parts[aType] = aValues;
},
getComponent: function getComponent(aType, aCount) {
var ucName = aType.toUpperCase();
var components = (ucName in this.parts ? this.parts[ucName] : []);
if (aCount) aCount.value = components.length;
return components;
},
getNextOccurrence: function getNextOccurrence(aStartTime, aRecurrenceId) {
var iter = this.iterator(aStartTime);
var next, cdt;
do {
next = iter.next();
} while (next && next.compare(aRecurrenceId) <= 0);
if (next && aRecurrenceId.zone) {
next.zone = aRecurrenceId.zone;
}
return next;
},
toJSON: function() {
//XXX: extract this list up to proto?
var propsToCopy = [
"freq",
"count",
"until",
"wkst",
"interval",
"parts"
];
var result = Object.create(null);
var i = 0;
var len = propsToCopy.length;
var prop;
for (; i < len; i++) {
var prop = propsToCopy[i];
result[prop] = this[prop];
}
if (result.until instanceof ICAL.Time) {
result.until = result.until.toJSON();
}
return result;
},
toString: function icalrecur_toString() {
// TODO retain order
var str = "FREQ=" + this.freq;
if (this.count) {
str += ";COUNT=" + this.count;
}
if (this.interval > 1) {
str += ";INTERVAL=" + this.interval;
}
for (var k in this.parts) {
str += ";" + k + "=" + this.parts[k];
}
if (this.until ){
str += ';UNTIL=' + this.until.toString();
}
if ('wkst' in this && this.wkst !== ICAL.Time.DEFAULT_WEEK_START) {
str += ';WKST=' + ICAL.Recur.numericDayToIcalDay(this.wkst);
}
return str;
}
};
function parseNumericValue(type, min, max, value) {
var result = value;
if (value[0] === '+') {
result = value.substr(1);
}
result = ICAL.helpers.strictParseInt(result);
if (min !== undefined && value < min) {
throw new Error(
type + ': invalid value "' + value + '" must be > ' + min
);
}
if (max !== undefined && value > max) {
throw new Error(
type + ': invalid value "' + value + '" must be < ' + min
);
}
return result;
}
/**
* Convert an ical representation of a day (SU, MO, etc..)
* into a numeric value of that day.
*
* @param {String} day ical day.
* @return {Numeric} numeric value of given day.
*/
ICAL.Recur.icalDayToNumericDay = function toNumericDay(string) {
//XXX: this is here so we can deal
// with possibly invalid string values.
return DOW_MAP[string];
};
/**
* Convert a numeric day value into its ical representation (SU, MO, etc..)
*
* @param {Numeric} numeric value of given day.
* @return {String} day ical day.
*/
ICAL.Recur.numericDayToIcalDay = function toIcalDay(num) {
//XXX: this is here so we can deal with possibly invalid number values.
// Also, this allows consistent mapping between day numbers and day
// names for external users.
return REVERSE_DOW_MAP[num];
};
var VALID_DAY_NAMES = /^(SU|MO|TU|WE|TH|FR|SA)$/;
var VALID_BYDAY_PART = /^([+-])?(5[0-3]|[1-4][0-9]|[1-9])?(SU|MO|TU|WE|TH|FR|SA)$/
var ALLOWED_FREQ = ['SECONDLY', 'MINUTELY', 'HOURLY',
'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY'];
var optionDesign = {
FREQ: function(value, dict) {
// yes this is actually equal or faster then regex.
// upside here is we can enumerate the valid values.
if (ALLOWED_FREQ.indexOf(value) !== -1) {
dict.freq = value;
} else {
throw new Error(
'invalid frequency "' + value + '" expected: "' +
ALLOWED_FREQ.join(', ') + '"'
);
}
},
COUNT: function(value, dict) {
dict.count = ICAL.helpers.strictParseInt(value);
},
INTERVAL: function(value, dict) {
dict.interval = ICAL.helpers.strictParseInt(value);
if (dict.interval < 1) {
// 0 or negative values are not allowed, some engines seem to generate
// it though. Assume 1 instead.
dict.interval = 1;
}
},
UNTIL: function(value, dict) {
dict.until = ICAL.Time.fromString(value);
},
WKST: function(value, dict) {
if (VALID_DAY_NAMES.test(value)) {
dict.wkst = ICAL.Recur.icalDayToNumericDay(value);
} else {
throw new Error('invalid WKST value "' + value + '"');
}
}
};
var partDesign = {
BYSECOND: parseNumericValue.bind(this, 'BYSECOND', 0, 60),
BYMINUTE: parseNumericValue.bind(this, 'BYMINUTE', 0, 59),
BYHOUR: parseNumericValue.bind(this, 'BYHOUR', 0, 23),
BYDAY: function(value) {
if (VALID_BYDAY_PART.test(value)) {
return value;
} else {
throw new Error('invalid BYDAY value "' + value + '"');
}
},
BYMONTHDAY: parseNumericValue.bind(this, 'BYMONTHDAY', -31, 31),
BYYEARDAY: parseNumericValue.bind(this, 'BYYEARDAY', -366, 366),
BYWEEKNO: parseNumericValue.bind(this, 'BYWEEKNO', -53, 53),
BYMONTH: parseNumericValue.bind(this, 'BYMONTH', 0, 12),
BYSETPOS: parseNumericValue.bind(this, 'BYSETPOS', -366, 366)
};
ICAL.Recur.fromString = function(string) {
var dict = Object.create(null);
var dictParts = dict.parts = Object.create(null);
// split is slower in FF but fast enough.
// v8 however this is faster then manual split?
var values = string.split(';');
var len = values.length;
for (var i = 0; i < len; i++) {
var parts = values[i].split('=');
var name = parts[0];
var value = parts[1];
if (name in partDesign) {
var partArr = value.split(',');
var partArrIdx = 0;
var partArrLen = partArr.length;
for (; partArrIdx < partArrLen; partArrIdx++) {
partArr[partArrIdx] = partDesign[name](partArr[partArrIdx]);
}
dictParts[name] = partArr;
} else if (name in optionDesign) {
optionDesign[name](value, dict);
}
}
return new ICAL.Recur(dict);
};
})();