Commit f69806a1 authored by Sebastian's avatar Sebastian
Browse files

Merge branch 'develop'

parents 6280432e 721b9986
...@@ -271,7 +271,13 @@ Empty the Calender. ...@@ -271,7 +271,13 @@ Empty the Calender.
#### uid([_String_|_Number_ uid]) or id([_String_|_Number_ id]) #### uid([_String_|_Number_ uid]) or id([_String_|_Number_ id])
Use this method to set the event's ID. If not set, an UID will be generated randomly. Use this method to set the event's ID. If not set, an UID will be generated randomly. When output, the ID will be suffixed with '@' + your calendar's domain.
#### sequence([_Number_ sequence])
Use this method to set the event's revision sequence number of the
calendar component within a sequence of revisions.
#### start([_Date_ start]) #### start([_Date_ start])
...@@ -284,6 +290,13 @@ Appointment date of beginning as Date object. This is required for all events! ...@@ -284,6 +290,13 @@ Appointment date of beginning as Date object. This is required for all events!
Appointment date of end as Date object. Appointment date of end as Date object.
#### timezone([_String_ timezone])
Use this method to set your event's timezone using the TZID property parameter on start and end dates, as per [date-time form #3 in section 3.3.5 of RFC 554](https://tools.ietf.org/html/rfc5545#section-3.3.5).
This and the 'floating' flag (see below) are mutually exclusive, and setting a timezone will unset the 'floating' flag. If neither 'timezone' nor 'floating' are set, the date will be output with in UTC format (see [date-time form #2 in section 3.3.5 of RFC 554](https://tools.ietf.org/html/rfc5545#section-3.3.5)).
#### timestamp([_Date_ stamp]) or stamp([_Date_ stamp]) #### timestamp([_Date_ stamp]) or stamp([_Date_ stamp])
Appointment date of creation as Date object. Default to `new Date()`. Appointment date of creation as Date object. Default to `new Date()`.
...@@ -295,6 +308,7 @@ When allDay == true -> appointment is for the whole day ...@@ -295,6 +308,7 @@ When allDay == true -> appointment is for the whole day
#### floating([_Boolean_ floating]) #### floating([_Boolean_ floating])
Appointment is a "floating" time. From [section 3.3.12 of RFC 554](https://tools.ietf.org/html/rfc5545#section-3.3.12): Appointment is a "floating" time. From [section 3.3.12 of RFC 554](https://tools.ietf.org/html/rfc5545#section-3.3.12):
> Time values of this type are said to be "floating" and are not > Time values of this type are said to be "floating" and are not
...@@ -305,8 +319,11 @@ Appointment is a "floating" time. From [section 3.3.12 of RFC 554](https://tools ...@@ -305,8 +319,11 @@ Appointment is a "floating" time. From [section 3.3.12 of RFC 554](https://tools
> AM to 1:00 PM every day, no matter which time zone the person is > AM to 1:00 PM every day, no matter which time zone the person is
> in. In these cases, a local time can be specified. > in. In these cases, a local time can be specified.
This and the 'timezone' setting (see above) are mutually exclusive, and setting the floating flag will unset the 'timezone'. If neither 'timezone' nor 'floating' are set, the date will be output with in UTC format (see [date-time form #2 in section 3.3.5 of RFC 554](https://tools.ietf.org/html/rfc5545#section-3.3.5)).
#### repeating([_Object_ repeating]) #### repeating([_Object_ repeating])
Appointment is a repeating event Appointment is a repeating event
```javascript ```javascript
......
...@@ -27,6 +27,23 @@ module.exports.formatDate = function formatDate(d, dateonly, floating) { ...@@ -27,6 +27,23 @@ module.exports.formatDate = function formatDate(d, dateonly, floating) {
return s; return s;
}; };
// For information about this format, see RFC 5545, section 3.3.5
// https://tools.ietf.org/html/rfc5545#section-3.3.5
module.exports.formatDateTZ = function formatDateTZ(property, date, eventData) {
var tzParam = '';
var floating = eventData.floating;
if(eventData.timezone) {
tzParam = ';TZID=' + eventData.timezone;
// This isn't a 'floating' event because it has a timezone;
// but we use it to omit the 'Z' UTC specifier in formatDate()
floating = true;
}
return property + tzParam + ':' + module.exports.formatDate(date, false, floating);
};
module.exports.escape = function escape(str) { module.exports.escape = function escape(str) {
return str.replace(/[\\;,"]/g, function(match) { return str.replace(/[\\;,"]/g, function(match) {
return '\\' + match; return '\\' + match;
......
...@@ -123,10 +123,10 @@ var ICalCalendar = function(_data) { ...@@ -123,10 +123,10 @@ var ICalCalendar = function(_data) {
* string like "//sebbo.net//ical-generator//EN" or an * string like "//sebbo.net//ical-generator//EN" or an
* object like * object like
* { * {
* "company": "sebbo.net", * "company": "sebbo.net",
* "product": "ical-generator" * "product": "ical-generator"
* "language": "EN" * "language": "EN"
* } * }
* *
* `language` is optional and defaults to `EN`. * `language` is optional and defaults to `EN`.
* *
...@@ -285,11 +285,6 @@ var ICalCalendar = function(_data) { ...@@ -285,11 +285,6 @@ var ICalCalendar = function(_data) {
throw 'event.start is a mandatory item!'; throw 'event.start is a mandatory item!';
} }
// validation: end
if(!event.end) {
throw 'event.end is a mandatory item!';
}
// validation: summary // validation: summary
if(!event.summary) { if(!event.summary) {
throw 'event.summary is a mandatory item!'; throw 'event.summary is a mandatory item!';
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* @constructor ICalEvent Event * @constructor ICalEvent Event
*/ */
var ICalEvent = function(_data) { var ICalEvent = function(_data) {
var attributes = ['id', 'uid', 'start', 'end', 'stamp', 'timestamp', 'allDay', 'floating', 'repeating', 'summary', 'location', 'description', 'organizer', 'attendees', 'alarms', 'method', 'status', 'url'], var attributes = ['id', 'uid', 'sequence', 'start', 'end', 'timezone', 'stamp', 'timestamp', 'allDay', 'floating', 'repeating', 'summary', 'location', 'description', 'organizer', 'attendees', 'alarms', 'method', 'status', 'url'],
vars, vars,
i, i,
data; data;
...@@ -20,8 +20,10 @@ var ICalEvent = function(_data) { ...@@ -20,8 +20,10 @@ var ICalEvent = function(_data) {
data = { data = {
id: ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).substr(-4), id: ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).substr(-4),
sequence: 0,
start: null, start: null,
end: null, end: null,
timezone: null,
stamp: new Date(), stamp: new Date(),
allDay: false, allDay: false,
floating: false, floating: false,
...@@ -65,6 +67,27 @@ var ICalEvent = function(_data) { ...@@ -65,6 +67,27 @@ var ICalEvent = function(_data) {
this.uid = this.id; this.uid = this.id;
/**
* Set/Get the event's SEQUENCE number
*
* @param {Number} sequence
* @since 0.2.6
* @returns {ICalEvent|Number}
*/
this.sequence = function(sequence) {
if(sequence === undefined) {
return data.sequence;
}
var s = parseInt(sequence, 10);
if(isNaN(s)) {
throw '`sequence` must be a number!';
}
data.sequence = s;
return this;
};
/** /**
* Set/Get the event's start date * Set/Get the event's start date
* *
...@@ -81,7 +104,7 @@ var ICalEvent = function(_data) { ...@@ -81,7 +104,7 @@ var ICalEvent = function(_data) {
if(typeof start === 'string') { if(typeof start === 'string') {
start = new Date(start); start = new Date(start);
} }
if(!(start instanceof Date) || !start.getTime()) { if(!(start instanceof Date) || isNaN(start.getTime())) {
throw '`start` must be a Date Object!'; throw '`start` must be a Date Object!';
} }
data.start = start; data.start = start;
...@@ -110,7 +133,7 @@ var ICalEvent = function(_data) { ...@@ -110,7 +133,7 @@ var ICalEvent = function(_data) {
if(typeof end === 'string') { if(typeof end === 'string') {
end = new Date(end); end = new Date(end);
} }
if(!(end instanceof Date) || !end.getTime()) { if(!(end instanceof Date) || isNaN(end.getTime())) {
throw '`end` must be a Date Object!'; throw '`end` must be a Date Object!';
} }
data.end = end; data.end = end;
...@@ -124,6 +147,26 @@ var ICalEvent = function(_data) { ...@@ -124,6 +147,26 @@ var ICalEvent = function(_data) {
}; };
/**
* Set/Get the event's timezone. This unsets the event's floating flag.
* Used on date properties
*
* @param [timezone] Timezone
* @example event.timezone('America/New_York');
* @since 0.2.6
* @returns {ICalEvent|String}
*/
this.timezone = function(timezone) {
if(!timezone) {
return data.timezone;
}
data.timezone = timezone.toString();
data.floating = false;
return this;
};
/** /**
* Set/Get the event's timestamp * Set/Get the event's timestamp
* *
...@@ -139,7 +182,7 @@ var ICalEvent = function(_data) { ...@@ -139,7 +182,7 @@ var ICalEvent = function(_data) {
if(typeof stamp === 'string') { if(typeof stamp === 'string') {
stamp = new Date(stamp); stamp = new Date(stamp);
} }
if(!(stamp instanceof Date) || !stamp.getTime()) { if(!(stamp instanceof Date) || isNaN(stamp.getTime())) {
throw '`stamp` must be a Date Object!'; throw '`stamp` must be a Date Object!';
} }
data.stamp = stamp; data.stamp = stamp;
...@@ -176,7 +219,7 @@ var ICalEvent = function(_data) { ...@@ -176,7 +219,7 @@ var ICalEvent = function(_data) {
/** /**
* Set/Get the event's floating flag * Set/Get the event's floating flag. This unsets the event's timezone.
* See https://tools.ietf.org/html/rfc5545#section-3.3.12 * See https://tools.ietf.org/html/rfc5545#section-3.3.12
* *
* @param {Boolean} floating * @param {Boolean} floating
...@@ -189,6 +232,7 @@ var ICalEvent = function(_data) { ...@@ -189,6 +232,7 @@ var ICalEvent = function(_data) {
} }
data.floating = !!floating; data.floating = !!floating;
data.timezone = null;
return this; return this;
}; };
...@@ -232,7 +276,7 @@ var ICalEvent = function(_data) { ...@@ -232,7 +276,7 @@ var ICalEvent = function(_data) {
if(typeof repeating.until === 'string') { if(typeof repeating.until === 'string') {
repeating.until = new Date(repeating.until); repeating.until = new Date(repeating.until);
} }
if(!(repeating.until instanceof Date) || !repeating.until.getTime()) { if(!(repeating.until instanceof Date) || isNaN(repeating.until.getTime())) {
throw '`repeating.until` must be a Date Object!'; throw '`repeating.until` must be a Date Object!';
} }
...@@ -566,16 +610,20 @@ var ICalEvent = function(_data) { ...@@ -566,16 +610,20 @@ var ICalEvent = function(_data) {
// DATE & TIME // DATE & TIME
g += 'BEGIN:VEVENT\r\n'; g += 'BEGIN:VEVENT\r\n';
g += 'UID:' + data.id + '@' + calendar.domain() + '\r\n'; g += 'UID:' + data.id + '@' + calendar.domain() + '\r\n';
// SEQUENCE
g += 'SEQUENCE:' + data.sequence + '\r\n';
g += 'DTSTAMP:' + tools.formatDate(data.stamp) + '\r\n'; g += 'DTSTAMP:' + tools.formatDate(data.stamp) + '\r\n';
if(data.allDay) { if(data.allDay) {
g += 'DTSTART;VALUE=DATE:' + tools.formatDate(data.start, true) + '\r\n'; g += 'DTSTART;VALUE=DATE:' + tools.formatDate(data.start, true) + '\r\n';
if (data.end) { if(data.end) {
g += 'DTEND;VALUE=DATE:' + tools.formatDate(data.end, true) + '\r\n'; g += 'DTEND;VALUE=DATE:' + tools.formatDate(data.end, true) + '\r\n';
} }
} else { } else {
g += 'DTSTART:' + tools.formatDate(data.start, false, data.floating) + '\r\n'; g += tools.formatDateTZ('DTSTART', data.start, data) + '\r\n';
if (data.end) { if(data.end) {
g += 'DTEND:' + tools.formatDate(data.end, false, data.floating) + '\r\n'; g += tools.formatDateTZ('DTEND', data.end, data) + '\r\n';
} }
} }
......
...@@ -3,6 +3,7 @@ VERSION:2.0 ...@@ -3,6 +3,7 @@ VERSION:2.0
PRODID:-//sebbo.net//ical-generator.tests//EN PRODID:-//sebbo.net//ical-generator.tests//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:123@sebbo.net UID:123@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131004T231500Z DTEND:20131004T231500Z
......
...@@ -3,6 +3,7 @@ VERSION:2.0 ...@@ -3,6 +3,7 @@ VERSION:2.0
PRODID:-//sebbo.net//ical-generator.tests//EN PRODID:-//sebbo.net//ical-generator.tests//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:123@sebbo.net UID:123@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131004T231500Z DTEND:20131004T231500Z
......
...@@ -3,6 +3,7 @@ VERSION:2.0 ...@@ -3,6 +3,7 @@ VERSION:2.0
PRODID:-//sebbo.net//ical-generator.tests//EN PRODID:-//sebbo.net//ical-generator.tests//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:123@sebbo.net UID:123@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART;VALUE=DATE:20131004 DTSTART;VALUE=DATE:20131004
DTEND;VALUE=DATE:20131006 DTEND;VALUE=DATE:20131006
......
...@@ -3,6 +3,7 @@ VERSION:2.0 ...@@ -3,6 +3,7 @@ VERSION:2.0
PRODID:-//sebbo.net//ical-generator.tests//EN PRODID:-//sebbo.net//ical-generator.tests//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:1@sebbo.net UID:1@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131006T231500Z DTEND:20131006T231500Z
...@@ -11,6 +12,7 @@ SUMMARY:repeating by month ...@@ -11,6 +12,7 @@ SUMMARY:repeating by month
END:VEVENT END:VEVENT
BEGIN:VEVENT BEGIN:VEVENT
UID:2@sebbo.net UID:2@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131006T231500Z DTEND:20131006T231500Z
...@@ -19,6 +21,7 @@ SUMMARY:repeating by day\, twice ...@@ -19,6 +21,7 @@ SUMMARY:repeating by day\, twice
END:VEVENT END:VEVENT
BEGIN:VEVENT BEGIN:VEVENT
UID:3@sebbo.net UID:3@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131006T231500Z DTEND:20131006T231500Z
......
...@@ -3,6 +3,7 @@ VERSION:2.0 ...@@ -3,6 +3,7 @@ VERSION:2.0
PRODID:-//sebbo.net//ical-generator.tests//EN PRODID:-//sebbo.net//ical-generator.tests//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:1@sebbo.net UID:1@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930 DTSTART:20131004T223930
DTEND:20131006T231500 DTEND:20131006T231500
......
...@@ -3,6 +3,7 @@ VERSION:2.0 ...@@ -3,6 +3,7 @@ VERSION:2.0
PRODID:-//sebbo.net//ical-generator.tests//EN PRODID:-//sebbo.net//ical-generator.tests//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:123@sebbo.net UID:123@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART;VALUE=DATE:20131004 DTSTART;VALUE=DATE:20131004
DTEND;VALUE=DATE:20131006 DTEND;VALUE=DATE:20131006
......
...@@ -3,6 +3,7 @@ VERSION:2.0 ...@@ -3,6 +3,7 @@ VERSION:2.0
PRODID:-//sebbo.net//ical-generator.tests//EN PRODID:-//sebbo.net//ical-generator.tests//EN
BEGIN:VEVENT BEGIN:VEVENT
UID:1@sebbo.net UID:1@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131006T231500Z DTEND:20131006T231500Z
...@@ -11,6 +12,7 @@ SUMMARY:repeating by month ...@@ -11,6 +12,7 @@ SUMMARY:repeating by month
END:VEVENT END:VEVENT
BEGIN:VEVENT BEGIN:VEVENT
UID:2@sebbo.net UID:2@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131006T231500Z DTEND:20131006T231500Z
...@@ -19,6 +21,7 @@ SUMMARY:repeating on Mo/We/Fr\, twice ...@@ -19,6 +21,7 @@ SUMMARY:repeating on Mo/We/Fr\, twice
END:VEVENT END:VEVENT
BEGIN:VEVENT BEGIN:VEVENT
UID:3@sebbo.net UID:3@sebbo.net
SEQUENCE:0
DTSTAMP:20131004T233453Z DTSTAMP:20131004T233453Z
DTSTART:20131004T223930Z DTSTART:20131004T223930Z
DTEND:20131006T231500Z DTEND:20131006T231500Z
......
...@@ -185,17 +185,6 @@ describe('ical-generator 0.1.x', function() { ...@@ -185,17 +185,6 @@ describe('ical-generator 0.1.x', function() {
}, /event\.start must be a Date Object/); }, /event\.start must be a Date Object/);
}); });
it('should throw error when no end time given', function() {
var generator = require(__dirname + '/../lib/index.js'),
cal = generator();
assert.throws(function() {
cal.addEvent({
start: new Date()
});
}, /event\.end is a mandatory item/);
});
it('should throw error when end time is not a date', function() { it('should throw error when end time is not a date', function() {
var generator = require(__dirname + '/../lib/index.js'), var generator = require(__dirname + '/../lib/index.js'),
cal = generator(); cal = generator();
......
...@@ -147,7 +147,7 @@ describe('ical-generator 0.2.x / ICalCalendar', function() { ...@@ -147,7 +147,7 @@ describe('ical-generator 0.2.x / ICalCalendar', function() {
assert.equal(cal.timezone(), 'Europe/Berlin'); assert.equal(cal.timezone(), 'Europe/Berlin');
}); });
it('should change something', function() { it('should make a difference to iCal output', function() {
var cal = ical().timezone('Europe/London'); var cal = ical().timezone('Europe/London');
cal.createEvent({ cal.createEvent({
start: new Date(), start: new Date(),
...@@ -156,6 +156,20 @@ describe('ical-generator 0.2.x / ICalCalendar', function() { ...@@ -156,6 +156,20 @@ describe('ical-generator 0.2.x / ICalCalendar', function() {
}); });
assert.ok(cal.toString().indexOf('Europe/London') > -1); assert.ok(cal.toString().indexOf('Europe/London') > -1);
}); });
it('should mark event as not floating', function() {
var cal = ical().timezone('Europe/London'),
evt = cal.createEvent({
start: new Date(),
end: new Date(new Date().getTime() + 3600000),
summary: 'Example Event',
floating: true
});
evt.timezone('Europe/Berlin');
assert.equal(evt.floating(), false);
});
}); });
describe('ttl()', function() { describe('ttl()', function() {
...@@ -216,6 +230,15 @@ describe('ical-generator 0.2.x / ICalCalendar', function() { ...@@ -216,6 +230,15 @@ describe('ical-generator 0.2.x / ICalCalendar', function() {
assert.equal(event.summary(), 'Patch-Day'); assert.equal(event.summary(), 'Patch-Day');
}); });
it('should not require optional parameters', function() {
assert.doesNotThrow(function() {
ical().addEvent({
start: new Date(),
summary: 'Patch-Day'
});
}, Error);
});
}); });
describe('events()', function() { describe('events()', function() {
...@@ -411,6 +434,44 @@ describe('ical-generator 0.2.x / ICalCalendar', function() { ...@@ -411,6 +434,44 @@ describe('ical-generator 0.2.x / ICalCalendar', function() {
}); });
}); });
describe('sequence()', function() {
it('setter should return this', function() {
var e = ical().createEvent();
assert.deepEqual(e, e.sequence(1));
});
it('getter should return value', function() {
var e = ical().createEvent().sequence(1048);
assert.equal(e.sequence(), 1048);
});
it('should change something', function() {
var cal = ical();
cal.createEvent({
sequenze: 512,
start: new Date(),
end: new Date(new Date().getTime() + 3600000),
summary: 'Example Event'
});
assert.ok(cal.toString().indexOf('512') > -1);
});
it('setter should throw error when sequence is not valid', function() {
var e = ical().createEvent();
assert.throws(function() {
e.sequence('hello');
}, /`sequence`/);
});
it('setter should work with 0', function() {
var e = ical().createEvent().sequence(12);
assert.equal(e.sequence(), 12);
e.sequence(0);
assert.equal(e.sequence(), 0);
});
});
describe('start()', function() { describe('start()', function() {
it('setter should return this', function() { it('setter should return this', function() {
var e = ical().createEvent(); var e = ical().createEvent();
...@@ -467,6 +528,30 @@ describe('ical-generator 0.2.x / ICalCalendar', function() { ...@@ -467,6 +528,30 @@ describe('ical-generator 0.2.x / ICalCalendar', function() {
}); });
}); });
describe('timezone()', function() {
it('setter should return this', function() {
var e = ical().createEvent();