diff --git a/src/event.ts b/src/event.ts index c4009d71c..cdbabe165 100755 --- a/src/event.ts +++ b/src/event.ts @@ -151,7 +151,7 @@ interface ICalEventInternalRepeatingData { byDay?: ICalWeekday[]; byMonth?: number[]; byMonthDay?: number[]; - bySetPos?: number; + bySetPos?: number[]; exclude?: ICalDateTimeValue[]; startOfWeek?: ICalWeekday; } @@ -655,7 +655,7 @@ export default class ICalEvent { this.data.repeating.byMonthDay = byMonthDayArray.map(monthDay => { - if (typeof monthDay !== 'number' || monthDay < 1 || monthDay > 31) { + if (typeof monthDay !== 'number' || monthDay < -31 || monthDay > 31 || monthDay === 0) { throw new Error('`repeating.byMonthDay` contains invalid value `' + monthDay + '`!'); } @@ -667,12 +667,13 @@ export default class ICalEvent { if (!this.data.repeating.byDay) { throw '`repeating.bySetPos` must be used along with `repeating.byDay`!'; } - if (typeof repeating.bySetPos !== 'number' || repeating.bySetPos < -1 || repeating.bySetPos > 4) { - throw '`repeating.bySetPos` contains invalid value `' + repeating.bySetPos + '`!'; - } - - this.data.repeating.byDay.splice(1); - this.data.repeating.bySetPos = repeating.bySetPos; + const bySetPosArray = Array.isArray(repeating.bySetPos) ? repeating.bySetPos : [repeating.bySetPos]; + this.data.repeating.bySetPos = bySetPosArray.map(bySetPos => { + if (typeof bySetPos !== 'number' || bySetPos < -366 || bySetPos > 366 || bySetPos === 0) { + throw '`repeating.bySetPos` contains invalid value `' + bySetPos + '`!'; + } + return bySetPos; + }); } if (repeating.exclude) { @@ -1506,7 +1507,7 @@ export default class ICalEvent { } if (this.data.repeating.bySetPos) { - g += ';BYSETPOS=' + this.data.repeating.bySetPos; + g += ';BYSETPOS=' + this.data.repeating.bySetPos.join(','); } if (this.data.repeating.startOfWeek) { diff --git a/src/types.ts b/src/types.ts index 3e423d9d9..d5794f15a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,7 +14,7 @@ export interface ICalRepeatingOptions { byDay?: ICalWeekday[] | ICalWeekday; byMonth?: number[] | number; byMonthDay?: number[] | number; - bySetPos?: number; + bySetPos?: number[] | number; exclude?: ICalDateTimeValue[] | ICalDateTimeValue; startOfWeek?: ICalWeekday; } diff --git a/test/event.ts b/test/event.ts index 9f97d3454..8a1b4da32 100644 --- a/test/event.ts +++ b/test/event.ts @@ -835,10 +835,36 @@ describe('ical-generator Event', function () { repeating: { freq: ICalEventRepeatingFreq.DAILY, interval: 2, - byMonthDay: [1, 32, 15] + byMonthDay: [1, 32, -15] } }, new ICalCalendar()); }, /`repeating\.byMonthDay` contains invalid value `32`/); + + assert.throws(function () { + new ICalEvent({ + start: moment(), + end: moment(), + summary: 'test', + repeating: { + freq: ICalEventRepeatingFreq.DAILY, + interval: 2, + byMonthDay: [-1, -32, 15] + } + }, new ICalCalendar()); + }, /`repeating\.byMonthDay` contains invalid value `-32`/); + + assert.throws(function () { + new ICalEvent({ + start: moment(), + end: moment(), + summary: 'test', + repeating: { + freq: ICalEventRepeatingFreq.DAILY, + interval: 2, + byMonthDay: [1, 0, 15] + } + }, new ICalCalendar()); + }, /`repeating\.byMonthDay` contains invalid value `0`/); }); it('setter should update repeating.byMonthDay', function () { @@ -861,10 +887,38 @@ describe('ical-generator Event', function () { freq: ICalEventRepeatingFreq.MONTHLY, interval: 2, byDay: [ICalWeekday.SU], - bySetPos: 6 + bySetPos: [367] + } + }, new ICalCalendar()); + }, /`repeating\.bySetPos` contains invalid value `367`/); + + assert.throws(function () { + new ICalEvent({ + start: moment(), + end: moment(), + summary: 'test', + repeating: { + freq: ICalEventRepeatingFreq.MONTHLY, + interval: 2, + byDay: [ICalWeekday.SU], + bySetPos: [-367] + } + }, new ICalCalendar()); + }, /`repeating\.bySetPos` contains invalid value `-367`/); + + assert.throws(function () { + new ICalEvent({ + start: moment(), + end: moment(), + summary: 'test', + repeating: { + freq: ICalEventRepeatingFreq.MONTHLY, + interval: 2, + byDay: [ICalWeekday.SU], + bySetPos: [0] } }, new ICalCalendar()); - }, /`repeating\.bySetPos` contains invalid value `6`/); + }, /`repeating\.bySetPos` contains invalid value `0`/); assert.throws(function () { new ICalEvent({ @@ -876,7 +930,7 @@ describe('ical-generator Event', function () { interval: 2, byDay: [ICalWeekday.SU], // @ts-ignore - bySetPos: 'FOO' + bySetPos: ['FOO'] } }, new ICalCalendar()); }, /`repeating\.bySetPos` contains invalid value `FOO`/); @@ -903,13 +957,13 @@ describe('ical-generator Event', function () { e.repeating({ freq: ICalEventRepeatingFreq.MONTHLY, byDay: [ICalWeekday.SU], - bySetPos: 2 + bySetPos: [2] }); const result = e.repeating(); assert.ok(result && !isRRule(result) && typeof result !== 'string'); assert.strictEqual(result.byDay?.length, 1); - assert.strictEqual(result.bySetPos, 2); + assert.strictEqual(result.bySetPos?.length, 1); }); it('should throw error when repeating.exclude is not valid', function () { diff --git a/test/issues.ts b/test/issues.ts index 7ff503f94..90ceffab9 100644 --- a/test/issues.ts +++ b/test/issues.ts @@ -60,7 +60,7 @@ describe('Issues', function () { assert.ok(str.indexOf('RRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=1;BYDAY=SU;BYSETPOS=3') > -1); }); - it('should work with repeating bySetPos by taking the first elemnt of the byDay array', function () { + it('should work with repeating bySetPos by taking all elements of the byDay array', function () { const calendar = ical({ prodId: '//superman-industries.com//ical-generator//EN', events: [{ @@ -79,7 +79,7 @@ describe('Issues', function () { }); const str = calendar.toString(); - assert.ok(str.indexOf('RRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=1;BYDAY=MO;BYSETPOS=3') > -1); + assert.ok(str.indexOf('RRULE:FREQ=MONTHLY;COUNT=3;INTERVAL=1;BYDAY=MO,FR;BYSETPOS=3') > -1); }); });