Comparing version 0.5.4 to 0.6.0
@@ -28,2 +28,6 @@ // Copyright (C) 2011 Tri Tech Computers Ltd. | ||
var properties = exports.properties = { | ||
EXDATE: { | ||
type: 'DATE-TIME', | ||
list: true | ||
}, | ||
DTSTAMP: { | ||
@@ -76,3 +80,3 @@ type: 'DATE-TIME' | ||
for(var prop in this.properties) | ||
obj.addProperty(this.properties[prop]); | ||
obj.addProperties(this.properties[prop]); | ||
@@ -86,10 +90,16 @@ var comp = this.getComponents(); | ||
CalendarObject.prototype.addProperties = function(props) { | ||
props.forEach(this.addProperty.bind(this)); | ||
} | ||
CalendarObject.prototype.addProperty = function(prop, value, parameters) { | ||
if(!(prop instanceof CalendarProperty)) | ||
if(!(prop instanceof CalendarProperty)) { | ||
if(value === undefined) return; | ||
prop = new CalendarProperty(prop, value, parameters); | ||
} | ||
else | ||
prop = prop.clone(); | ||
// TODO: What about multiple occurances of the same property? | ||
this.properties[prop.name] = prop; | ||
this.properties[prop.name] = this.properties[prop.name] || []; | ||
this.properties[prop.name].push(prop); | ||
return prop; | ||
@@ -130,10 +140,31 @@ } | ||
CalendarObject.prototype.getProperty = function(prop) { | ||
return this.properties[prop]; | ||
CalendarObject.prototype.getProperty = function(prop, i) { | ||
return (this.properties[prop] || [])[i || 0]; | ||
} | ||
CalendarObject.prototype.getPropertyValue = function(prop) { | ||
return (this.properties[prop] || {}).value; | ||
CalendarObject.prototype.getProperties = function(prop) { | ||
return this.properties[prop] || []; | ||
} | ||
CalendarObject.prototype.getPropertyValue = function(prop, i) { | ||
return (this.getProperty(prop, i) || {}).value; | ||
} | ||
CalendarObject.prototype.validate = function() { | ||
var self = this; | ||
var _schema = schema[self.element]; | ||
if(_schema && _schema.required_properties) { | ||
_schema.required_properties.forEach(function(req) { | ||
if(!self.getPropertyValue(req)) | ||
throw new Error(req+" is a required property of "+self.element); | ||
}); | ||
} | ||
for(var type in self.components) { | ||
self.components[type].forEach(function(comp) { | ||
comp.validate(); | ||
}); | ||
} | ||
} | ||
CalendarObject.prototype.toString = function() { | ||
@@ -156,4 +187,7 @@ // Make sure output always includes a VCALENDAR object | ||
var lines = ['BEGIN:'+this.element]; | ||
for(var i in this.properties) | ||
lines.push.apply(lines, this.properties[i].format()); | ||
for(var i in this.properties) { | ||
this.properties[i].forEach(function(prop) { | ||
lines.push.apply(lines, prop.format()); | ||
}); | ||
} | ||
@@ -202,3 +236,10 @@ for(var comp in this.components) { | ||
CalendarProperty.prototype.format = function() { | ||
var data = new Buffer(this.name+':'+format_value(this.type, this.value, this.parameters)); | ||
var params = []; | ||
for(var k in this.parameters) | ||
params.push(k+'='+this.parameters[k]); | ||
if(params.length) | ||
params = ';'+params.join(';'); | ||
var data = new Buffer(this.name+params+':'+format_value(this.type, this.value, this.parameters)); | ||
var pos = 0, len; | ||
@@ -205,0 +246,0 @@ var output = []; |
@@ -0,1 +1,2 @@ | ||
"use strict"; | ||
// Copyright (C) 2011 Tri Tech Computers Ltd. | ||
@@ -72,5 +73,6 @@ // | ||
return new RRule(rr, | ||
this.getPropertyValue('DTSTART'), | ||
this.getPropertyValue('DTEND')); | ||
return new RRule(rr, { | ||
DTSTART: this.getPropertyValue('DTSTART'), | ||
DTEND: this.getPropertyValue('DTEND') | ||
}); | ||
} | ||
@@ -77,0 +79,0 @@ |
@@ -81,4 +81,4 @@ // Copyright (C) 2011 Tri Tech Computers Ltd. | ||
else { | ||
value = parse_value((properties[element] || {}).type, | ||
value, parameters, cal); | ||
var prop = properties[element] || {}; | ||
value = parse_value(prop.type, value, parameters, cal, prop.list); | ||
component.addProperty(element, value, parameters); | ||
@@ -117,3 +117,3 @@ return this_state; | ||
var j = i; | ||
while(j+1<data.length && data[j+1][0] == ' ') | ||
while(j+1<data.length && (data[j+1][0] === ' ' || data[j+1][0] === '\t')) | ||
++j; | ||
@@ -120,0 +120,0 @@ |
121
lib/rrule.js
@@ -45,7 +45,11 @@ // Copyright (C) 2011 Tri Tech Computers Ltd. | ||
return new Date(Date.UTC.apply(null, dt)); | ||
//udt.date_only = dt.date_only; | ||
return udt; | ||
} | ||
function from_utc_date(dt) { | ||
return new Date(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate(), | ||
dt.getUTCHours(), dt.getUTCMinutes(), dt.getUTCSeconds(), dt.getUTCMilliseconds()); | ||
function from_utc_date(udt) { | ||
var dt = new Date(udt.getUTCFullYear(), udt.getUTCMonth(), udt.getUTCDate(), | ||
udt.getUTCHours(), udt.getUTCMinutes(), udt.getUTCSeconds(), udt.getUTCMilliseconds()); | ||
//dt.date_only = udt.date_only; | ||
return dt; | ||
} | ||
@@ -109,6 +113,12 @@ | ||
var RRule = exports.RRule = function(rule, start, end) { | ||
this.start = start ? to_utc_date(start) : null; | ||
this.end = end ? to_utc_date(end) : null; | ||
var RRule = exports.RRule = function(rule, options, dtend) { | ||
if(options instanceof Date) | ||
options = { DTSTART: options, DTEND: dtend }; | ||
options = options || {}; | ||
this.start = options.DTSTART ? to_utc_date(options.DTSTART) : null; | ||
this.end = options.DTEND ? to_utc_date(options.DTEND) : null; | ||
this.exceptions = options.EXDATE || []; | ||
if(typeof rule === 'string') | ||
@@ -169,4 +179,21 @@ rule = RRule.parse(rule); | ||
var next = freq.next(this.rule, this.start, after); | ||
NextOccurs: | ||
while(true) { | ||
var next = freq.next(this.rule, this.start, after); | ||
// Exclude EXDATES | ||
var nextInLocal = from_utc_date(next); | ||
for(var i=0; i < this.exceptions.length; i++) { | ||
var exdate = this.exceptions[i]; | ||
if((exdate.valueOf() == nextInLocal.valueOf()) | ||
|| (exdate.date_only && y(exdate) == y(nextInLocal) | ||
&& m(exdate) == m(nextInLocal) && d(exdate) == d(nextInLocal))) { | ||
after = next; | ||
continue NextOccurs; | ||
} | ||
} | ||
break; | ||
} | ||
// Date is off the end of the spectrum... | ||
@@ -265,2 +292,14 @@ if(this.end && next > this.end) | ||
} | ||
}, | ||
EXDATE: { | ||
parse: function(v) { | ||
return v.split(',').map(function(dt) { | ||
return dt.length == 8 ? types.parse_value('DATE', dt) : types.parse_value('DATE-TIME', dt); | ||
}); | ||
}, | ||
format: function(v) { | ||
return v.map(function(dt) { | ||
return types.format_value(dt.date_only ? 'DATE' : 'DATE-TIME', dt); | ||
}).join(','); | ||
} | ||
} | ||
@@ -277,2 +316,7 @@ }; | ||
var next = new Date(after); | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
var interval = rule.INTERVAL || 1; | ||
@@ -286,7 +330,3 @@ | ||
for(var i=0; i<2; ++i) { | ||
next = byday(rule.BYDAY, next); | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
next = byday(rule.BYDAY, next, after); | ||
@@ -305,2 +345,7 @@ if(next.valueOf() > after.valueOf()) | ||
var next = new Date(after); | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
var interval = rule.INTERVAL || 1; | ||
@@ -314,3 +359,3 @@ | ||
while(true) { | ||
next = byday(rule.BYDAY, next); | ||
next = byday(rule.BYDAY, next, after); | ||
@@ -329,6 +374,2 @@ // Fall back to the start day of the week | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
@@ -348,2 +389,7 @@ if(next.valueOf() > after.valueOf() | ||
var next = new Date(after); | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
var interval = rule.INTERVAL || 1; | ||
@@ -358,5 +404,5 @@ | ||
for(var i=0; i<2; ++i) { | ||
set_d(next, 1); | ||
next = byday(rule.BYDAY, next); | ||
next = bymonthday(rule.BYMONTHDAY, next); | ||
if (i) set_d(next, 1); // Start at the beginning of the month for subsequent months | ||
next = byday(rule.BYDAY, next, after); | ||
next = bymonthday(rule.BYMONTHDAY, next, after); | ||
@@ -367,7 +413,2 @@ // Fall back to the start day of the month | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
if(next.valueOf() > after.valueOf()) | ||
@@ -386,2 +427,8 @@ break; | ||
var next = new Date(after); | ||
// TODO: Add actual byhour/minute/second methods | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
var interval = rule.INTERVAL || 1; | ||
@@ -396,5 +443,5 @@ | ||
for(var i=0; i<2; ++i) { | ||
next = bymonth(rule.BYMONTH, next, i); | ||
next = bymonthday(rule.BYMONTHDAY, next); | ||
next = byday(rule.BYDAY, next, i); | ||
next = bymonth(rule.BYMONTH, next); | ||
next = bymonthday(rule.BYMONTHDAY, next, after); | ||
next = byday(rule.BYDAY, next, after); | ||
@@ -407,8 +454,2 @@ // Fall back the the start month and day of the month | ||
// TODO: Add actual byhour/minute/second methods | ||
set_hr(next, hr(start)); | ||
set_min(next, min(start)); | ||
set_sec(next, sec(start)); | ||
set_ms(next, ms(start)); | ||
// Don't loop back again if we found a new date | ||
@@ -461,3 +502,3 @@ if(next.valueOf() > after.valueOf()) | ||
function bymonthday(rules, dt) { | ||
function bymonthday(rules, dt, after) { | ||
if(!rules || !rules.length) return dt; | ||
@@ -467,3 +508,3 @@ | ||
var newdt = set_d(new Date(dt), rule); | ||
return (newdt < dt ? null : newdt); | ||
return (newdt.valueOf() <= after.valueOf() ? null : newdt); | ||
}); | ||
@@ -477,3 +518,3 @@ | ||
// Advance to the next day that satisfies the byday rule... | ||
function byday(rules, dt) { | ||
function byday(rules, dt, after) { | ||
if(!rules || !rules.length) return dt; | ||
@@ -489,3 +530,3 @@ | ||
if(rule[0] > 0) { | ||
var wk = 0 | (d(newdt) / 7) + 1; | ||
var wk = 0 | ((d(newdt) - 1) / 7) + 1; | ||
if(wk > rule[0]) return null; | ||
@@ -508,4 +549,4 @@ | ||
// Don't look outside the current month... | ||
if(m(newdt) !== m(dt)) return null; | ||
// Ignore if it's a past date... | ||
if (newdt.valueOf() <= after.valueOf()) return null; | ||
@@ -512,0 +553,0 @@ return newdt; |
@@ -171,2 +171,6 @@ // Copyright (C) 2011 Tri Tech Computers Ltd. | ||
return value.toString().replace(/([\\,;])/g, "\\$1").replace(/\n/g, "\\n"); | ||
}, | ||
parse: function(value) { | ||
return value.replace(/\\([\\,;])/g, "$1") | ||
.replace(/\\[nN]/g, '\n'); | ||
} | ||
@@ -219,6 +223,14 @@ }, | ||
return fmt.format(value, parameters || {}); | ||
// PERIOD is a corner case here; it's an array of two values | ||
if(Array.isArray(value) && type !== 'PERIOD' | ||
|| type === 'PERIOD' && value[0] && Array.isArray(value[0])) | ||
return value.map(function(v) { return fmt.format(v, parameters || {}); }).join(','); | ||
else | ||
return fmt.format(value, parameters || {}); | ||
} | ||
var parse_value = exports.parse_value = function(type, value, parameters, calendar) { | ||
var parse_value = exports.parse_value = function(type, value, parameters, calendar, expect_list) { | ||
if(expect_list) | ||
return value.split(',').map(function(x) { return parse_value(type, x, parameters, calendar); }); | ||
var fmt = _types[type || 'TEXT']; | ||
@@ -225,0 +237,0 @@ if(fmt === undefined) |
{ | ||
"name": "icalendar", | ||
"version": "0.5.4", | ||
"version": "0.6.0", | ||
"author": "James Emerton <james@tri-tech.com>", | ||
@@ -5,0 +5,0 @@ "description": "RFC5545 iCalendar parser/generator", |
@@ -56,5 +56,8 @@ iCalendar for Node | ||
* RDATE and EXDATE are not implemented, but support is planned | ||
* RDATE is not yet implemented | ||
* RECURRENCE-ID and multiple related VEVENTS are not currently supported | ||
* Documentation is pretty weak | ||
Contact | ||
@@ -61,0 +64,0 @@ ------- |
@@ -129,2 +129,19 @@ // Test search | ||
}); | ||
it('formats objects with multiple occurrences of a property', function() { | ||
var vevent = new icalendar.VEvent('testuid@tri-tech.com'); | ||
vevent.addProperty('EXDATE', new Date(Date.UTC(2012,2,3, 11,30,00))); | ||
vevent.addProperty('EXDATE', new Date(2011,1,2), { VALUE: 'DATE' }); | ||
var dtstamp = icalendar.format_value('DATE-TIME',vevent.getPropertyValue('DTSTAMP')); | ||
assert.deepEqual([ | ||
'BEGIN:VEVENT', | ||
'DTSTAMP:'+dtstamp, | ||
'UID:testuid@tri-tech.com', | ||
'EXDATE:20120303T113000Z', | ||
'EXDATE;VALUE=DATE:20110202', | ||
'END:VEVENT' | ||
], | ||
vevent.format()); | ||
}); | ||
}); |
@@ -34,2 +34,47 @@ // Test search | ||
it("decodes escaped chars as per RFC", function() { | ||
var cal = parse_calendar( | ||
'BEGIN:VCALENDAR\r\n'+ | ||
'PRODID:-//Microsoft Corporation//Outlook 14.0 MIMEDIR//EN\r\n'+ | ||
'VERSION:2.0\r\n'+ | ||
'BEGIN:VEVENT\r\n'+ | ||
'DESCRIPTION:Once upon a time\\; someone used a comma\\,\\nand a newline!\r\n'+ | ||
'DTEND:20120427T170000Z\r\n'+ | ||
'DTSTART:20120427T150000Z\r\n'+ | ||
'UID:testuid@someotherplace.com\r\n'+ | ||
'END:VEVENT\r\n'+ | ||
'END:VCALENDAR\r\n'); | ||
var vevent = cal.components['VEVENT'][0]; | ||
assert.equal('Once upon a time; someone used a comma,\nand a newline!', | ||
vevent.getPropertyValue('DESCRIPTION')); | ||
}); | ||
it("handles data using tabs for line continuations (Outlook)", function() { | ||
var cal = parse_calendar( | ||
'BEGIN:VCALENDAR\r\n'+ | ||
'PRODID:-//Microsoft Corporation//Outlook 14.0 MIMEDIR//EN\r\n'+ | ||
'VERSION:2.0\r\n'+ | ||
'BEGIN:VEVENT\r\n'+ | ||
'CLASS:PUBLIC\r\n'+ | ||
'DESCRIPTION:Come print for free! We will have more ally prints\\, and all th\r\n'+ | ||
' e great colors you’ve come to expect from Open. \\n\\nFood! \\n\\nMusic! \\n\\\r\n'+ | ||
' nSun!\\n\r\n'+ | ||
'DTEND:20120427T170000Z\r\n'+ | ||
'DTSTART:20120427T150000Z\r\n'+ | ||
'UID:040000008200E00074C5B7101A82E00800000000C0132EF80F0ECD01000000000000000\r\n'+ | ||
' 0100000008B70F4BD4D344E418B5834B8D78C50A3\r\n'+ | ||
'END:VEVENT\r\n'+ | ||
'END:VCALENDAR\r\n'); | ||
var vevent = cal.components['VEVENT'][0]; | ||
assert.equal('Come print for free! We will have more ally prints, and all '+ | ||
'the great colors you’ve come to expect from Open. \n\nFood! \n\nMusic! \n\nSun!\n', | ||
vevent.getPropertyValue('DESCRIPTION')); | ||
assert.equal('040000008200E00074C5B7101A82E00800000000C0132EF80F0ECD0100000000000'+ | ||
'00000100000008B70F4BD4D344E418B5834B8D78C50A3', | ||
vevent.getPropertyValue('UID')); | ||
}); | ||
it('parses large collections', function() { | ||
@@ -156,3 +201,24 @@ var cal = parse_calendar( | ||
}); | ||
it('parses EXDATE properties', function() { | ||
var cal = parse_calendar( | ||
'BEGIN:VCALENDAR\r\n'+ | ||
'PRODID:-//Bobs Software Emporium//NONSGML Bobs Calendar//EN\r\n'+ | ||
'VERSION:2.0\r\n'+ | ||
'BEGIN:VEVENT\r\n'+ | ||
'DTSTAMP:20111202T165900\r\n'+ | ||
'UID:testuid@someotherplace.com\r\n'+ | ||
'EXDATE:20120102T100000,20120203T100000\r\n'+ | ||
'EXDATE;VALUE=DATE:20120304\r\n'+ | ||
'END:VEVENT\r\n'+ | ||
'END:VCALENDAR\r\n'); | ||
var vevent = cal.getComponents('VEVENT')[0]; | ||
expect(vevent.getPropertyValue('EXDATE')) | ||
.toEqual([new Date(2012,0,2, 10,0,0), new Date(2012,1,3, 10,0,0)]); | ||
expect(vevent.getPropertyValue('EXDATE',1)) | ||
.toEqual([new Date(2012,2,4)]); | ||
expect(vevent.getPropertyValue('EXDATE',1)[0].date_only).toBeTruthy(); | ||
}); | ||
}); | ||
var RRule = require('../lib/rrule').RRule; | ||
function date_only(y, m, d) { | ||
// Return a date object with the date_only set | ||
var dt = new Date(y, m, d); | ||
dt.date_only = true; | ||
return dt; | ||
} | ||
describe("RRule", function() { | ||
@@ -12,4 +19,11 @@ it("should parse RRULEs correctly", function() { | ||
.toEqual({FREQ: 'WEEKLY', BYMONTH: [1,2,3]}); | ||
rrule = new RRule('FREQ=WEEKLY;BYMONTH=1,2,3').valueOf(); | ||
expect(rrule.FREQ).toEqual('WEEKLY'); | ||
expect(rrule.BYMONTH).toEqual([1,2,3]); | ||
}); | ||
it("should format RRULEs correctly", function() { | ||
expect(new RRule('FREQ=WEEKLY;BYMONTH=1,2,3').toString()).toEqual('FREQ=WEEKLY;BYMONTH=1,2,3'); | ||
}); | ||
it("respects UNTIL parts", function() { | ||
@@ -48,2 +62,34 @@ var start = new Date(2011,0,1,2,0,0); | ||
it("respects EXDATE date_only parts", function() { | ||
var rrule = new RRule('FREQ=MONTHLY', { | ||
DTSTART: new Date(2011,0,1), | ||
EXDATE: [date_only(2011,1,1), date_only(2011,3,1)] | ||
}); | ||
expect(rrule.nextOccurences(new Date(2010,11,31), 4)) | ||
.toEqual([ | ||
new Date(2011,0,1), | ||
new Date(2011,2,1), | ||
new Date(2011,4,1), | ||
new Date(2011,5,1) | ||
]); | ||
}); | ||
// it("respects EXDATE full date parts", function() { | ||
// var feb01 = new Date(2011,1,1); | ||
// feb01.date_only = true; | ||
// | ||
// var rrule = new RRule('FREQ=MONTHLY', { | ||
// DTSTART: new Date(2011,0,1,10), | ||
// EXDATE: [feb01, new Date(2011,2,1, 10,0,0), new Date(2011,3,1, 12,0,0)] | ||
// }); | ||
// | ||
// expect(rrule.nextOccurences(new Date(2010,11,31), 3)) | ||
// .toEqual([ | ||
// new Date(2011,0,1,10), | ||
// new Date(2011,3,1,10), | ||
// new Date(2011,4,1,10) | ||
// ]); | ||
// }); | ||
describe("yearly recurrence", function() { | ||
@@ -112,2 +158,16 @@ it("handles yearly recurrence", function() { | ||
it("handles monthly recurrences with multiple BYDAY values", function () { | ||
var rrule = new RRule('FREQ=MONTHLY;BYDAY=1MO,2TU,3WE', new Date(2012, 0, 1)); | ||
expect(rrule.nextOccurences(new Date(2012, 0, 1), 6)) | ||
.toEqual([ | ||
new Date(2012, 0, 2), | ||
new Date(2012, 0, 10), | ||
new Date(2012, 0, 18), | ||
new Date(2012, 1, 6), | ||
new Date(2012, 1, 14), | ||
new Date(2012, 1, 15) | ||
]); | ||
}); | ||
it("handles monthly recurrences on the start day of month", function() { | ||
@@ -232,4 +292,12 @@ var rrule = new RRule(RRule.parse('FREQ=MONTHLY'), new Date(2011,0,1)); | ||
}); | ||
it("handles daily weekend recurrence across month boundary", function() { | ||
var rrule = new RRule('FREQ=DAILY;BYDAY=SA,SU', new Date(2012,3,14,17,0)); | ||
expect(rrule.next(new Date(2012,4,29,17,0))) | ||
.toEqual(new Date(2012,5,2,17,0)); | ||
}); | ||
}); | ||
}); | ||
@@ -47,3 +47,16 @@ | ||
}); | ||
it('formats array values correctly', function() { | ||
assert.equal( | ||
'20120101,20120201', | ||
icalendar.format_value('DATE', [new Date(2012,0,1), new Date(2012,1,1)])); | ||
var dt = new Date(Date.UTC(2011,10,9,17,32,16)); | ||
var dt2 = new Date(Date.UTC(2011,10,10,19,32)); | ||
assert.equal( | ||
'20111109T173216Z/20111110T193200Z,20111110T193200Z/20111109T173216Z', | ||
icalendar.format_value('PERIOD', [[dt, dt2], [dt2, dt]])); | ||
}); | ||
it('value parsers', function() { | ||
@@ -50,0 +63,0 @@ assert.equal('\u0000\u0001\u0002\u0004\u0005\u0006', |
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
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
190651
1969
66