Skip to content

Commit

Permalink
Merge pull request #64 from mbalfour/master
Browse files Browse the repository at this point in the history
Fixes for Recurrence Rules
  • Loading branch information
peterbraden authored Jul 19, 2016
2 parents fd4be74 + b9f2e93 commit abca2cb
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 37 deletions.
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

0 comments on commit abca2cb

Please sign in to comment.