ical-generator 3.5.1 → 3.5.2-develop.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * ical-generator entrypoint
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ import ICalCalendar, {ICalCalendarData} from './calendar';
8
+
9
+
10
+ /**
11
+ * Create a new, empty calendar and returns it.
12
+ *
13
+ * ```javascript
14
+ * import ical from 'ical-generator';
15
+ *
16
+ * // or use require:
17
+ * // const ical = require('ical-generator');
18
+ *
19
+ * const cal = ical();
20
+ * ```
21
+ *
22
+ * You can pass options to setup your calendar or use setters to do this.
23
+ *
24
+ * ```javascript
25
+ * import ical from 'ical-generator';
26
+ *
27
+ * // or use require:
28
+ * // const ical = require('ical-generator');
29
+ * const cal = ical({domain: 'sebbo.net'});
30
+ *
31
+ * // is the same as
32
+ *
33
+ * const cal = ical().domain('sebbo.net');
34
+ *
35
+ * // is the same as
36
+ *
37
+ * const cal = ical();
38
+ * cal.domain('sebbo.net');
39
+ * ```
40
+ *
41
+ * @param data Calendar data
42
+ */
43
+ function ical(data?: ICalCalendarData): ICalCalendar {
44
+ return new ICalCalendar(data);
45
+ }
46
+
47
+ export default ical;
48
+
49
+ export {
50
+ default as ICalAlarm,
51
+ ICalAlarmData,
52
+ ICalAlarmType,
53
+ ICalAlarmTypeValue,
54
+ ICalAlarmJSONData,
55
+ ICalAttachment
56
+ } from './alarm';
57
+
58
+ export {
59
+ default as ICalAttendee,
60
+ ICalAttendeeData,
61
+ ICalAttendeeType,
62
+ ICalAttendeeRole,
63
+ ICalAttendeeStatus,
64
+ ICalAttendeeJSONData
65
+ } from './attendee';
66
+
67
+ export {
68
+ default as ICalCalendar,
69
+ ICalCalendarData,
70
+ ICalCalendarProdIdData,
71
+ ICalCalendarMethod,
72
+ ICalCalendarJSONData
73
+ } from './calendar';
74
+
75
+ export {
76
+ default as ICalCategory,
77
+ ICalCategoryData
78
+ } from './category';
79
+
80
+ export {
81
+ default as ICalEvent,
82
+ ICalEventStatus,
83
+ ICalEventBusyStatus,
84
+ ICalEventTransparency,
85
+ ICalEventData,
86
+ ICalEventJSONData,
87
+ ICalEventClass,
88
+ } from './event';
89
+
90
+ export {
91
+ ICalDateTimeValue,
92
+ ICalRepeatingOptions,
93
+ ICalLocation,
94
+ ICalGeo,
95
+ ICalOrganizer,
96
+ ICalDescription,
97
+ ICalEventRepeatingFreq,
98
+ ICalWeekday,
99
+ ICalTimezone,
100
+ ICalMomentStub,
101
+ ICalMomentTimezoneStub,
102
+ ICalMomentDurationStub,
103
+ ICalLuxonDateTimeStub,
104
+ ICalDayJsStub,
105
+ ICalRRuleStub
106
+ } from './types';
107
+
108
+ export {
109
+ formatDate,
110
+ formatDateTZ,
111
+ escape,
112
+ foldLines
113
+ } from './tools';
114
+
115
+ /* istanbul ignore else */
116
+ if (typeof module !== 'undefined') {
117
+ module.exports = Object.assign(ical, module.exports);
118
+ }
package/src/tools.ts ADDED
@@ -0,0 +1,402 @@
1
+ 'use strict';
2
+
3
+ import {
4
+ ICalDateTimeValue, ICalDayJsStub, ICalLuxonDateTimeStub,
5
+ ICalMomentDurationStub,
6
+ ICalMomentStub,
7
+ ICalMomentTimezoneStub,
8
+ ICalOrganizer, ICalRRuleStub
9
+ } from './types';
10
+
11
+ /**
12
+ * Converts a valid date/time object supported by this library to a string.
13
+ */
14
+ export function formatDate (timezone: string | null, d: ICalDateTimeValue, dateonly?: boolean, floating?: boolean): string {
15
+ if(timezone?.startsWith('/')) {
16
+ timezone = timezone.substr(1);
17
+ }
18
+
19
+ if(typeof d === 'string' || d instanceof Date) {
20
+ const m = new Date(d);
21
+
22
+ // (!dateonly && !floating) || !timezone => utc
23
+ let s = m.getUTCFullYear() +
24
+ String(m.getUTCMonth() + 1).padStart(2, '0') +
25
+ m.getUTCDate().toString().padStart(2, '0');
26
+
27
+ // (dateonly || floating) && timezone => tz
28
+ if(timezone) {
29
+ s = m.getFullYear() +
30
+ String(m.getMonth() + 1).padStart(2, '0') +
31
+ m.getDate().toString().padStart(2, '0');
32
+ }
33
+
34
+ if(dateonly) {
35
+ return s;
36
+ }
37
+
38
+ if(timezone) {
39
+ s += 'T' + m.getHours().toString().padStart(2, '0') +
40
+ m.getMinutes().toString().padStart(2, '0') +
41
+ m.getSeconds().toString().padStart(2, '0');
42
+
43
+ return s;
44
+ }
45
+
46
+ s += 'T' + m.getUTCHours().toString().padStart(2, '0') +
47
+ m.getUTCMinutes().toString().padStart(2, '0') +
48
+ m.getUTCSeconds().toString().padStart(2, '0') +
49
+ (floating ? '' : 'Z');
50
+
51
+ return s;
52
+ }
53
+ else if(isMoment(d)) {
54
+ // @see https://momentjs.com/timezone/docs/#/using-timezones/parsing-in-zone/
55
+ const m = timezone ? (isMomentTZ(d) && !d.tz() ? d.clone().tz(timezone) : d) : (floating ? d : d.utc());
56
+ return m.format('YYYYMMDD') + (!dateonly ? (
57
+ 'T' + m.format('HHmmss') + (floating || timezone ? '' : 'Z')
58
+ ) : '');
59
+ }
60
+ else if(isLuxonDate(d)) {
61
+ const m = timezone ? d.setZone(timezone) : (floating ? d : d.setZone('utc'));
62
+ return m.toFormat('yyyyLLdd') + (!dateonly ? (
63
+ 'T' + m.toFormat('HHmmss') + (floating || timezone ? '' : 'Z')
64
+ ) : '');
65
+ }
66
+ else {
67
+ // @see https://day.js.org/docs/en/plugin/utc
68
+
69
+ let m = d;
70
+ if(timezone) {
71
+ // @see https://day.js.org/docs/en/plugin/timezone
72
+ // @ts-ignore
73
+ m = typeof d.tz === 'function' ? d.tz(timezone) : d;
74
+ }
75
+ else if(floating) {
76
+ // m = d;
77
+ }
78
+
79
+ // @ts-ignore
80
+ else if (typeof d.utc === 'function') {
81
+ // @ts-ignore
82
+ m = d.utc();
83
+ }
84
+ else {
85
+ throw new Error('Unable to convert dayjs object to UTC value: UTC plugin is not available!');
86
+ }
87
+
88
+ return m.format('YYYYMMDD') + (!dateonly ? (
89
+ 'T' + m.format('HHmmss') + (floating || timezone ? '' : 'Z')
90
+ ) : '');
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Converts a valid date/time object supported by this library to a string.
96
+ * For information about this format, see RFC 5545, section 3.3.5
97
+ * https://tools.ietf.org/html/rfc5545#section-3.3.5
98
+ */
99
+ export function formatDateTZ (timezone: string | null, property: string, date: ICalDateTimeValue | Date | string, eventData?: {floating?: boolean | null, timezone?: string | null}): string {
100
+ let tzParam = '';
101
+ let floating = eventData?.floating || false;
102
+
103
+ if (eventData?.timezone) {
104
+ tzParam = ';TZID=' + eventData.timezone;
105
+
106
+ // This isn't a 'floating' event because it has a timezone;
107
+ // but we use it to omit the 'Z' UTC specifier in formatDate()
108
+ floating = true;
109
+ }
110
+
111
+ return property + tzParam + ':' + formatDate(timezone, date, false, floating);
112
+ }
113
+
114
+ /**
115
+ * Escapes special characters in the given string
116
+ */
117
+ export function escape (str: string | unknown, inQuotes: boolean): string {
118
+ return String(str).replace(inQuotes ? /[\\;,"]/g : /[\\;,]/g, function (match) {
119
+ return '\\' + match;
120
+ }).replace(/(?:\r\n|\r|\n)/g, '\\n');
121
+ }
122
+
123
+ /**
124
+ * Trim line length of given string
125
+ */
126
+ export function foldLines (input: string): string {
127
+ return input.split('\r\n').map(function (line) {
128
+ let result = '';
129
+ let c = 0;
130
+ for (let i = 0; i < line.length; i++) {
131
+ let ch = line.charAt(i);
132
+
133
+ // surrogate pair, see https://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs
134
+ if (ch >= '\ud800' && ch <= '\udbff') {
135
+ ch += line.charAt(++i);
136
+ }
137
+
138
+ // TextEncoder is available in browsers and node.js >= 11.0.0
139
+ const charsize = new TextEncoder().encode(ch).length;
140
+ c += charsize;
141
+ if (c > 74) {
142
+ result += '\r\n ';
143
+ c = charsize;
144
+ }
145
+
146
+ result += ch;
147
+ }
148
+ return result;
149
+ }).join('\r\n');
150
+ }
151
+
152
+ export function addOrGetCustomAttributes (data: {x: [string, string][]}, keyOrArray: ({key: string, value: string})[] | [string, string][] | Record<string, string>): void;
153
+ export function addOrGetCustomAttributes (data: {x: [string, string][]}, keyOrArray: string, value: string): void;
154
+ export function addOrGetCustomAttributes (data: {x: [string, string][]}): ({key: string, value: string})[];
155
+ export function addOrGetCustomAttributes (data: {x: [string, string][]}, keyOrArray?: ({key: string, value: string})[] | [string, string][] | Record<string, string> | string | undefined, value?: string | undefined): void | ({key: string, value: string})[] {
156
+ if (Array.isArray(keyOrArray)) {
157
+ data.x = keyOrArray.map((o: {key: string, value: string} | [string, string]) => {
158
+ if(Array.isArray(o)) {
159
+ return o;
160
+ }
161
+ if (typeof o.key !== 'string' || typeof o.value !== 'string') {
162
+ throw new Error('Either key or value is not a string!');
163
+ }
164
+ if (o.key.substr(0, 2) !== 'X-') {
165
+ throw new Error('Key has to start with `X-`!');
166
+ }
167
+
168
+ return [o.key, o.value] as [string, string];
169
+ });
170
+ }
171
+ else if (typeof keyOrArray === 'object') {
172
+ data.x = Object.entries(keyOrArray).map(([key, value]) => {
173
+ if (typeof key !== 'string' || typeof value !== 'string') {
174
+ throw new Error('Either key or value is not a string!');
175
+ }
176
+ if (key.substr(0, 2) !== 'X-') {
177
+ throw new Error('Key has to start with `X-`!');
178
+ }
179
+
180
+ return [key, value];
181
+ });
182
+ }
183
+ else if (typeof keyOrArray === 'string' && typeof value === 'string') {
184
+ if (keyOrArray.substr(0, 2) !== 'X-') {
185
+ throw new Error('Key has to start with `X-`!');
186
+ }
187
+
188
+ data.x.push([keyOrArray, value]);
189
+ }
190
+ else {
191
+ return data.x.map(a => ({
192
+ key: a[0],
193
+ value: a[1]
194
+ }));
195
+ }
196
+ }
197
+
198
+ export function generateCustomAttributes (data: {x: [string, string][]}): string {
199
+ const str = data.x
200
+ .map(([key, value]) => key.toUpperCase() + ':' + escape(value, false))
201
+ .join('\r\n');
202
+ return str.length ? str + '\r\n' : '';
203
+ }
204
+
205
+ /**
206
+ * Check the given string or ICalOrganizer. Parses
207
+ * the string for name and email address if possible.
208
+ *
209
+ * @param attribute Attribute name for error messages
210
+ * @param value Value to parse name/email from
211
+ */
212
+ export function checkNameAndMail (attribute: string, value: string | ICalOrganizer): ICalOrganizer {
213
+ let result: ICalOrganizer | null = null;
214
+
215
+ if (typeof value === 'string') {
216
+ const match = value.match(/^(.+) ?<([^>]+)>$/);
217
+ if (match) {
218
+ result = {
219
+ name: match[1].trim(),
220
+ email: match[2].trim()
221
+ };
222
+ }
223
+ else if(value.includes('@')) {
224
+ result = {
225
+ name: value.trim(),
226
+ email: value.trim()
227
+ };
228
+ }
229
+ }
230
+ else if (typeof value === 'object') {
231
+ result = {
232
+ name: value.name,
233
+ email: value.email,
234
+ mailto: value.mailto,
235
+ sentBy: value.sentBy
236
+ };
237
+ }
238
+
239
+ if (!result && typeof value === 'string') {
240
+ throw new Error(
241
+ '`' + attribute + '` isn\'t formated correctly. See https://sebbo2002.github.io/ical-generator/develop/'+
242
+ 'reference/interfaces/ICalOrganizer.html'
243
+ );
244
+ }
245
+ else if (!result) {
246
+ throw new Error(
247
+ '`' + attribute + '` needs to be a valid formed string or an object. See https://sebbo2002.github.io/'+
248
+ 'ical-generator/develop/reference/interfaces/ICalOrganizer.html'
249
+ );
250
+ }
251
+
252
+ if (!result.name) {
253
+ throw new Error('`' + attribute + '.name` is empty!');
254
+ }
255
+
256
+ return result;
257
+ }
258
+
259
+ /**
260
+ * Checks if the given string `value` is a
261
+ * valid one for the type `type`
262
+ */
263
+ export function checkEnum(type: Record<string, string>, value: unknown): unknown {
264
+ const allowedValues = Object.values(type);
265
+ const valueStr = String(value).toUpperCase();
266
+
267
+ if (!valueStr || !allowedValues.includes(valueStr)) {
268
+ throw new Error(`Input must be one of the following: ${allowedValues.join(', ')}`);
269
+ }
270
+
271
+ return valueStr;
272
+ }
273
+
274
+ /**
275
+ * Checks if the given input is a valid date and
276
+ * returns the internal representation (= moment object)
277
+ */
278
+ export function checkDate(value: ICalDateTimeValue, attribute: string): ICalDateTimeValue {
279
+
280
+ // Date & String
281
+ if(
282
+ (value instanceof Date && isNaN(value.getTime())) ||
283
+ (typeof value === 'string' && isNaN(new Date(value).getTime()))
284
+ ) {
285
+ throw new Error(`\`${attribute}\` has to be a valid date!`);
286
+ }
287
+ if(value instanceof Date || typeof value === 'string') {
288
+ return value;
289
+ }
290
+
291
+ // Luxon
292
+ if(isLuxonDate(value) && value.isValid === true) {
293
+ return value;
294
+ }
295
+
296
+ // Moment / Moment Timezone
297
+ if((isMoment(value) || isDayjs(value)) && value.isValid()) {
298
+ return value;
299
+ }
300
+
301
+ throw new Error(`\`${attribute}\` has to be a valid date!`);
302
+ }
303
+
304
+ export function toDate(value: ICalDateTimeValue): Date {
305
+ if(typeof value === 'string' || value instanceof Date) {
306
+ return new Date(value);
307
+ }
308
+
309
+ // @ts-ignore
310
+ if(isLuxonDate(value)) {
311
+ return value.toJSDate();
312
+ }
313
+
314
+ return value.toDate();
315
+ }
316
+
317
+ export function isMoment(value: ICalDateTimeValue): value is ICalMomentStub {
318
+
319
+ // @ts-ignore
320
+ return value != null && value._isAMomentObject != null;
321
+ }
322
+ export function isMomentTZ(value: ICalDateTimeValue): value is ICalMomentTimezoneStub {
323
+ return isMoment(value) && 'tz' in value && typeof value.tz === 'function';
324
+ }
325
+ export function isDayjs(value: ICalDateTimeValue): value is ICalDayJsStub {
326
+ return typeof value === 'object' &&
327
+ value !== null &&
328
+ !(value instanceof Date) &&
329
+ !isMoment(value) &&
330
+ !isLuxonDate(value);
331
+ }
332
+ export function isLuxonDate(value: ICalDateTimeValue): value is ICalLuxonDateTimeStub {
333
+ return typeof value === 'object' && value !== null && 'toJSDate' in value && typeof value.toJSDate === 'function';
334
+ }
335
+
336
+ export function isMomentDuration(value: unknown): value is ICalMomentDurationStub {
337
+
338
+ // @ts-ignore
339
+ return value !== null && typeof value === 'object' && typeof value.asSeconds === 'function';
340
+ }
341
+
342
+ export function isRRule(value: unknown): value is ICalRRuleStub {
343
+
344
+ // @ts-ignore
345
+ return value !== null && typeof value === 'object' && typeof value.between === 'function' && typeof value.toString === 'function';
346
+ }
347
+
348
+ export function toJSON(value: ICalDateTimeValue | null | undefined): string | null | undefined {
349
+ if(!value) {
350
+ return value;
351
+ }
352
+ if(typeof value === 'string') {
353
+ return value;
354
+ }
355
+
356
+ return value.toJSON();
357
+ }
358
+
359
+ export function toDurationString(seconds: number): string {
360
+ let string = '';
361
+
362
+ // < 0
363
+ if(seconds < 0) {
364
+ string = '-';
365
+ seconds *= -1;
366
+ }
367
+
368
+ string += 'P';
369
+
370
+ // DAYS
371
+ if(seconds >= 86400) {
372
+ string += Math.floor(seconds / 86400) + 'D';
373
+ seconds %= 86400;
374
+ }
375
+ if(!seconds && string.length > 1) {
376
+ return string;
377
+ }
378
+
379
+ string += 'T';
380
+
381
+ // HOURS
382
+ if(seconds >= 3600) {
383
+ string += Math.floor(seconds / 3600) + 'H';
384
+ seconds %= 3600;
385
+ }
386
+
387
+ // MINUTES
388
+ if(seconds >= 60) {
389
+ string += Math.floor(seconds / 60) + 'M';
390
+ seconds %= 60;
391
+ }
392
+
393
+ // SECONDS
394
+ if(seconds > 0) {
395
+ string += seconds + 'S';
396
+ }
397
+ else if(string.length <= 2) {
398
+ string += '0S';
399
+ }
400
+
401
+ return string;
402
+ }
package/src/types.ts ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ical-generator supports [native Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date),
3
+ * [moment.js](https://momentjs.com/) (and [moment-timezone](https://momentjs.com/timezone/), [Day.js](https://day.js.org/en/) and
4
+ * [Luxon](https://moment.github.io/luxon/)'s [DateTime](https://moment.github.io/luxon/docs/class/src/datetime.js~DateTime.html)
5
+ * objects. You can also pass a string which is then passed to javascript's Date internally.
6
+ */
7
+ export type ICalDateTimeValue = Date | ICalMomentStub | ICalMomentTimezoneStub | ICalLuxonDateTimeStub | ICalDayJsStub | string;
8
+
9
+ export interface ICalRepeatingOptions {
10
+ freq: ICalEventRepeatingFreq;
11
+ count?: number;
12
+ interval?: number;
13
+ until?: ICalDateTimeValue;
14
+ byDay?: ICalWeekday[] | ICalWeekday;
15
+ byMonth?: number[] | number;
16
+ byMonthDay?: number[] | number;
17
+ bySetPos?: number;
18
+ exclude?: ICalDateTimeValue[] | ICalDateTimeValue;
19
+ startOfWeek?: ICalWeekday;
20
+ }
21
+
22
+ export interface ICalLocation {
23
+ title: string;
24
+ address?: string;
25
+ radius?: number;
26
+ geo?: ICalGeo;
27
+ }
28
+
29
+ export interface ICalGeo {
30
+ lat: number;
31
+ lon: number;
32
+ }
33
+
34
+ export interface ICalOrganizer {
35
+ name: string;
36
+ email?: string;
37
+ mailto?: string;
38
+ sentBy?: string;
39
+ }
40
+
41
+ export interface ICalDescription {
42
+ plain: string;
43
+ html?: string;
44
+ }
45
+
46
+ export interface ICalTimezone {
47
+ name: string | null;
48
+ generator?: (timezone: string) => string|null;
49
+ }
50
+
51
+ export interface ICalMomentStub {
52
+ format(format?: string): string;
53
+ clone(): ICalMomentStub;
54
+ utc(): ICalMomentStub;
55
+ toDate(): Date;
56
+ isValid(): boolean;
57
+ toJSON(): string;
58
+ }
59
+
60
+ export interface ICalMomentTimezoneStub extends ICalMomentStub {
61
+ clone(): ICalMomentTimezoneStub;
62
+ utc(): ICalMomentTimezoneStub;
63
+ tz(): string | undefined;
64
+ tz(timezone: string): ICalMomentTimezoneStub;
65
+ }
66
+
67
+ export interface ICalMomentDurationStub {
68
+ asSeconds(): number;
69
+ }
70
+
71
+ export interface ICalLuxonDateTimeStub {
72
+ setZone(zone?: string): ICalLuxonDateTimeStub;
73
+ toFormat(fmt: string): string;
74
+ toJSDate(): Date;
75
+ get isValid(): boolean;
76
+ toJSON(): string;
77
+ }
78
+
79
+ export interface ICalDayJsStub {
80
+ tz(zone?: string): ICalDayJsStub;
81
+ utc(): ICalDayJsStub;
82
+ format(format?: string): string;
83
+ toDate(): Date;
84
+ isValid(): boolean;
85
+ toJSON(): string;
86
+ }
87
+
88
+ export interface ICalRRuleStub {
89
+ between(after: Date, before: Date, inc?: boolean, iterator?: (d: Date, len: number) => boolean): Date[];
90
+ toString(): string;
91
+ }
92
+
93
+ export enum ICalEventRepeatingFreq {
94
+ SECONDLY = 'SECONDLY',
95
+ MINUTELY = 'MINUTELY',
96
+ HOURLY = 'HOURLY',
97
+ DAILY = 'DAILY',
98
+ WEEKLY = 'WEEKLY',
99
+ MONTHLY = 'MONTHLY',
100
+ YEARLY = 'YEARLY'
101
+ }
102
+
103
+ export enum ICalWeekday {
104
+ SU = 'SU',
105
+ MO = 'MO',
106
+ TU = 'TU',
107
+ WE = 'WE',
108
+ TH = 'TH',
109
+ FR = 'FR',
110
+ SA = 'SA'
111
+ }