Skip to content

Commit

Permalink
fix: issue #11 and naimo84/node-red-contrib-ical-events/#133
Browse files Browse the repository at this point in the history
  • Loading branch information
naimo84 committed Oct 16, 2023
1 parent 722ff59 commit fa9df15
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 23 deletions.
1 change: 1 addition & 0 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export function convertEvent(event: iCalEvent, config: Config): IKalenderEvent |
location: event.location || '',
organizer: event.organizer || '',
rrule: event.rrule,
rdate: event.rdate,
rruleText: rruleText,
uid: uid,
isRecurring: !!recurrence || !!event.rrule,
Expand Down
67 changes: 52 additions & 15 deletions src/ical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import axios from 'axios';
const { v4: uuid } = require('uuid');
const rrule = require('rrule').RRule;
import moment = require('moment-timezone');
import RRule, { RRuleSet } from 'rrule';

function text(t = '') {
return t
Expand Down Expand Up @@ -113,7 +114,7 @@ function getIanaTZFromMS(msTZName: string | number) {
return he ? he.iana[0] : null;
}

function getTimeZone(value: any) {
function getTimeZone(value: any,config:any) {
let tz = value;
let found = '';
// If this is the custom timezone from MS Outlook
Expand All @@ -140,8 +141,13 @@ function getTimeZone(value: any) {
if (tz && tz.startsWith('(')) {
// Extract just the offset
const regex = /[+|-]\d*:\d*/;
tz = null;
found = tz.match(regex);
// guess Timezone, because RRule's TZID only accepts IANA tz
// possible fix for issue https://github.com/naimo84/node-red-contrib-ical-events/issues/133
if (found) {
tz = config.timezone;
found = '';
}
}

// Timezone not confirmed yet
Expand Down Expand Up @@ -369,6 +375,35 @@ function recurrenceParameter(name: string) {
return dateParameter(name);
};

//@ts-ignore
function rdateParameter(name: string) {
//@ts-ignore
return function (value: any, parameters: any, curr: any) {

//@ts-ignore
storeParameter(value, parameters, curr);
if (parameters.length > 0 && parameters[0].indexOf('PERIOD') >= 0) {
let values = value.split(',');
const rruleSet = new RRuleSet()
for (const rdate of values) {
const rdateSplit = rdate.split('/')


const dtstart = moment(rdateSplit[0])
const until = moment(rdateSplit[1])
rruleSet.rrule(
new RRule({
dtstart: dtstart.toDate(),
until:until.toDate()
}))
}
curr[name] = rruleSet.rrules();
}
return curr;
};
};


function addFBType(fb: { type?: any; }, parameters: any) {
const p = parseParameters(parameters);

Expand Down Expand Up @@ -418,7 +453,8 @@ const objectHandlers: any = {

return { type: component, params: parameters };
},
END(value: string, parameters: any, curr: { rrule: string; start: any; }, stack: any) {
//@ts-ignore
END(value: string, parameters: any, curr: { rrule: string; start: any; }, stack: any, line: any, config: any) {
// Original end function
//@ts-ignore
function originalEnd(component: string, parameters_: any, curr: { [x: string]: any; end: Date; datetype: string; start: any; duration: string | undefined; uid: string | number; recurrenceid: { toISOString: () => string; } | undefined; }, stack: any[]) {
Expand Down Expand Up @@ -607,7 +643,7 @@ const objectHandlers: any = {
try {
// If the original date has a TZID, add it
if (curr.start.tz) {
const tz = getTimeZone(curr.start.tz);
const tz = getTimeZone(curr.start.tz,config);
rule += `;DTSTART;TZID=${tz}:${curr.start.toISOString().replace(/[-:]/g, '')}`;
} else {
rule += `;DTSTART=${curr.start.toISOString().replace(/[-:]/g, '')}`;
Expand Down Expand Up @@ -655,16 +691,17 @@ const objectHandlers: any = {
CREATED: dateParameter('created'),
'LAST-MODIFIED': dateParameter('lastmodified'),
'RECURRENCE-ID': recurrenceParameter('recurrenceid'),
'RDATE': rdateParameter('rdate'),
//@ts-ignore
RRULE(value: any, parameters: any, curr: { rrule: any; }, stack: any, line: any) {
curr.rrule = line;
return curr;
}
}

export function handleObject(name: string, value: any, parameters: any, ctx: any, stack: string | any[], line: any) {
export function handleObject(name: string, value: any, parameters: any, ctx: any, stack: string | any[], line: any, config: any) {
if (objectHandlers[name]) {
return objectHandlers[name](value, parameters, ctx, stack, line);
return objectHandlers[name](value, parameters, ctx, stack, line, config);
}

// Handling custom properties
Expand All @@ -679,7 +716,7 @@ export function handleObject(name: string, value: any, parameters: any, ctx: any
return storeParameter(name.toLowerCase())(value, parameters, ctx);
}

export function parseLines(lines: string | any[], limit: number, ctx?: { type?: any; params?: any; } | undefined, stack?: never[], lastIndex?: number, cb?: (arg0: null, arg1: any) => void) {
export function parseLines(lines: string | any[], limit: number, config:any , ctx?: { type?: any; params?: any; } | undefined, stack?: never[], lastIndex?: number, cb?: (arg0: null, arg1: any) => void) {
if (!cb && typeof ctx === 'function') {
cb = ctx;
ctx = undefined;
Expand Down Expand Up @@ -718,7 +755,7 @@ export function parseLines(lines: string | any[], limit: number, ctx?: { type?:
const name = kv[0];
const parameters = kv[1] ? kv[1].split(';').slice(1) : [];

ctx = handleObject(name, value, parameters, ctx, stack, l) || {};
ctx = handleObject(name, value, parameters, ctx, stack, l,config) || {};
if (++limitCounter > limit) {
break;
}
Expand All @@ -733,7 +770,7 @@ export function parseLines(lines: string | any[], limit: number, ctx?: { type?:
if (cb) {
if (i < lines.length) {
setImmediate(() => {
parseLines(lines, limit, ctx, stack, i + 1, cb);
parseLines(lines, limit, ctx, config, stack, i + 1, cb);
});
} else {
setImmediate(() => {
Expand All @@ -746,22 +783,22 @@ export function parseLines(lines: string | any[], limit: number, ctx?: { type?:
return null;
}

export function parseICS(string: string) :any{
export function parseICS(string: string, config:any): any {
const lineEndType = getLineBreakChar(string);
const lines = string.split(lineEndType === '\n' ? /\n/ : /\r?\n/);
let ctx = parseLines(lines, lines.length);
let ctx = parseLines(lines, lines.length, config);
return ctx;
}

export async function fromURL(url: any, options: any) {
export async function fromURL(url: any, options: any, config: any) {
const response = await axios.get(url, options)
if (Math.floor(response.status / 100) !== 2) {
throw new Error(`${response.status} ${response.statusText}`);
}
return parseICS(response.data);
return parseICS(response.data, config);
}

export async function parseFile(filename: any) {
export async function parseFile(filename: any, config: any) {
const data = await fs.promises.readFile(filename, 'utf8')
return parseICS(data)
return parseICS(data, config)
}
2 changes: 2 additions & 0 deletions src/interfaces/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface iCalEvent {
exdate: any;
recurrences: any;
rrule?: any;
rdate?: any;
startDate?: any;
endDate?: any;
recurrenceId?: any;
Expand Down Expand Up @@ -49,6 +50,7 @@ export interface IKalenderEvent {
id?: string,
allDay?: boolean,
rrule?: any,
rdate?: any,
rruleText?: string,
countdown?: object,
calendarName?: string,
Expand Down
26 changes: 21 additions & 5 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export class KalenderEvents {

const { preview, pastview } = getPreviews(this.config as Config);

debug(`getEvents - config: ${this.config}`)
debug(`getEvents - pastview: ${pastview}`)
debug(`getEvents - preview: ${preview}`)
let processedData = this.processData(data, realnow, pastview.toDate(), preview.toDate());
Expand All @@ -139,6 +140,7 @@ export class KalenderEvents {
}

private async getCal(): Promise<IKalenderEvent[]> {
debug(this.config)
if (this.config.type && this.config.type === 'icloud') {
debug('getCal - icloud');

Expand Down Expand Up @@ -189,7 +191,7 @@ export class KalenderEvents {
};
}

let data = await fromURL(this.config.url, header);
let data = await fromURL(this.config.url, header, this.config);
debug(data)

let converted = await convertEvents(data, this.config);
Expand All @@ -199,18 +201,22 @@ export class KalenderEvents {
if (!this.config.url) {
throw "URL/File is not defined";
}
let data = await parseFile(this.config.url);
let data = await parseFile(this.config.url, this.config);
debug(data)
let converted = await convertEvents(data, this.config);
return converted;
}
}
}

private processRRule(ev: IKalenderEvent, preview: Date, pastview: Date) {
private processRRule(ev: IKalenderEvent, preview: Date, pastview: Date, rdate: boolean = false) {
var eventLength = ev.eventEnd!.getTime() - ev.eventStart!.getTime();
var options = RRule.parseString(ev.rrule.toString());
options.dtstart = this.addOffset(ev.eventStart!, -getTimezoneOffset(ev.eventStart!));
if (!rdate)
options.dtstart = this.addOffset(ev.eventStart!, -getTimezoneOffset(ev.eventStart!));
else
options.dtstart = this.addOffset(options.dtstart, -getTimezoneOffset(options.dtstart));

if (options.until) {
options.until = this.addOffset(options.until, -getTimezoneOffset(options.until));
}
Expand Down Expand Up @@ -339,7 +345,17 @@ export class KalenderEvents {
}

if (ev.rrule === undefined) {
this.checkDates(ev, preview, pastview, realnow, ' ', reslist);
if (ev.rdate === undefined) {
this.checkDates(ev, preview, pastview, realnow, ' ', reslist);
} else {
for (let rdate of ev.rdate) {
ev.rrule = rdate;
let evlist = this.processRRule(ev, preview, pastview, true);
for (let ev2 of evlist) {
this.checkDates(ev2 as IKalenderEvent, preview, pastview, realnow, ev.rrule, reslist);
}
}
}
} else {
let evlist = this.processRRule(ev, preview, pastview);
for (let ev2 of evlist) {
Expand Down
50 changes: 47 additions & 3 deletions test/issues_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,54 @@ use(require('chai-things'));

describe('issues', () => {


it('#133', async () => {
return new Promise(async (resolve, reject) => {
try {

let ke = new KalenderEvents({
url: "./test/mocks/133.ics"
});
let events = await ke.getEvents({
now: moment('20160919').toDate(),
pastview: 1,
preview: 2,
includeTodo: true,
timezone: 'Europe/Amsterdam'
});
expect(events).to.have.lengthOf(1)
resolve();
} catch (err) {
reject(err);
}
});
});

it('#11', async () => {
return new Promise(async (resolve, reject) => {
try {

let ke = new KalenderEvents({
url: "./test/mocks/11.ics"
});
let events = await ke.getEvents({
now: moment('20221108').toDate(),
pastview: 10,
preview: 10,
includeTodo: true
});
expect(events).to.have.lengthOf(1)
resolve();
} catch (err) {
reject(err);
}
});
});

it('#131', async () => {
return new Promise(async (resolve, reject) => {
try {
if (!process.env.CALDAV1_URL) resolve();

let ke = new KalenderEvents({
url: "./test/mocks/131.ics"
});
Expand All @@ -31,7 +75,7 @@ describe('issues', () => {
it('#15', async () => {
return new Promise(async (resolve, reject) => {
try {
if (!process.env.CALDAV1_URL) resolve();

let ke = new KalenderEvents({
url: "./test/mocks/15.ics"
});
Expand Down Expand Up @@ -83,7 +127,7 @@ describe('issues', () => {
it('#77', async () => {
return new Promise(async (resolve, reject) => {
try {
if (!process.env.CALDAV1_URL) resolve();

let ke = new KalenderEvents({
url: "./test/mocks/77.ics"
});
Expand Down
17 changes: 17 additions & 0 deletions test/mocks/11.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
BEGIN:VCALENDAR
METHOD:PUBLISH
VERSION:2.0
BEGIN:VEVENT
SUMMARY:3375
DESCRIPTION: test description
DTSTART:20220913T133001
DTEND:20220913T160002
RDATE;VALUE=PERIOD:20221018T133001/20221018T160002,20221115T133001/20221115T160002,20230221T133001/20230221T160002,20230321T133001/20230321T160002,20230418T133001/20230418T160002
UID:2022-09-13 13:30:01_3375@demo.icalendar.org
DTSTAMP:20221114T224954
CREATED:20220223T112042
LAST-MODIFIED:20220223T112042
STATUS:CONFIRMED
ATTENDEE;CN=:MAILTO:TEST@TEST.com
END:VEVENT
END:VCALENDAR
Loading

0 comments on commit fa9df15

Please sign in to comment.