Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var ical = require('ical')
var ical = require('./node-ical')
, months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']


Expand Down
143 changes: 111 additions & 32 deletions ical.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,61 +55,70 @@
return val;
}

var storeParam = function(name){
return function(val, params, curr){
var data;
if (params && params.length && !(params.length==1 && params[0]==='CHARSET=utf-8')){
data = {params:parseParams(params), val:text(val)}
var storeValParam = function (name) {
return function (val, curr) {
var current = curr[name];
if (Array.isArray(current)) {
current.push(val);
return curr;
}

if (current != null) {
curr[name] = [current, val];
return curr;
}

curr[name] = val;
return curr
}
else
data = text(val)
}

var current = curr[name];
if (Array.isArray(current)){
current.push(data);
return curr;
}
var storeParam = function (name) {
return function (val, params, curr) {
var data;
if (params && params.length && !(params.length == 1 && params[0] === 'CHARSET=utf-8')) {
data = { params: parseParams(params), val: text(val) }
}
else
data = text(val)

if (current != null){
curr[name] = [current, data];
return curr;
return storeValParam(name)(data, curr);
}

curr[name] = data;
return curr
}
}

var addTZ = function(dt, name, params){
var addTZ = function (dt, params) {
var p = parseParams(params);

if (params && p){
dt[name].tz = p.TZID
dt.tz = p.TZID
}

return dt
}


var dateParam = function(name){
return function(val, params, curr){
return function (val, params, curr) {

var newDate = text(val);

// Store as string - worst case scenario
storeParam(name)(val, undefined, curr)

if (params && params[0] === "VALUE=DATE") {
// Just Date

var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(val);
if (comps !== null) {
// No TZ info - assume same timezone as this computer
curr[name] = new Date(
newDate = new Date(
comps[1],
parseInt(comps[2], 10)-1,
comps[3]
);

return addTZ(curr, name, params);
newDate = addTZ(newDate, params);

// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
}
}

Expand All @@ -118,7 +127,7 @@
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
curr[name] = new Date(Date.UTC(
newDate = new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
Expand All @@ -128,7 +137,7 @@
));
// TODO add tz
} else {
curr[name] = new Date(
newDate = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
Expand All @@ -137,10 +146,14 @@
parseInt(comps[6], 10)
);
}
}

return addTZ(curr, name, params)
newDate = addTZ(newDate, params);
}


// Store as string - worst case scenario
return storeValParam(name)(newDate, curr)
}
}


Expand All @@ -166,7 +179,27 @@
}
}

var addFBType = function(fb, params){
// EXDATE is an entry that represents exceptions to a recurrence rule (ex: "repeat every day except on 7/4").
// There can be more than one of these in a calendar record, so we create an array of them.
// The index into the array is the ISO string of the date itself, for ease of use.
// i.e. You can check if ((curr.exdate != undefined) && (curr.exdate[date iso string] != undefined)) to see if a date is an exception.
var exdateParam = function (name) {
return function (val, params, curr) {
var exdate = new Array();
dateParam(name)(val, params, exdate);
curr[name] = curr[name] || [];
curr[name][exdate[name].toISOString()] = exdate[name];
return curr;
}
}

// RECURRENCE-ID is the ID of a specific recurrence within a recurrence rule.
// TODO: It's also possible for it to have a range, like "THISANDPRIOR", "THISANDFUTURE". This isn't currently handled.
var recurrenceParam = function (name) {
return dateParam(name);
}

var addFBType = function (fb, params) {
var p = parseParams(params);

if (params && p){
Expand Down Expand Up @@ -226,7 +259,47 @@
var par = stack.pop()

if (curr.uid)
par[curr.uid] = curr
{
// If this is the first time we run into this UID, just save it.
if (par[curr.uid] === undefined)
{
par[curr.uid] = curr
}
else
{
// If we have multiple ical entries with the same UID, it's either going to be a
// modification to a recurrence (RECURRENCE-ID), and/or a significant modification
// to the entry (SEQUENCE).

// TODO: Look into proper sequence logic.

// If we have recurrence-id entries, list them as an array of recurrences keyed off of recurrence-id.
// To use - as you're running through the dates of an rrule, you can try looking it up in the recurrences
// array. If it exists, then use the data from the calendar object in the recurrence instead of the parent
// for that day.

var parent = par[curr.uid];
if (curr.recurrenceid != null) {
if (parent.recurrences === undefined) {
parent.recurrences = new Array();
}

// TODO: Is there ever a case where we have to worry about overwriting an existing entry here?

parent.recurrences[curr.recurrenceid.toISOString()] = curr;
}
else
{
// If we have the same UID as an existing record, and it *isn't* a specific recurrence ID,
// not quite sure what the correct behaviour should be. For now, just take the new information
// and merge it with the old record by overwriting only the fields that appear in the new record.
var key;
for (key in curr) {
par[key] = curr[key];
}
}
}
}
else
par[Math.random()*100000] = curr // Randomly assign ID : TODO - use true GUID

Expand All @@ -247,6 +320,12 @@
, 'COMPLETED': dateParam('completed')
, 'CATEGORIES': categoriesParam('categories')
, 'FREEBUSY': freebusyParam('freebusy')
, 'DTSTAMP': dateParam('dtstamp')
, 'EXDATE': exdateParam('exdate')
, 'CREATED': dateParam('created')
, 'LAST-MODIFIED': dateParam('lastmodified')
, 'RECURRENCE-ID': recurrenceParam('recurrenceid')

},


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"dependencies": {
"request": "2.68.0",
"rrule": "2.0.0"
"rrule": "2.1.0"
},
"devDependencies": {
"vows": "0.7.0",
Expand Down
35 changes: 32 additions & 3 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ vows.describe('node-ical').addBatch({
}
}

, 'with test6.ics (testing assembly.org)' : {
, 'with test6.ics (testing assembly.org)': {
topic: function () {
return ical.parseFile('./test/test6.ics')
}
Expand Down Expand Up @@ -370,9 +370,38 @@ vows.describe('node-ical').addBatch({
assert.equal(topic.end.getUTCMinutes(), 00);
}
}
},
}

, 'with test12.ics (testing recurrences and exdates)': {
topic: function () {
return ical.parseFile('./test/test12.ics')
}
, 'event with rrule': {
topic: function (events) {
return _.select(_.values(events), function (x) {
return x.uid === '0000001';
})[0];
}
, "Has an RRULE": function (topic) {
assert.notEqual(topic.rrule, undefined);
}
, "Has summary Treasure Hunting": function (topic) {
assert.equal(topic.summary, 'Treasure Hunting');
}
, "Has two EXDATES": function (topic) {
assert.notEqual(topic.exdate, undefined);
assert.notEqual(topic.exdate[new Date(2015, 06, 08, 12, 0, 0).toISOString()], undefined);
assert.notEqual(topic.exdate[new Date(2015, 06, 10, 12, 0, 0).toISOString()], undefined);
}
, "Has a RECURRENCE-ID override": function (topic) {
assert.notEqual(topic.recurrences, undefined);
assert.notEqual(topic.recurrences[new Date(2015, 06, 07, 12, 0, 0).toISOString()], undefined);
assert.equal(topic.recurrences[new Date(2015, 06, 07, 12, 0, 0).toISOString()].summary, 'More Treasure Hunting');
}
}
}

'url request errors' : {
, 'url request errors' : {
topic : function () {
ical.fromURL('http://not.exist/', {}, this.callback);
}
Expand Down
19 changes: 19 additions & 0 deletions test/test12.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
BEGIN:VCALENDAR
BEGIN:VEVENT
UID:0000001
SUMMARY:Treasure Hunting
DTSTART;TZID=America/Los_Angeles:20150706T120000
DTEND;TZID=America/Los_Angeles:20150706T130000
RRULE:FREQ=DAILY;COUNT=10
EXDATE;TZID=America/Los_Angeles:20150708T120000
EXDATE;TZID=America/Los_Angeles:20150710T120000
END:VEVENT
BEGIN:VEVENT
UID:0000001
SUMMARY:More Treasure Hunting
LOCATION:The other island
DTSTART;TZID=America/Los_Angeles:20150709T150000
DTEND;TZID=America/Los_Angeles:20150707T160000
RECURRENCE-ID;TZID=America/Los_Angeles:20150707T120000
END:VEVENT
END:VCALENDAR