From 4790ef4156bcecd620826dbcb30ace3698e8992d Mon Sep 17 00:00:00 2001 From: Connor Bode Date: Mon, 18 Aug 2014 18:50:17 -0400 Subject: [PATCH 01/11] fix lint --- ics.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ics.js b/ics.js index 083d963..439e783 100644 --- a/ics.js +++ b/ics.js @@ -51,7 +51,7 @@ var ics = function() { typeof stop === 'undefined' ) { return false; - }; + } //TODO add time and time zone? use moment to format? var start_date = new Date(begin); @@ -74,7 +74,7 @@ var ics = function() { // Since some calendars don't add 0 second events, we need to remove time if there is none... var start_time = ''; var end_time = ''; - if (start_minutes + start_seconds + end_minutes + end_seconds != 0) { + if (start_minutes + start_seconds + end_minutes + end_seconds !== 0) { start_time = 'T' + start_hours + start_minutes + start_seconds; end_time = 'T' + end_hours + end_minutes + end_seconds; } From 762d842513b514416ea47b09f9b0479c058b8353 Mon Sep 17 00:00:00 2001 From: Connor Bode Date: Mon, 18 Aug 2014 18:53:34 -0400 Subject: [PATCH 02/11] fix tests --- test/spec/test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/spec/test.js b/test/spec/test.js index 4db3f6c..4e35018 100644 --- a/test/spec/test.js +++ b/test/spec/test.js @@ -21,24 +21,24 @@ cal.addEvent('Christmas', 'Christian holiday celebrating the birth of Jesus Christ', 'Bethlehem', '12/25/2013', '12/25/2013'); assert.isArray(cal.events(), 'calendarEvents not an array'); assert.lengthOf(cal.events(), '1'); - assert.strictEqual(cal.events()[0], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART;VALUE=DATE:20131225\nDTEND;VALUE=DATE:20131225\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[0], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART;VALUE=DATE:20131225T000000\nDTEND;VALUE=DATE:20131225T000000\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); describe('Calendar String', function () { it('should have one event', function () { - assert.strictEqual(cal.calendar(), 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART;VALUE=DATE:20131225\nDTEND;VALUE=DATE:20131225\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT\nEND:VCALENDAR', 'calendar does not match'); + assert.strictEqual(cal.calendar(), 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART;VALUE=DATE:20131225T000000\nDTEND;VALUE=DATE:20131225T000000\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT\nEND:VCALENDAR', 'calendar does not match'); }); }); describe('Calendar String of Single Digit Months', function () { it('should have one event', function () { cal.addEvent('Easter', 'Christian holiday celebrating the resurrection of Jesus Christ', 'Jerusalem', '04/20/2014', '04/20/2014'); - assert.strictEqual(cal.events()[1], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the resurrection of Jesus Christ\nDTSTART;VALUE=DATE:20140420\nDTEND;VALUE=DATE:20140420\nLOCATION:Jerusalem\nSUMMARY;LANGUAGE=en-us:Easter\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[1], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the resurrection of Jesus Christ\nDTSTART;VALUE=DATE:20140420T000000\nDTEND;VALUE=DATE:20140420T000000\nLOCATION:Jerusalem\nSUMMARY;LANGUAGE=en-us:Easter\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); describe('Calendar String of Single Digit Day', function () { it('should have one event', function () { cal.addEvent('April Fools Day', 'What isn\'t is', 'America', '4/1/2014', '4/1/2014'); - assert.strictEqual(cal.events()[2], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:What isn\'t is\nDTSTART;VALUE=DATE:20140401\nDTEND;VALUE=DATE:20140401\nLOCATION:America\nSUMMARY;LANGUAGE=en-us:April Fools Day\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[2], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:What isn\'t is\nDTSTART;VALUE=DATE:20140401T000000\nDTEND;VALUE=DATE:20140401T000000\nLOCATION:America\nSUMMARY;LANGUAGE=en-us:April Fools Day\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); }); From c86d55ac398fc10c67c0639346b3dddcd5261ad7 Mon Sep 17 00:00:00 2001 From: Connor Bode Date: Mon, 18 Aug 2014 19:20:30 -0400 Subject: [PATCH 03/11] failing spec for recurring events --- test/spec/test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/spec/test.js b/test/spec/test.js index 4e35018..b3b8fa4 100644 --- a/test/spec/test.js +++ b/test/spec/test.js @@ -41,5 +41,17 @@ assert.strictEqual(cal.events()[2], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:What isn\'t is\nDTSTART;VALUE=DATE:20140401T000000\nDTEND;VALUE=DATE:20140401T000000\nLOCATION:America\nSUMMARY;LANGUAGE=en-us:April Fools Day\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); + + describe('Recurring Events', function () { + it('should add recurring events using frequency and until', function () { + cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', until: '08/18/2014'}); + assert.strictEqual(cal.events()[3], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + }); + + it('should add recurring events using interval and count', function () { + cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', interval: 2, count: 10}); + assert.strictEqual(cal.events()[4], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + }); + }); }); })(); From 64eaacd0b90f5470a0997a0caacf9f0f8a1861e7 Mon Sep 17 00:00:00 2001 From: Connor Bode Date: Mon, 18 Aug 2014 20:13:42 -0400 Subject: [PATCH 04/11] add functionality for recurring events --- ics.deps.min.js | 4 ++-- ics.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++-- ics.min.js | 4 ++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/ics.deps.min.js b/ics.deps.min.js index 6241080..ec99146 100644 --- a/ics.deps.min.js +++ b/ics.deps.min.js @@ -1,2 +1,2 @@ -/*! ics.js March 11, 2014 */ -var saveAs=saveAs||typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob&&navigator.msSaveOrOpenBlob.bind(navigator)||function(e){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=e.URL||e.webkitURL||e,i=t.createElementNS("http://www.w3.org/1999/xhtml","a"),s=!e.externalHost&&"download"in i,o=function(n){var r=t.createEvent("MouseEvents");r.initMouseEvent("click",true,false,e,0,0,0,0,0,false,false,false,false,0,null);n.dispatchEvent(r)},u=e.webkitRequestFileSystem,a=e.requestFileSystem||u||e.mozRequestFileSystem,f=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},l="application/octet-stream",c=0,h=[],p=function(){var e=h.length;while(e--){var t=h[e];if(typeof t==="string"){r.revokeObjectURL(t)}else{t.remove()}}h.length=0},d=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var i=e["on"+t[r]];if(typeof i==="function"){try{i.call(e,n||e)}catch(s){f(s)}}}},v=function(r,o){var f=this,p=r.type,v=false,m,g,y=function(){var e=n().createObjectURL(r);h.push(e);return e},b=function(){d(f,"writestart progress write writeend".split(" "))},w=function(){if(v||!m){m=y(r)}if(g){g.location.href=m}else{window.open(m,"_blank")}f.readyState=f.DONE;b()},E=function(e){return function(){if(f.readyState!==f.DONE){return e.apply(this,arguments)}}},S={create:true,exclusive:false},x;f.readyState=f.INIT;if(!o){o="download"}if(s){m=y(r);t=e.document;i=t.createElementNS("http://www.w3.org/1999/xhtml","a");i.href=m;i.download=o;var T=t.createEvent("MouseEvents");T.initMouseEvent("click",true,false,e,0,0,0,0,0,false,false,false,false,0,null);i.dispatchEvent(T);f.readyState=f.DONE;b();return}if(e.chrome&&p&&p!==l){x=r.slice||r.webkitSlice;r=x.call(r,0,r.size,l);v=true}if(u&&o!=="download"){o+=".download"}if(p===l||u){g=e}if(!a){w();return}c+=r.size;a(e.TEMPORARY,c,E(function(e){e.root.getDirectory("saved",S,E(function(e){var t=function(){e.getFile(o,S,E(function(e){e.createWriter(E(function(t){t.onwriteend=function(t){g.location.href=e.toURL();h.push(e);f.readyState=f.DONE;d(f,"writeend",t)};t.onerror=function(){var e=t.error;if(e.code!==e.ABORT_ERR){w()}};"writestart progress write abort".split(" ").forEach(function(e){t["on"+e]=f["on"+e]});t.write(r);f.abort=function(){t.abort();f.readyState=f.DONE};f.readyState=f.WRITING}),w)}),w)};e.getFile(o,{create:false},E(function(e){e.remove();t()}),E(function(e){if(e.code===e.NOT_FOUND_ERR){t()}else{w()}}))}),w)}),w)},m=v.prototype,g=function(e,t){return new v(e,t)};m.abort=function(){var e=this;e.readyState=e.DONE;d(e,"abort")};m.readyState=m.INIT=0;m.WRITING=1;m.DONE=2;m.error=m.onwritestart=m.onprogress=m.onwrite=m.onabort=m.onerror=m.onwriteend=null;e.addEventListener("unload",p,false);g.unload=function(){p();e.removeEventListener("unload",p,false)};return g}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined")module.exports=saveAs;if(!(typeof Blob==="function"||typeof Blob==="object")||typeof URL==="undefined")if((typeof Blob==="function"||typeof Blob==="object")&&typeof webkitURL!=="undefined")self.URL=webkitURL;else var Blob=function(e){"use strict";var t=e.BlobBuilder||e.WebKitBlobBuilder||e.MozBlobBuilder||e.MSBlobBuilder||function(e){var t=function(e){return Object.prototype.toString.call(e).match(/^\[object\s(.*)\]$/)[1]},n=function(){this.data=[]},r=function(t,n,r){this.data=t;this.size=t.length;this.type=n;this.encoding=r},i=n.prototype,s=r.prototype,o=e.FileReaderSync,u=function(e){this.code=this[this.name=e]},a=("NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "+"NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR").split(" "),f=a.length,l=e.URL||e.webkitURL||e,c=l.createObjectURL,h=l.revokeObjectURL,p=l,d=e.btoa,v=e.atob,m=e.ArrayBuffer,g=e.Uint8Array;r.fake=s.fake=true;while(f--){u.prototype[a[f]]=f+1}if(!l.createObjectURL){p=e.URL={}}p.createObjectURL=function(e){var t=e.type,n;if(t===null){t="application/octet-stream"}if(e instanceof r){n="data:"+t;if(e.encoding==="base64"){return n+";base64,"+e.data}else if(e.encoding==="URI"){return n+","+decodeURIComponent(e.data)}if(d){return n+";base64,"+d(e.data)}else{return n+","+encodeURIComponent(e.data)}}else if(c){return c.call(l,e)}};p.revokeObjectURL=function(e){if(e.substring(0,5)!=="data:"&&h){h.call(l,e)}};i.append=function(e){var n=this.data;if(g&&(e instanceof m||e instanceof g)){var i="",s=new g(e),a=0,f=s.length;for(;a1?t:this.data.length),n,this.encoding)};s.toString=function(){return"[object Blob]"};return n}(e);return function(n,r){var i=r?r.type||"":"";var s=new t;if(n){for(var o=0,u=n.length;o-1&&navigator.userAgent.indexOf("MSIE 10")==-1){console.log("Unsupported Browser");return}var e=navigator.appVersion.indexOf("Win")!==-1?"\r\n":"\n";var t=[];var n=["BEGIN:VCALENDAR","VERSION:2.0"].join(e);var r=e+"END:VCALENDAR";return{events:function(){return t},calendar:function(){return n+e+t.join(e)+r},addEvent:function(n,r,i,s,o){if(typeof n==="undefined"||typeof r==="undefined"||typeof i==="undefined"||typeof s==="undefined"||typeof o==="undefined"){return false}var u=new Date(s);var a=new Date(o);var f=("0000"+u.getFullYear().toString()).slice(-4);var l=("00"+(u.getMonth()+1).toString()).slice(-2);var c=("00"+u.getDate().toString()).slice(-2);var h=("00"+u.getHours().toString()).slice(-2);var p=("00"+u.getMinutes().toString()).slice(-2);var d=("00"+u.getMinutes().toString()).slice(-2);var v=("0000"+a.getFullYear().toString()).slice(-4);var m=("00"+(a.getMonth()+1).toString()).slice(-2);var g=("00"+a.getDate().toString()).slice(-2);var y=("00"+a.getHours().toString()).slice(-2);var b=("00"+a.getMinutes().toString()).slice(-2);var w=("00"+a.getMinutes().toString()).slice(-2);var E="";var S="";if(p+d+b+w!=0){E="T"+h+p+d;S="T"+y+b+w}var x=f+l+c+E;var T=v+m+g+S;var N=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+r,"DTSTART;VALUE=DATE:"+x,"DTEND;VALUE=DATE:"+T,"LOCATION:"+i,"SUMMARY;LANGUAGE=en-us:"+n,"TRANSP:TRANSPARENT","END:VEVENT"].join(e);t.push(N);return N},download:function(i,s){if(t.length<1){return false}s=typeof s!=="undefined"?s:".ics";i=typeof i!=="undefined"?i:"calendar";var o=n+e+t.join(e)+r;var u;if(navigator.userAgent.indexOf("MSIE 10")===-1){u=new Blob([o])}else{var a=new BlobBuilder;a.append(o);u=a.getBlob("text/x-vCalendar;charset="+document.characterSet)}saveAs(u,i+s);return o}}} +/*! ics.js Mon Aug 18 2014 20:11:18 */ +!function(a){"use strict";if(a.URL=a.URL||a.webkitURL,a.Blob&&a.URL)try{return void new Blob}catch(b){}var c=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||function(a){var b=function(a){return Object.prototype.toString.call(a).match(/^\[object\s(.*)\]$/)[1]},c=function(){this.data=[]},d=function(a,b,c){this.data=a,this.size=a.length,this.type=b,this.encoding=c},e=c.prototype,f=d.prototype,g=a.FileReaderSync,h=function(a){this.code=this[this.name=a]},i="NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR".split(" "),j=i.length,k=a.URL||a.webkitURL||a,l=k.createObjectURL,m=k.revokeObjectURL,n=k,o=a.btoa,p=a.atob,q=a.ArrayBuffer,r=a.Uint8Array,s=/^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/;for(d.fake=f.fake=!0;j--;)h.prototype[i[j]]=j+1;return k.createObjectURL||(n=a.URL=function(a){var b,c=document.createElementNS("http://www.w3.org/1999/xhtml","a");return c.href=a,"origin"in c||("data:"===c.protocol.toLowerCase()?c.origin=null:(b=a.match(s),c.origin=b&&b[1])),c}),n.createObjectURL=function(a){var b,c=a.type;return null===c&&(c="application/octet-stream"),a instanceof d?(b="data:"+c,"base64"===a.encoding?b+";base64,"+a.data:"URI"===a.encoding?b+","+decodeURIComponent(a.data):o?b+";base64,"+o(a.data):b+","+encodeURIComponent(a.data)):l?l.call(k,a):void 0},n.revokeObjectURL=function(a){"data:"!==a.substring(0,5)&&m&&m.call(k,a)},e.append=function(a){var c=this.data;if(r&&(a instanceof q||a instanceof r)){for(var e="",f=new r(a),i=0,j=f.length;j>i;i++)e+=String.fromCharCode(f[i]);c.push(e)}else if("Blob"===b(a)||"File"===b(a)){if(!g)throw new h("NOT_READABLE_ERR");var k=new g;c.push(k.readAsBinaryString(a))}else a instanceof d?"base64"===a.encoding&&p?c.push(p(a.data)):"URI"===a.encoding?c.push(decodeURIComponent(a.data)):"raw"===a.encoding&&c.push(a.data):("string"!=typeof a&&(a+=""),c.push(unescape(encodeURIComponent(a))))},e.getBlob=function(a){return arguments.length||(a=null),new d(this.data.join(""),a,"raw")},e.toString=function(){return"[object BlobBuilder]"},f.slice=function(a,b,c){var e=arguments.length;return 3>e&&(c=null),new d(this.data.slice(a,e>1?b:this.data.length),c,this.encoding)},f.toString=function(){return"[object Blob]"},f.close=function(){this.size=0,delete this.data},c}(a);a.Blob=function(a,b){var d=b?b.type||"":"",e=new c;if(a)for(var f=0,g=a.length;g>f;f++)e.append(a[f]);return e.getBlob(d)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content||this);var saveAs=saveAs||"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob&&navigator.msSaveOrOpenBlob.bind(navigator)||function(a){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var b=a.document,c=function(){return a.URL||a.webkitURL||a},d=b.createElementNS("http://www.w3.org/1999/xhtml","a"),e=!a.externalHost&&"download"in d,f=function(c){var d=b.createEvent("MouseEvents");d.initMouseEvent("click",!0,!1,a,0,0,0,0,0,!1,!1,!1,!1,0,null),c.dispatchEvent(d)},g=a.webkitRequestFileSystem,h=a.requestFileSystem||g||a.mozRequestFileSystem,i=function(b){(a.setImmediate||a.setTimeout)(function(){throw b},0)},j="application/octet-stream",k=0,l=10,m=function(b){var d=function(){"string"==typeof b?c().revokeObjectURL(b):b.remove()};a.chrome?d():setTimeout(d,l)},n=function(a,b,c){b=[].concat(b);for(var d=b.length;d--;){var e=a["on"+b[d]];if("function"==typeof e)try{e.call(a,c||a)}catch(f){i(f)}}},o=function(b,i){var l,o,p,q=this,r=b.type,s=!1,t=function(){n(q,"writestart progress write writeend".split(" "))},u=function(){if((s||!l)&&(l=c().createObjectURL(b)),o)o.location.href=l;else{var d=a.open(l,"_blank");void 0==d&&"undefined"!=typeof safari&&(a.location.href=l)}q.readyState=q.DONE,t(),m(l)},v=function(a){return function(){return q.readyState!==q.DONE?a.apply(this,arguments):void 0}},w={create:!0,exclusive:!1};return q.readyState=q.INIT,i||(i="download"),e?(l=c().createObjectURL(b),d.href=l,d.download=i,f(d),q.readyState=q.DONE,t(),void m(l)):(a.chrome&&r&&r!==j&&(p=b.slice||b.webkitSlice,b=p.call(b,0,b.size,j),s=!0),g&&"download"!==i&&(i+=".download"),(r===j||g)&&(o=a),h?(k+=b.size,void h(a.TEMPORARY,k,v(function(a){a.root.getDirectory("saved",w,v(function(a){var c=function(){a.getFile(i,w,v(function(a){a.createWriter(v(function(c){c.onwriteend=function(b){o.location.href=a.toURL(),q.readyState=q.DONE,n(q,"writeend",b),m(a)},c.onerror=function(){var a=c.error;a.code!==a.ABORT_ERR&&u()},"writestart progress write abort".split(" ").forEach(function(a){c["on"+a]=q["on"+a]}),c.write(b),q.abort=function(){c.abort(),q.readyState=q.DONE},q.readyState=q.WRITING}),u)}),u)};a.getFile(i,{create:!1},v(function(a){a.remove(),c()}),v(function(a){a.code===a.NOT_FOUND_ERR?c():u()}))}),u)}),u)):void u())},p=o.prototype,q=function(a,b){return new o(a,b)};return p.abort=function(){var a=this;a.readyState=a.DONE,n(a,"abort")},p.readyState=p.INIT=0,p.WRITING=1,p.DONE=2,p.error=p.onwritestart=p.onprogress=p.onwrite=p.onabort=p.onerror=p.onwriteend=null,q}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&null!==module?module.exports=saveAs:"undefined"!=typeof define&&null!==define&&null!=define.amd&&define([],function(){return saveAs});var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR";return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,e,f,g,h){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof e||"undefined"==typeof f||"undefined"==typeof g)return!1;if(h&&!h.rule){if("YEARLY"!==h.freq&&"MONTHLY"!==h.freq&&"WEEKLY"!==h.freq&&"DAILY"!==h.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(h.until&&isNaN(Date.parse(h.until)))throw"Recurrence rule 'until' must be a valid date string";if(h.interval&&isNaN(parseInt(h.interval)))throw"Recurrence rule 'interval' must be an integer";if(h.count&&isNaN(parseInt(h.count)))throw"Recurrence rule 'count' must be an integer"}var i=new Date(f),j=new Date(g),k=("0000"+i.getFullYear().toString()).slice(-4),l=("00"+(i.getMonth()+1).toString()).slice(-2),m=("00"+i.getDate().toString()).slice(-2),n=("00"+i.getHours().toString()).slice(-2),o=("00"+i.getMinutes().toString()).slice(-2),p=("00"+i.getMinutes().toString()).slice(-2),q=("0000"+j.getFullYear().toString()).slice(-4),r=("00"+(j.getMonth()+1).toString()).slice(-2),s=("00"+j.getDate().toString()).slice(-2),t=("00"+j.getHours().toString()).slice(-2),u=("00"+j.getMinutes().toString()).slice(-2),v=("00"+j.getMinutes().toString()).slice(-2),w="",x="";o+p+u+v!==0&&(w="T"+n+o+p,x="T"+t+u+v);var y,z=k+l+m+w,A=q+r+s+x;if(h)if(h.rule)y=h.rule;else{if(y="RRULE:FREQ="+h.freq,h.until){var B=new Date(Date.parse(h.until)).toISOString();y+=";UNTIL="+B.substring(0,B.length-13).replace(/[-]/g,"")+"000000Z"}h.interval&&(y+=";INTERVAL="+h.interval),h.count&&(y+=";COUNT="+h.count)}var C=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+z,"DTEND;VALUE=DATE:"+A,"LOCATION:"+e,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return y&&C.splice(4,0,y),C=C.join(a),b.push(C),C},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file diff --git a/ics.js b/ics.js index 439e783..a1249f7 100644 --- a/ics.js +++ b/ics.js @@ -42,7 +42,7 @@ var ics = function() { * @param {string} begin Beginning date of event * @param {string} stop Ending date of event */ - 'addEvent': function(subject, description, location, begin, stop) { + 'addEvent': function(subject, description, location, begin, stop, rrule) { // I'm not in the mood to make these optional... So they are all required if (typeof subject === 'undefined' || typeof description === 'undefined' || @@ -53,6 +53,33 @@ var ics = function() { return false; } + // validate rrule + if (rrule) { + if (!rrule.rule) { + if (rrule.freq !== 'YEARLY' && rrule.freq !== 'MONTHLY' && rrule.freq !== 'WEEKLY' && rrule.freq !== 'DAILY') { + throw "Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'"; + } + + if (rrule.until) { + if (isNaN(Date.parse(rrule.until))) { + throw "Recurrence rule 'until' must be a valid date string"; + } + } + + if (rrule.interval) { + if (isNaN(parseInt(rrule.interval))) { + throw "Recurrence rule 'interval' must be an integer"; + } + } + + if (rrule.count) { + if (isNaN(parseInt(rrule.count))) { + throw "Recurrence rule 'count' must be an integer"; + } + } + } + } + //TODO add time and time zone? use moment to format? var start_date = new Date(begin); var end_date = new Date(stop); @@ -82,6 +109,29 @@ var ics = function() { var start = start_year + start_month + start_day + start_time; var end = end_year + end_month + end_day + end_time; + // recurrence rule vars + var rruleString; + if (rrule) { + if (rrule.rule) { + rruleString = rrule.rule; + } else { + rruleString = 'RRULE:FREQ=' + rrule.freq; + + if (rrule.until) { + var uDate = new Date(Date.parse(rrule.until)).toISOString(); + rruleString += ';UNTIL=' + uDate.substring(0, uDate.length - 13).replace(/[-]/g, '') + '000000Z'; + } + + if (rrule.interval) { + rruleString += ';INTERVAL=' + rrule.interval; + } + + if (rrule.count) { + rruleString += ';COUNT=' + rrule.count; + } + } + } + var calendarEvent = [ 'BEGIN:VEVENT', 'CLASS:PUBLIC', @@ -92,7 +142,13 @@ var ics = function() { 'SUMMARY;LANGUAGE=en-us:' + subject, 'TRANSP:TRANSPARENT', 'END:VEVENT' - ].join(SEPARATOR); + ]; + + if (rruleString) { + calendarEvent.splice(4, 0, rruleString); + } + + calendarEvent = calendarEvent.join(SEPARATOR); calendarEvents.push(calendarEvent); return calendarEvent; diff --git a/ics.min.js b/ics.min.js index ffa9994..b99aecb 100644 --- a/ics.min.js +++ b/ics.min.js @@ -1,2 +1,2 @@ -/*! ics.js March 11, 2014 */ -var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&navigator.userAgent.indexOf("MSIE 10")==-1){console.log("Unsupported Browser");return}var e=navigator.appVersion.indexOf("Win")!==-1?"\r\n":"\n";var t=[];var n=["BEGIN:VCALENDAR","VERSION:2.0"].join(e);var r=e+"END:VCALENDAR";return{events:function(){return t},calendar:function(){return n+e+t.join(e)+r},addEvent:function(n,r,i,s,o){if(typeof n==="undefined"||typeof r==="undefined"||typeof i==="undefined"||typeof s==="undefined"||typeof o==="undefined"){return false}var u=new Date(s);var a=new Date(o);var f=("0000"+u.getFullYear().toString()).slice(-4);var l=("00"+(u.getMonth()+1).toString()).slice(-2);var c=("00"+u.getDate().toString()).slice(-2);var h=("00"+u.getHours().toString()).slice(-2);var p=("00"+u.getMinutes().toString()).slice(-2);var d=("00"+u.getMinutes().toString()).slice(-2);var v=("0000"+a.getFullYear().toString()).slice(-4);var m=("00"+(a.getMonth()+1).toString()).slice(-2);var g=("00"+a.getDate().toString()).slice(-2);var y=("00"+a.getHours().toString()).slice(-2);var b=("00"+a.getMinutes().toString()).slice(-2);var w=("00"+a.getMinutes().toString()).slice(-2);var E="";var S="";if(p+d+b+w!=0){E="T"+h+p+d;S="T"+y+b+w}var x=f+l+c+E;var T=v+m+g+S;var N=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+r,"DTSTART;VALUE=DATE:"+x,"DTEND;VALUE=DATE:"+T,"LOCATION:"+i,"SUMMARY;LANGUAGE=en-us:"+n,"TRANSP:TRANSPARENT","END:VEVENT"].join(e);t.push(N);return N},download:function(i,s){if(t.length<1){return false}s=typeof s!=="undefined"?s:".ics";i=typeof i!=="undefined"?i:"calendar";var o=n+e+t.join(e)+r;var u;if(navigator.userAgent.indexOf("MSIE 10")===-1){u=new Blob([o])}else{var a=new BlobBuilder;a.append(o);u=a.getBlob("text/x-vCalendar;charset="+document.characterSet)}saveAs(u,i+s);return o}}} +/*! ics.js Mon Aug 18 2014 20:11:18 */ +var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR";return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,e,f,g,h){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof e||"undefined"==typeof f||"undefined"==typeof g)return!1;if(h&&!h.rule){if("YEARLY"!==h.freq&&"MONTHLY"!==h.freq&&"WEEKLY"!==h.freq&&"DAILY"!==h.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(h.until&&isNaN(Date.parse(h.until)))throw"Recurrence rule 'until' must be a valid date string";if(h.interval&&isNaN(parseInt(h.interval)))throw"Recurrence rule 'interval' must be an integer";if(h.count&&isNaN(parseInt(h.count)))throw"Recurrence rule 'count' must be an integer"}var i=new Date(f),j=new Date(g),k=("0000"+i.getFullYear().toString()).slice(-4),l=("00"+(i.getMonth()+1).toString()).slice(-2),m=("00"+i.getDate().toString()).slice(-2),n=("00"+i.getHours().toString()).slice(-2),o=("00"+i.getMinutes().toString()).slice(-2),p=("00"+i.getMinutes().toString()).slice(-2),q=("0000"+j.getFullYear().toString()).slice(-4),r=("00"+(j.getMonth()+1).toString()).slice(-2),s=("00"+j.getDate().toString()).slice(-2),t=("00"+j.getHours().toString()).slice(-2),u=("00"+j.getMinutes().toString()).slice(-2),v=("00"+j.getMinutes().toString()).slice(-2),w="",x="";o+p+u+v!==0&&(w="T"+n+o+p,x="T"+t+u+v);var y,z=k+l+m+w,A=q+r+s+x;if(h)if(h.rule)y=h.rule;else{if(y="RRULE:FREQ="+h.freq,h.until){var B=new Date(Date.parse(h.until)).toISOString();y+=";UNTIL="+B.substring(0,B.length-13).replace(/[-]/g,"")+"000000Z"}h.interval&&(y+=";INTERVAL="+h.interval),h.count&&(y+=";COUNT="+h.count)}var C=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+z,"DTEND;VALUE=DATE:"+A,"LOCATION:"+e,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return y&&C.splice(4,0,y),C=C.join(a),b.push(C),C},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file From 75369c0184e5b6c52f471dba9add8de7d295c35e Mon Sep 17 00:00:00 2001 From: Connor Bode Date: Mon, 18 Aug 2014 20:21:28 -0400 Subject: [PATCH 05/11] update readme for recurring events --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index db5204f..3b250e7 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,22 @@ Simply use invoke the object and use the functions... `begin` and `end` need to be formatted in a way that is friendly to `Date()` +Recurring Events +---------------- +Recurring events can be added with the `rrule` object. + +`cal.addEvent(subject, description, location, begin, end, rrule)` + +The `rrule` object has the following properties: + +- `freq` : __Required.__ The frequency of event recurrence. Can be `DAILY`, `WEEKLY`, `MONTHLY`, or `YEARLY`. +- `until` : A date string representing the date on which to end repitition. Must be friendly to `Date()` +- `count` : Alternative to until. Repeat the event `count` times. Must be an integer +- `interval` : The interval of `freq` to recur at. For example, if `freq` is `WEEKLY` and `interval` is `2`, the event will repeat every 2 weeks. Must be an integer. + +The four properties described above are not exhaustive of recurrence rule capabilities. If extra functionality is required, you can set the `rrule.rule` property to a full recurrence rule string. In this case, none of the four properties described above are necessary. See [this page](http://www.kanzaki.com/docs/ical/rrule.html) for a description of recurrence rules. + + Example ------- * **[Demo](http://htmlpreview.github.io/?https://github.com/nwcell/ics.js/blob/master/demo/demo.html)** From 98b22dd60aa3ba9ada9cca6fabe9a7128d06e54d Mon Sep 17 00:00:00 2001 From: jmuia Date: Wed, 20 Aug 2014 14:46:26 -0400 Subject: [PATCH 06/11] add BYDAY property to recurrence events --- README.md | 1 + ics.deps.min.js | 4 ++-- ics.js | 26 ++++++++++++++++++++++++++ ics.min.js | 4 ++-- test/spec/test.js | 10 ++++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3b250e7..6b7d765 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ The `rrule` object has the following properties: - `until` : A date string representing the date on which to end repitition. Must be friendly to `Date()` - `count` : Alternative to until. Repeat the event `count` times. Must be an integer - `interval` : The interval of `freq` to recur at. For example, if `freq` is `WEEKLY` and `interval` is `2`, the event will repeat every 2 weeks. Must be an integer. +- `byday` : Which days of the week the event is to occur. An array containing any of `SU`, `MO`, `TU`, `WE`, `TH`, `FR`, `SA`. The four properties described above are not exhaustive of recurrence rule capabilities. If extra functionality is required, you can set the `rrule.rule` property to a full recurrence rule string. In this case, none of the four properties described above are necessary. See [this page](http://www.kanzaki.com/docs/ical/rrule.html) for a description of recurrence rules. diff --git a/ics.deps.min.js b/ics.deps.min.js index ec99146..f54cf17 100644 --- a/ics.deps.min.js +++ b/ics.deps.min.js @@ -1,2 +1,2 @@ -/*! ics.js Mon Aug 18 2014 20:11:18 */ -!function(a){"use strict";if(a.URL=a.URL||a.webkitURL,a.Blob&&a.URL)try{return void new Blob}catch(b){}var c=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||function(a){var b=function(a){return Object.prototype.toString.call(a).match(/^\[object\s(.*)\]$/)[1]},c=function(){this.data=[]},d=function(a,b,c){this.data=a,this.size=a.length,this.type=b,this.encoding=c},e=c.prototype,f=d.prototype,g=a.FileReaderSync,h=function(a){this.code=this[this.name=a]},i="NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR".split(" "),j=i.length,k=a.URL||a.webkitURL||a,l=k.createObjectURL,m=k.revokeObjectURL,n=k,o=a.btoa,p=a.atob,q=a.ArrayBuffer,r=a.Uint8Array,s=/^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/;for(d.fake=f.fake=!0;j--;)h.prototype[i[j]]=j+1;return k.createObjectURL||(n=a.URL=function(a){var b,c=document.createElementNS("http://www.w3.org/1999/xhtml","a");return c.href=a,"origin"in c||("data:"===c.protocol.toLowerCase()?c.origin=null:(b=a.match(s),c.origin=b&&b[1])),c}),n.createObjectURL=function(a){var b,c=a.type;return null===c&&(c="application/octet-stream"),a instanceof d?(b="data:"+c,"base64"===a.encoding?b+";base64,"+a.data:"URI"===a.encoding?b+","+decodeURIComponent(a.data):o?b+";base64,"+o(a.data):b+","+encodeURIComponent(a.data)):l?l.call(k,a):void 0},n.revokeObjectURL=function(a){"data:"!==a.substring(0,5)&&m&&m.call(k,a)},e.append=function(a){var c=this.data;if(r&&(a instanceof q||a instanceof r)){for(var e="",f=new r(a),i=0,j=f.length;j>i;i++)e+=String.fromCharCode(f[i]);c.push(e)}else if("Blob"===b(a)||"File"===b(a)){if(!g)throw new h("NOT_READABLE_ERR");var k=new g;c.push(k.readAsBinaryString(a))}else a instanceof d?"base64"===a.encoding&&p?c.push(p(a.data)):"URI"===a.encoding?c.push(decodeURIComponent(a.data)):"raw"===a.encoding&&c.push(a.data):("string"!=typeof a&&(a+=""),c.push(unescape(encodeURIComponent(a))))},e.getBlob=function(a){return arguments.length||(a=null),new d(this.data.join(""),a,"raw")},e.toString=function(){return"[object BlobBuilder]"},f.slice=function(a,b,c){var e=arguments.length;return 3>e&&(c=null),new d(this.data.slice(a,e>1?b:this.data.length),c,this.encoding)},f.toString=function(){return"[object Blob]"},f.close=function(){this.size=0,delete this.data},c}(a);a.Blob=function(a,b){var d=b?b.type||"":"",e=new c;if(a)for(var f=0,g=a.length;g>f;f++)e.append(a[f]);return e.getBlob(d)}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content||this);var saveAs=saveAs||"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob&&navigator.msSaveOrOpenBlob.bind(navigator)||function(a){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var b=a.document,c=function(){return a.URL||a.webkitURL||a},d=b.createElementNS("http://www.w3.org/1999/xhtml","a"),e=!a.externalHost&&"download"in d,f=function(c){var d=b.createEvent("MouseEvents");d.initMouseEvent("click",!0,!1,a,0,0,0,0,0,!1,!1,!1,!1,0,null),c.dispatchEvent(d)},g=a.webkitRequestFileSystem,h=a.requestFileSystem||g||a.mozRequestFileSystem,i=function(b){(a.setImmediate||a.setTimeout)(function(){throw b},0)},j="application/octet-stream",k=0,l=10,m=function(b){var d=function(){"string"==typeof b?c().revokeObjectURL(b):b.remove()};a.chrome?d():setTimeout(d,l)},n=function(a,b,c){b=[].concat(b);for(var d=b.length;d--;){var e=a["on"+b[d]];if("function"==typeof e)try{e.call(a,c||a)}catch(f){i(f)}}},o=function(b,i){var l,o,p,q=this,r=b.type,s=!1,t=function(){n(q,"writestart progress write writeend".split(" "))},u=function(){if((s||!l)&&(l=c().createObjectURL(b)),o)o.location.href=l;else{var d=a.open(l,"_blank");void 0==d&&"undefined"!=typeof safari&&(a.location.href=l)}q.readyState=q.DONE,t(),m(l)},v=function(a){return function(){return q.readyState!==q.DONE?a.apply(this,arguments):void 0}},w={create:!0,exclusive:!1};return q.readyState=q.INIT,i||(i="download"),e?(l=c().createObjectURL(b),d.href=l,d.download=i,f(d),q.readyState=q.DONE,t(),void m(l)):(a.chrome&&r&&r!==j&&(p=b.slice||b.webkitSlice,b=p.call(b,0,b.size,j),s=!0),g&&"download"!==i&&(i+=".download"),(r===j||g)&&(o=a),h?(k+=b.size,void h(a.TEMPORARY,k,v(function(a){a.root.getDirectory("saved",w,v(function(a){var c=function(){a.getFile(i,w,v(function(a){a.createWriter(v(function(c){c.onwriteend=function(b){o.location.href=a.toURL(),q.readyState=q.DONE,n(q,"writeend",b),m(a)},c.onerror=function(){var a=c.error;a.code!==a.ABORT_ERR&&u()},"writestart progress write abort".split(" ").forEach(function(a){c["on"+a]=q["on"+a]}),c.write(b),q.abort=function(){c.abort(),q.readyState=q.DONE},q.readyState=q.WRITING}),u)}),u)};a.getFile(i,{create:!1},v(function(a){a.remove(),c()}),v(function(a){a.code===a.NOT_FOUND_ERR?c():u()}))}),u)}),u)):void u())},p=o.prototype,q=function(a,b){return new o(a,b)};return p.abort=function(){var a=this;a.readyState=a.DONE,n(a,"abort")},p.readyState=p.INIT=0,p.WRITING=1,p.DONE=2,p.error=p.onwritestart=p.onprogress=p.onwrite=p.onabort=p.onerror=p.onwriteend=null,q}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&null!==module?module.exports=saveAs:"undefined"!=typeof define&&null!==define&&null!=define.amd&&define([],function(){return saveAs});var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR";return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,e,f,g,h){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof e||"undefined"==typeof f||"undefined"==typeof g)return!1;if(h&&!h.rule){if("YEARLY"!==h.freq&&"MONTHLY"!==h.freq&&"WEEKLY"!==h.freq&&"DAILY"!==h.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(h.until&&isNaN(Date.parse(h.until)))throw"Recurrence rule 'until' must be a valid date string";if(h.interval&&isNaN(parseInt(h.interval)))throw"Recurrence rule 'interval' must be an integer";if(h.count&&isNaN(parseInt(h.count)))throw"Recurrence rule 'count' must be an integer"}var i=new Date(f),j=new Date(g),k=("0000"+i.getFullYear().toString()).slice(-4),l=("00"+(i.getMonth()+1).toString()).slice(-2),m=("00"+i.getDate().toString()).slice(-2),n=("00"+i.getHours().toString()).slice(-2),o=("00"+i.getMinutes().toString()).slice(-2),p=("00"+i.getMinutes().toString()).slice(-2),q=("0000"+j.getFullYear().toString()).slice(-4),r=("00"+(j.getMonth()+1).toString()).slice(-2),s=("00"+j.getDate().toString()).slice(-2),t=("00"+j.getHours().toString()).slice(-2),u=("00"+j.getMinutes().toString()).slice(-2),v=("00"+j.getMinutes().toString()).slice(-2),w="",x="";o+p+u+v!==0&&(w="T"+n+o+p,x="T"+t+u+v);var y,z=k+l+m+w,A=q+r+s+x;if(h)if(h.rule)y=h.rule;else{if(y="RRULE:FREQ="+h.freq,h.until){var B=new Date(Date.parse(h.until)).toISOString();y+=";UNTIL="+B.substring(0,B.length-13).replace(/[-]/g,"")+"000000Z"}h.interval&&(y+=";INTERVAL="+h.interval),h.count&&(y+=";COUNT="+h.count)}var C=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+z,"DTEND;VALUE=DATE:"+A,"LOCATION:"+e,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return y&&C.splice(4,0,y),C=C.join(a),b.push(C),C},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file +/*! ics.js Wed Aug 20 2014 14:44:48 */ +var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR",e=["SU","MO","TU","WE","TH","FR","SA"];return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,f,g,h,i){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof f||"undefined"==typeof g||"undefined"==typeof h)return!1;if(i&&!i.rule){if("YEARLY"!==i.freq&&"MONTHLY"!==i.freq&&"WEEKLY"!==i.freq&&"DAILY"!==i.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(i.until&&isNaN(Date.parse(i.until)))throw"Recurrence rule 'until' must be a valid date string";if(i.interval&&isNaN(parseInt(i.interval)))throw"Recurrence rule 'interval' must be an integer";if(i.count&&isNaN(parseInt(i.count)))throw"Recurrence rule 'count' must be an integer";if("undefined"!=typeof i.byday){if("[object Array]"!==Object.prototype.toString.call(i.byday))throw"Recurrence rule 'byday' must be an array";if(i.byday.length>7)throw"Recurrence rule 'byday' array must not be longer than the 7 days in a week";i.byday=i.byday.filter(function(a,b){return i.byday.indexOf(a)==b});for(var j in i.byday)if(e.indexOf(i.byday[j])<0)throw"Recurrence rule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"}}var k=new Date(g),l=new Date(h),m=("0000"+k.getFullYear().toString()).slice(-4),n=("00"+(k.getMonth()+1).toString()).slice(-2),o=("00"+k.getDate().toString()).slice(-2),p=("00"+k.getHours().toString()).slice(-2),q=("00"+k.getMinutes().toString()).slice(-2),r=("00"+k.getMinutes().toString()).slice(-2),s=("0000"+l.getFullYear().toString()).slice(-4),t=("00"+(l.getMonth()+1).toString()).slice(-2),u=("00"+l.getDate().toString()).slice(-2),v=("00"+l.getHours().toString()).slice(-2),w=("00"+l.getMinutes().toString()).slice(-2),x=("00"+l.getMinutes().toString()).slice(-2),y="",z="";q+r+w+x!==0&&(y="T"+p+q+r,z="T"+v+w+x);var A,B=m+n+o+y,C=s+t+u+z;if(i)if(i.rule)A=i.rule;else{if(A="RRULE:FREQ="+i.freq,i.until){var D=new Date(Date.parse(i.until)).toISOString();A+=";UNTIL="+D.substring(0,D.length-13).replace(/[-]/g,"")+"000000Z"}i.interval&&(A+=";INTERVAL="+i.interval),i.count&&(A+=";COUNT="+i.count),i.byday&&i.byday.length>0&&(A+=";BYDAY="+i.byday.join(","))}var E=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+B,"DTEND;VALUE=DATE:"+C,"LOCATION:"+f,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return A&&E.splice(4,0,A),E=E.join(a),b.push(E),E},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file diff --git a/ics.js b/ics.js index a1249f7..b76394b 100644 --- a/ics.js +++ b/ics.js @@ -16,6 +16,7 @@ var ics = function() { 'VERSION:2.0' ].join(SEPARATOR); var calendarEnd = SEPARATOR + 'END:VCALENDAR'; + var BYDAY_VALUES = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; return { /** @@ -77,6 +78,27 @@ var ics = function() { throw "Recurrence rule 'count' must be an integer"; } } + + if (typeof rrule.byday !== 'undefined') { + if ( (Object.prototype.toString.call(rrule.byday) !== '[object Array]') ) { + throw "Recurrence rule 'byday' must be an array"; + } + + if (rrule.byday.length > 7) { + throw "Recurrence rule 'byday' array must not be longer than the 7 days in a week"; + } + + // Filter any possible repeats + rrule.byday = rrule.byday.filter(function(elem, pos) { + return rrule.byday.indexOf(elem) == pos; + }); + + for (var d in rrule.byday) { + if (BYDAY_VALUES.indexOf(rrule.byday[d]) < 0) { + throw "Recurrence rule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"; + } + } + } } } @@ -129,6 +151,10 @@ var ics = function() { if (rrule.count) { rruleString += ';COUNT=' + rrule.count; } + + if (rrule.byday && rrule.byday.length > 0) { + rruleString += ';BYDAY=' + rrule.byday.join(','); + } } } diff --git a/ics.min.js b/ics.min.js index b99aecb..f54cf17 100644 --- a/ics.min.js +++ b/ics.min.js @@ -1,2 +1,2 @@ -/*! ics.js Mon Aug 18 2014 20:11:18 */ -var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR";return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,e,f,g,h){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof e||"undefined"==typeof f||"undefined"==typeof g)return!1;if(h&&!h.rule){if("YEARLY"!==h.freq&&"MONTHLY"!==h.freq&&"WEEKLY"!==h.freq&&"DAILY"!==h.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(h.until&&isNaN(Date.parse(h.until)))throw"Recurrence rule 'until' must be a valid date string";if(h.interval&&isNaN(parseInt(h.interval)))throw"Recurrence rule 'interval' must be an integer";if(h.count&&isNaN(parseInt(h.count)))throw"Recurrence rule 'count' must be an integer"}var i=new Date(f),j=new Date(g),k=("0000"+i.getFullYear().toString()).slice(-4),l=("00"+(i.getMonth()+1).toString()).slice(-2),m=("00"+i.getDate().toString()).slice(-2),n=("00"+i.getHours().toString()).slice(-2),o=("00"+i.getMinutes().toString()).slice(-2),p=("00"+i.getMinutes().toString()).slice(-2),q=("0000"+j.getFullYear().toString()).slice(-4),r=("00"+(j.getMonth()+1).toString()).slice(-2),s=("00"+j.getDate().toString()).slice(-2),t=("00"+j.getHours().toString()).slice(-2),u=("00"+j.getMinutes().toString()).slice(-2),v=("00"+j.getMinutes().toString()).slice(-2),w="",x="";o+p+u+v!==0&&(w="T"+n+o+p,x="T"+t+u+v);var y,z=k+l+m+w,A=q+r+s+x;if(h)if(h.rule)y=h.rule;else{if(y="RRULE:FREQ="+h.freq,h.until){var B=new Date(Date.parse(h.until)).toISOString();y+=";UNTIL="+B.substring(0,B.length-13).replace(/[-]/g,"")+"000000Z"}h.interval&&(y+=";INTERVAL="+h.interval),h.count&&(y+=";COUNT="+h.count)}var C=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+z,"DTEND;VALUE=DATE:"+A,"LOCATION:"+e,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return y&&C.splice(4,0,y),C=C.join(a),b.push(C),C},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file +/*! ics.js Wed Aug 20 2014 14:44:48 */ +var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR",e=["SU","MO","TU","WE","TH","FR","SA"];return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,f,g,h,i){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof f||"undefined"==typeof g||"undefined"==typeof h)return!1;if(i&&!i.rule){if("YEARLY"!==i.freq&&"MONTHLY"!==i.freq&&"WEEKLY"!==i.freq&&"DAILY"!==i.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(i.until&&isNaN(Date.parse(i.until)))throw"Recurrence rule 'until' must be a valid date string";if(i.interval&&isNaN(parseInt(i.interval)))throw"Recurrence rule 'interval' must be an integer";if(i.count&&isNaN(parseInt(i.count)))throw"Recurrence rule 'count' must be an integer";if("undefined"!=typeof i.byday){if("[object Array]"!==Object.prototype.toString.call(i.byday))throw"Recurrence rule 'byday' must be an array";if(i.byday.length>7)throw"Recurrence rule 'byday' array must not be longer than the 7 days in a week";i.byday=i.byday.filter(function(a,b){return i.byday.indexOf(a)==b});for(var j in i.byday)if(e.indexOf(i.byday[j])<0)throw"Recurrence rule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"}}var k=new Date(g),l=new Date(h),m=("0000"+k.getFullYear().toString()).slice(-4),n=("00"+(k.getMonth()+1).toString()).slice(-2),o=("00"+k.getDate().toString()).slice(-2),p=("00"+k.getHours().toString()).slice(-2),q=("00"+k.getMinutes().toString()).slice(-2),r=("00"+k.getMinutes().toString()).slice(-2),s=("0000"+l.getFullYear().toString()).slice(-4),t=("00"+(l.getMonth()+1).toString()).slice(-2),u=("00"+l.getDate().toString()).slice(-2),v=("00"+l.getHours().toString()).slice(-2),w=("00"+l.getMinutes().toString()).slice(-2),x=("00"+l.getMinutes().toString()).slice(-2),y="",z="";q+r+w+x!==0&&(y="T"+p+q+r,z="T"+v+w+x);var A,B=m+n+o+y,C=s+t+u+z;if(i)if(i.rule)A=i.rule;else{if(A="RRULE:FREQ="+i.freq,i.until){var D=new Date(Date.parse(i.until)).toISOString();A+=";UNTIL="+D.substring(0,D.length-13).replace(/[-]/g,"")+"000000Z"}i.interval&&(A+=";INTERVAL="+i.interval),i.count&&(A+=";COUNT="+i.count),i.byday&&i.byday.length>0&&(A+=";BYDAY="+i.byday.join(","))}var E=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+B,"DTEND;VALUE=DATE:"+C,"LOCATION:"+f,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return A&&E.splice(4,0,A),E=E.join(a),b.push(E),E},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file diff --git a/test/spec/test.js b/test/spec/test.js index b3b8fa4..715f23e 100644 --- a/test/spec/test.js +++ b/test/spec/test.js @@ -52,6 +52,16 @@ cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', interval: 2, count: 10}); assert.strictEqual(cal.events()[4], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); + + it('should add recurring events using interval and until and byday', function () { + cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', interval: 1, until: '08/18/2014', byday: ['MO','WE','FR']}); + assert.strictEqual(cal.events()[5], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z;INTERVAL=1;BYDAY=MO,WE,FR\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + }); + + it('should add recurring events using interval and until and ignore duplicates in byday array', function () { + cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', interval: 1, until: '08/18/2014', byday: ['MO', 'WE', 'MO']}); + assert.strictEqual(cal.events()[6], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z;INTERVAL=1;BYDAY=MO,WE\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + }); }); }); })(); From 6502ca83df3be6e53f85590d663a0f89f42ee47a Mon Sep 17 00:00:00 2001 From: jmuia Date: Wed, 20 Aug 2014 17:28:58 -0400 Subject: [PATCH 07/11] Add .DS_Store to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 68b9e27..aa6f28c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ bower_components/ +.DS_Store From 592454900c2508905eee9d1e30a63807d9968036 Mon Sep 17 00:00:00 2001 From: jmuia Date: Wed, 20 Aug 2014 17:29:53 -0400 Subject: [PATCH 08/11] Add DTSTAMP to events and remove VALUE=DATE from DTSTART and DTEND properties --- ics.deps.min.js | 4 ++-- ics.js | 7 +++++-- ics.min.js | 4 ++-- test/spec/test.js | 20 ++++++++++++-------- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ics.deps.min.js b/ics.deps.min.js index f54cf17..2386388 100644 --- a/ics.deps.min.js +++ b/ics.deps.min.js @@ -1,2 +1,2 @@ -/*! ics.js Wed Aug 20 2014 14:44:48 */ -var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR",e=["SU","MO","TU","WE","TH","FR","SA"];return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,f,g,h,i){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof f||"undefined"==typeof g||"undefined"==typeof h)return!1;if(i&&!i.rule){if("YEARLY"!==i.freq&&"MONTHLY"!==i.freq&&"WEEKLY"!==i.freq&&"DAILY"!==i.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(i.until&&isNaN(Date.parse(i.until)))throw"Recurrence rule 'until' must be a valid date string";if(i.interval&&isNaN(parseInt(i.interval)))throw"Recurrence rule 'interval' must be an integer";if(i.count&&isNaN(parseInt(i.count)))throw"Recurrence rule 'count' must be an integer";if("undefined"!=typeof i.byday){if("[object Array]"!==Object.prototype.toString.call(i.byday))throw"Recurrence rule 'byday' must be an array";if(i.byday.length>7)throw"Recurrence rule 'byday' array must not be longer than the 7 days in a week";i.byday=i.byday.filter(function(a,b){return i.byday.indexOf(a)==b});for(var j in i.byday)if(e.indexOf(i.byday[j])<0)throw"Recurrence rule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"}}var k=new Date(g),l=new Date(h),m=("0000"+k.getFullYear().toString()).slice(-4),n=("00"+(k.getMonth()+1).toString()).slice(-2),o=("00"+k.getDate().toString()).slice(-2),p=("00"+k.getHours().toString()).slice(-2),q=("00"+k.getMinutes().toString()).slice(-2),r=("00"+k.getMinutes().toString()).slice(-2),s=("0000"+l.getFullYear().toString()).slice(-4),t=("00"+(l.getMonth()+1).toString()).slice(-2),u=("00"+l.getDate().toString()).slice(-2),v=("00"+l.getHours().toString()).slice(-2),w=("00"+l.getMinutes().toString()).slice(-2),x=("00"+l.getMinutes().toString()).slice(-2),y="",z="";q+r+w+x!==0&&(y="T"+p+q+r,z="T"+v+w+x);var A,B=m+n+o+y,C=s+t+u+z;if(i)if(i.rule)A=i.rule;else{if(A="RRULE:FREQ="+i.freq,i.until){var D=new Date(Date.parse(i.until)).toISOString();A+=";UNTIL="+D.substring(0,D.length-13).replace(/[-]/g,"")+"000000Z"}i.interval&&(A+=";INTERVAL="+i.interval),i.count&&(A+=";COUNT="+i.count),i.byday&&i.byday.length>0&&(A+=";BYDAY="+i.byday.join(","))}var E=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+B,"DTEND;VALUE=DATE:"+C,"LOCATION:"+f,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return A&&E.splice(4,0,A),E=E.join(a),b.push(E),E},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file +/*! ics.js Wed Aug 20 2014 17:23:02 */ +var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR",e=["SU","MO","TU","WE","TH","FR","SA"];return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,f,g,h,i){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof f||"undefined"==typeof g||"undefined"==typeof h)return!1;if(i&&!i.rule){if("YEARLY"!==i.freq&&"MONTHLY"!==i.freq&&"WEEKLY"!==i.freq&&"DAILY"!==i.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(i.until&&isNaN(Date.parse(i.until)))throw"Recurrence rule 'until' must be a valid date string";if(i.interval&&isNaN(parseInt(i.interval)))throw"Recurrence rule 'interval' must be an integer";if(i.count&&isNaN(parseInt(i.count)))throw"Recurrence rule 'count' must be an integer";if("undefined"!=typeof i.byday){if("[object Array]"!==Object.prototype.toString.call(i.byday))throw"Recurrence rule 'byday' must be an array";if(i.byday.length>7)throw"Recurrence rule 'byday' array must not be longer than the 7 days in a week";i.byday=i.byday.filter(function(a,b){return i.byday.indexOf(a)==b});for(var j in i.byday)if(e.indexOf(i.byday[j])<0)throw"Recurrence rule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"}}var k=new Date(g),l=new Date(h),m=("0000"+k.getFullYear().toString()).slice(-4),n=("00"+(k.getMonth()+1).toString()).slice(-2),o=("00"+k.getDate().toString()).slice(-2),p=("00"+k.getHours().toString()).slice(-2),q=("00"+k.getMinutes().toString()).slice(-2),r=("00"+k.getMinutes().toString()).slice(-2),s=("0000"+l.getFullYear().toString()).slice(-4),t=("00"+(l.getMonth()+1).toString()).slice(-2),u=("00"+l.getDate().toString()).slice(-2),v=("00"+l.getHours().toString()).slice(-2),w=("00"+l.getMinutes().toString()).slice(-2),x=("00"+l.getMinutes().toString()).slice(-2),y="",z="";q+r+w+x!==0&&(y="T"+p+q+r,z="T"+v+w+x);var A,B=m+n+o+y,C=s+t+u+z;if(i)if(i.rule)A=i.rule;else{if(A="RRULE:FREQ="+i.freq,i.until){var D=new Date(Date.parse(i.until)).toISOString();A+=";UNTIL="+D.substring(0,D.length-13).replace(/[-]/g,"")+"000000Z"}i.interval&&(A+=";INTERVAL="+i.interval),i.count&&(A+=";COUNT="+i.count),i.byday&&i.byday.length>0&&(A+=";BYDAY="+i.byday.join(","))}var E=(new Date).toISOString(),F=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART:"+B,"DTEND:"+C,"DTSTAMP:"+E.substring(0,E.length-13).replace(/[-]/g,"")+"000000Z","LOCATION:"+f,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return A&&F.splice(4,0,A),F=F.join(a),b.push(F),F},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file diff --git a/ics.js b/ics.js index b76394b..24a38c8 100644 --- a/ics.js +++ b/ics.js @@ -158,12 +158,15 @@ var ics = function() { } } + var stamp = new Date().toISOString(); + var calendarEvent = [ 'BEGIN:VEVENT', 'CLASS:PUBLIC', 'DESCRIPTION:' + description, - 'DTSTART;VALUE=DATE:' + start, - 'DTEND;VALUE=DATE:' + end, + 'DTSTART:' + start, + 'DTEND:' + end, + 'DTSTAMP:' + stamp.substring(0, stamp.length - 13).replace(/[-]/g, '') + '000000Z', 'LOCATION:' + location, 'SUMMARY;LANGUAGE=en-us:' + subject, 'TRANSP:TRANSPARENT', diff --git a/ics.min.js b/ics.min.js index f54cf17..2386388 100644 --- a/ics.min.js +++ b/ics.min.js @@ -1,2 +1,2 @@ -/*! ics.js Wed Aug 20 2014 14:44:48 */ -var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR",e=["SU","MO","TU","WE","TH","FR","SA"];return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,f,g,h,i){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof f||"undefined"==typeof g||"undefined"==typeof h)return!1;if(i&&!i.rule){if("YEARLY"!==i.freq&&"MONTHLY"!==i.freq&&"WEEKLY"!==i.freq&&"DAILY"!==i.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(i.until&&isNaN(Date.parse(i.until)))throw"Recurrence rule 'until' must be a valid date string";if(i.interval&&isNaN(parseInt(i.interval)))throw"Recurrence rule 'interval' must be an integer";if(i.count&&isNaN(parseInt(i.count)))throw"Recurrence rule 'count' must be an integer";if("undefined"!=typeof i.byday){if("[object Array]"!==Object.prototype.toString.call(i.byday))throw"Recurrence rule 'byday' must be an array";if(i.byday.length>7)throw"Recurrence rule 'byday' array must not be longer than the 7 days in a week";i.byday=i.byday.filter(function(a,b){return i.byday.indexOf(a)==b});for(var j in i.byday)if(e.indexOf(i.byday[j])<0)throw"Recurrence rule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"}}var k=new Date(g),l=new Date(h),m=("0000"+k.getFullYear().toString()).slice(-4),n=("00"+(k.getMonth()+1).toString()).slice(-2),o=("00"+k.getDate().toString()).slice(-2),p=("00"+k.getHours().toString()).slice(-2),q=("00"+k.getMinutes().toString()).slice(-2),r=("00"+k.getMinutes().toString()).slice(-2),s=("0000"+l.getFullYear().toString()).slice(-4),t=("00"+(l.getMonth()+1).toString()).slice(-2),u=("00"+l.getDate().toString()).slice(-2),v=("00"+l.getHours().toString()).slice(-2),w=("00"+l.getMinutes().toString()).slice(-2),x=("00"+l.getMinutes().toString()).slice(-2),y="",z="";q+r+w+x!==0&&(y="T"+p+q+r,z="T"+v+w+x);var A,B=m+n+o+y,C=s+t+u+z;if(i)if(i.rule)A=i.rule;else{if(A="RRULE:FREQ="+i.freq,i.until){var D=new Date(Date.parse(i.until)).toISOString();A+=";UNTIL="+D.substring(0,D.length-13).replace(/[-]/g,"")+"000000Z"}i.interval&&(A+=";INTERVAL="+i.interval),i.count&&(A+=";COUNT="+i.count),i.byday&&i.byday.length>0&&(A+=";BYDAY="+i.byday.join(","))}var E=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART;VALUE=DATE:"+B,"DTEND;VALUE=DATE:"+C,"LOCATION:"+f,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return A&&E.splice(4,0,A),E=E.join(a),b.push(E),E},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file +/*! ics.js Wed Aug 20 2014 17:23:02 */ +var ics=function(){"use strict";if(navigator.userAgent.indexOf("MSIE")>-1&&-1==navigator.userAgent.indexOf("MSIE 10"))return void console.log("Unsupported Browser");var a=-1!==navigator.appVersion.indexOf("Win")?"\r\n":"\n",b=[],c=["BEGIN:VCALENDAR","VERSION:2.0"].join(a),d=a+"END:VCALENDAR",e=["SU","MO","TU","WE","TH","FR","SA"];return{events:function(){return b},calendar:function(){return c+a+b.join(a)+d},addEvent:function(c,d,f,g,h,i){if("undefined"==typeof c||"undefined"==typeof d||"undefined"==typeof f||"undefined"==typeof g||"undefined"==typeof h)return!1;if(i&&!i.rule){if("YEARLY"!==i.freq&&"MONTHLY"!==i.freq&&"WEEKLY"!==i.freq&&"DAILY"!==i.freq)throw"Recurrence rule frequency must be provided and be one of the following: 'YEARLY', 'MONTHLY', 'WEEKLY', or 'DAILY'";if(i.until&&isNaN(Date.parse(i.until)))throw"Recurrence rule 'until' must be a valid date string";if(i.interval&&isNaN(parseInt(i.interval)))throw"Recurrence rule 'interval' must be an integer";if(i.count&&isNaN(parseInt(i.count)))throw"Recurrence rule 'count' must be an integer";if("undefined"!=typeof i.byday){if("[object Array]"!==Object.prototype.toString.call(i.byday))throw"Recurrence rule 'byday' must be an array";if(i.byday.length>7)throw"Recurrence rule 'byday' array must not be longer than the 7 days in a week";i.byday=i.byday.filter(function(a,b){return i.byday.indexOf(a)==b});for(var j in i.byday)if(e.indexOf(i.byday[j])<0)throw"Recurrence rule 'byday' values must include only the following: 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'"}}var k=new Date(g),l=new Date(h),m=("0000"+k.getFullYear().toString()).slice(-4),n=("00"+(k.getMonth()+1).toString()).slice(-2),o=("00"+k.getDate().toString()).slice(-2),p=("00"+k.getHours().toString()).slice(-2),q=("00"+k.getMinutes().toString()).slice(-2),r=("00"+k.getMinutes().toString()).slice(-2),s=("0000"+l.getFullYear().toString()).slice(-4),t=("00"+(l.getMonth()+1).toString()).slice(-2),u=("00"+l.getDate().toString()).slice(-2),v=("00"+l.getHours().toString()).slice(-2),w=("00"+l.getMinutes().toString()).slice(-2),x=("00"+l.getMinutes().toString()).slice(-2),y="",z="";q+r+w+x!==0&&(y="T"+p+q+r,z="T"+v+w+x);var A,B=m+n+o+y,C=s+t+u+z;if(i)if(i.rule)A=i.rule;else{if(A="RRULE:FREQ="+i.freq,i.until){var D=new Date(Date.parse(i.until)).toISOString();A+=";UNTIL="+D.substring(0,D.length-13).replace(/[-]/g,"")+"000000Z"}i.interval&&(A+=";INTERVAL="+i.interval),i.count&&(A+=";COUNT="+i.count),i.byday&&i.byday.length>0&&(A+=";BYDAY="+i.byday.join(","))}var E=(new Date).toISOString(),F=["BEGIN:VEVENT","CLASS:PUBLIC","DESCRIPTION:"+d,"DTSTART:"+B,"DTEND:"+C,"DTSTAMP:"+E.substring(0,E.length-13).replace(/[-]/g,"")+"000000Z","LOCATION:"+f,"SUMMARY;LANGUAGE=en-us:"+c,"TRANSP:TRANSPARENT","END:VEVENT"];return A&&F.splice(4,0,A),F=F.join(a),b.push(F),F},download:function(e,f){if(b.length<1)return!1;f="undefined"!=typeof f?f:".ics",e="undefined"!=typeof e?e:"calendar";var g,h=c+a+b.join(a)+d;if(-1===navigator.userAgent.indexOf("MSIE 10"))g=new Blob([h]);else{var i=new BlobBuilder;i.append(h),g=i.getBlob("text/x-vCalendar;charset="+document.characterSet)}return saveAs(g,e+f),h}}}; \ No newline at end of file diff --git a/test/spec/test.js b/test/spec/test.js index 715f23e..5875e35 100644 --- a/test/spec/test.js +++ b/test/spec/test.js @@ -2,6 +2,10 @@ (function () { 'use strict'; + + var stamp = new Date().toISOString(); + stamp = stamp.substring(0, stamp.length - 13).replace(/[-]/g, '') + '000000Z'; + describe('Load ics Object', function () { describe('Calendar Events Array', function () { it('should be an empty array initially', function () { @@ -21,46 +25,46 @@ cal.addEvent('Christmas', 'Christian holiday celebrating the birth of Jesus Christ', 'Bethlehem', '12/25/2013', '12/25/2013'); assert.isArray(cal.events(), 'calendarEvents not an array'); assert.lengthOf(cal.events(), '1'); - assert.strictEqual(cal.events()[0], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART;VALUE=DATE:20131225T000000\nDTEND;VALUE=DATE:20131225T000000\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[0], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART:20131225T000000\nDTEND:20131225T000000\nDTSTAMP:' + stamp + '\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); describe('Calendar String', function () { it('should have one event', function () { - assert.strictEqual(cal.calendar(), 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART;VALUE=DATE:20131225T000000\nDTEND;VALUE=DATE:20131225T000000\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT\nEND:VCALENDAR', 'calendar does not match'); + assert.strictEqual(cal.calendar(), 'BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the birth of Jesus Christ\nDTSTART:20131225T000000\nDTEND:20131225T000000\nDTSTAMP:' + stamp + '\nLOCATION:Bethlehem\nSUMMARY;LANGUAGE=en-us:Christmas\nTRANSP:TRANSPARENT\nEND:VEVENT\nEND:VCALENDAR', 'calendar does not match'); }); }); describe('Calendar String of Single Digit Months', function () { it('should have one event', function () { cal.addEvent('Easter', 'Christian holiday celebrating the resurrection of Jesus Christ', 'Jerusalem', '04/20/2014', '04/20/2014'); - assert.strictEqual(cal.events()[1], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the resurrection of Jesus Christ\nDTSTART;VALUE=DATE:20140420T000000\nDTEND;VALUE=DATE:20140420T000000\nLOCATION:Jerusalem\nSUMMARY;LANGUAGE=en-us:Easter\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[1], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Christian holiday celebrating the resurrection of Jesus Christ\nDTSTART:20140420T000000\nDTEND:20140420T000000\nDTSTAMP:' + stamp + '\nLOCATION:Jerusalem\nSUMMARY;LANGUAGE=en-us:Easter\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); describe('Calendar String of Single Digit Day', function () { it('should have one event', function () { cal.addEvent('April Fools Day', 'What isn\'t is', 'America', '4/1/2014', '4/1/2014'); - assert.strictEqual(cal.events()[2], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:What isn\'t is\nDTSTART;VALUE=DATE:20140401T000000\nDTEND;VALUE=DATE:20140401T000000\nLOCATION:America\nSUMMARY;LANGUAGE=en-us:April Fools Day\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[2], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:What isn\'t is\nDTSTART:20140401T000000\nDTEND:20140401T000000\nDTSTAMP:' + stamp + '\nLOCATION:America\nSUMMARY;LANGUAGE=en-us:April Fools Day\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); describe('Recurring Events', function () { it('should add recurring events using frequency and until', function () { cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', until: '08/18/2014'}); - assert.strictEqual(cal.events()[3], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[3], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z\nDTEND:20140918T000000\nDTSTAMP:' + stamp + '\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); it('should add recurring events using interval and count', function () { cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', interval: 2, count: 10}); - assert.strictEqual(cal.events()[4], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[4], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART:20140818T000000\nRRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10\nDTEND:20140918T000000\nDTSTAMP:' + stamp + '\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); it('should add recurring events using interval and until and byday', function () { cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', interval: 1, until: '08/18/2014', byday: ['MO','WE','FR']}); - assert.strictEqual(cal.events()[5], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z;INTERVAL=1;BYDAY=MO,WE,FR\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[5], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z;INTERVAL=1;BYDAY=MO,WE,FR\nDTEND:20140918T000000\nDTSTAMP:' + stamp + '\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); it('should add recurring events using interval and until and ignore duplicates in byday array', function () { cal.addEvent('Soccer Practice', 'Practice kicking the ball in the net! YAYY!!', 'Soccer field', '08/18/2014', '09/18/2014', {freq: 'WEEKLY', interval: 1, until: '08/18/2014', byday: ['MO', 'WE', 'MO']}); - assert.strictEqual(cal.events()[6], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART;VALUE=DATE:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z;INTERVAL=1;BYDAY=MO,WE\nDTEND;VALUE=DATE:20140918T000000\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); + assert.strictEqual(cal.events()[6], 'BEGIN:VEVENT\nCLASS:PUBLIC\nDESCRIPTION:Practice kicking the ball in the net! YAYY!!\nDTSTART:20140818T000000\nRRULE:FREQ=WEEKLY;UNTIL=20140818T000000Z;INTERVAL=1;BYDAY=MO,WE\nDTEND:20140918T000000\nDTSTAMP:' + stamp + '\nLOCATION:Soccer field\nSUMMARY;LANGUAGE=en-us:Soccer Practice\nTRANSP:TRANSPARENT\nEND:VEVENT'); }); }); }); From b18b40fa037cf064d164dba611098cb43767395c Mon Sep 17 00:00:00 2001 From: Alex Rinehart Date: Tue, 2 Jun 2015 23:56:43 -0700 Subject: [PATCH 09/11] Update README.md Fixed the 30 minute event to be spelled correctly and actually be 30 minutes (and not 1470 minutes). --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db5204f..8e77052 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Example Demo ``` From e7c3489c2d0bc27a810e46e7069e0f69cab61104 Mon Sep 17 00:00:00 2001 From: Alex Rinehart Date: Mon, 17 Aug 2015 12:18:33 -0500 Subject: [PATCH 10/11] Fix minute/second bug --- ics.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ics.js b/ics.js index 083d963..086da77 100644 --- a/ics.js +++ b/ics.js @@ -62,14 +62,14 @@ var ics = function() { var start_day = ("00" + ((start_date.getDate()).toString())).slice(-2); var start_hours = ("00" + (start_date.getHours().toString())).slice(-2); var start_minutes = ("00" + (start_date.getMinutes().toString())).slice(-2); - var start_seconds = ("00" + (start_date.getMinutes().toString())).slice(-2); + var start_seconds = ("00" + (start_date.getSeconds().toString())).slice(-2); var end_year = ("0000" + (end_date.getFullYear().toString())).slice(-4); var end_month = ("00" + ((end_date.getMonth() + 1).toString())).slice(-2); var end_day = ("00" + ((end_date.getDate()).toString())).slice(-2); var end_hours = ("00" + (end_date.getHours().toString())).slice(-2); var end_minutes = ("00" + (end_date.getMinutes().toString())).slice(-2); - var end_seconds = ("00" + (end_date.getMinutes().toString())).slice(-2); + var end_seconds = ("00" + (end_date.getSeconds().toString())).slice(-2); // Since some calendars don't add 0 second events, we need to remove time if there is none... var start_time = ''; From 28cf7546efeccafbf6e30569bd26106f16fb49d7 Mon Sep 17 00:00:00 2001 From: Nick Hunt Date: Thu, 5 May 2016 18:05:38 -0400 Subject: [PATCH 11/11] Multi-line description bug I have multi-line descriptions in the calendar I'm exporting from. When I try to import the file that your ics.js program generates into Outlook or Google Calendar, it only pulls in the first line in and it ignores the rest. My solution fixes this. I tested it in both Outlook and Google Calendar and it works and also follows generally accepted ics file formatting standards I believe. As far as I can tell, it doesn't seem to break anything. Let me know what you think. Thanks. --- ics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ics.js b/ics.js index 223486f..f7ce758 100644 --- a/ics.js +++ b/ics.js @@ -163,7 +163,7 @@ var ics = function() { var calendarEvent = [ 'BEGIN:VEVENT', 'CLASS:PUBLIC', - 'DESCRIPTION:' + description, + 'DESCRIPTION:' + description.replace('\r\n', '\\n'), 'DTSTART:' + start, 'DTEND:' + end, 'DTSTAMP:' + stamp.substring(0, stamp.length - 13).replace(/[-]/g, '') + '000000Z',