ical-generator 8.1.2-develop.9 → 9.0.0-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/event.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
- import uuid from 'uuid-random';
3
+ import ICalAlarm, { type ICalAlarmData } from './alarm.ts';
4
+ import ICalAttendee, { type ICalAttendeeData } from './attendee.ts';
5
+ import ICalCalendar from './calendar.ts';
6
+ import ICalCategory, { type ICalCategoryData } from './category.ts';
4
7
  import {
5
8
  addOrGetCustomAttributes,
6
9
  checkDate,
@@ -12,150 +15,147 @@ import {
12
15
  generateCustomAttributes,
13
16
  isRRule,
14
17
  toDate,
15
- toJSON
18
+ toJSON,
16
19
  } from './tools.ts';
17
- import ICalAttendee, { type ICalAttendeeData } from './attendee.ts';
18
- import ICalAlarm, { type ICalAlarmData } from './alarm.ts';
19
- import ICalCategory, { type ICalCategoryData } from './category.ts';
20
- import ICalCalendar from './calendar.ts';
21
20
  import {
22
- ICalEventRepeatingFreq,
23
- ICalWeekday,
24
21
  type ICalDateTimeValue,
25
22
  type ICalDescription,
23
+ ICalEventRepeatingFreq,
26
24
  type ICalLocation,
27
25
  type ICalOrganizer,
26
+ type ICalRepeatingOptions,
28
27
  type ICalRRuleStub,
29
- type ICalRepeatingOptions
28
+ ICalWeekday,
30
29
  } from './types.ts';
31
30
 
32
-
33
- export enum ICalEventStatus {
34
- CONFIRMED = 'CONFIRMED',
31
+ export enum ICalEventBusyStatus {
32
+ BUSY = 'BUSY',
33
+ FREE = 'FREE',
34
+ OOF = 'OOF',
35
35
  TENTATIVE = 'TENTATIVE',
36
- CANCELLED = 'CANCELLED'
37
36
  }
38
37
 
39
- export enum ICalEventBusyStatus {
40
- FREE = 'FREE',
38
+ export enum ICalEventClass {
39
+ CONFIDENTIAL = 'CONFIDENTIAL',
40
+ PRIVATE = 'PRIVATE',
41
+ PUBLIC = 'PUBLIC',
42
+ }
43
+
44
+ export enum ICalEventStatus {
45
+ CANCELLED = 'CANCELLED',
46
+ CONFIRMED = 'CONFIRMED',
41
47
  TENTATIVE = 'TENTATIVE',
42
- BUSY = 'BUSY',
43
- OOF = 'OOF'
44
48
  }
45
49
 
46
50
  export enum ICalEventTransparency {
51
+ OPAQUE = 'OPAQUE',
47
52
  TRANSPARENT = 'TRANSPARENT',
48
- OPAQUE = 'OPAQUE'
49
- }
50
-
51
- export enum ICalEventClass {
52
- PUBLIC = 'PUBLIC',
53
- PRIVATE = 'PRIVATE',
54
- CONFIDENTIAL = 'CONFIDENTIAL'
55
53
  }
56
54
 
57
55
  export interface ICalEventData {
58
- id?: string | number | null,
59
- sequence?: number,
60
- start: ICalDateTimeValue,
61
- end?: ICalDateTimeValue | null,
62
- recurrenceId?: ICalDateTimeValue | null,
63
- timezone?: string | null,
64
- stamp?: ICalDateTimeValue,
65
- allDay?: boolean,
66
- floating?: boolean,
67
- repeating?: ICalRepeatingOptions | ICalRRuleStub | string | null,
68
- summary?: string,
69
- location?: ICalLocation | string | null,
70
- description?: ICalDescription | string | null,
71
- organizer?: ICalOrganizer | string | null,
72
- attendees?: ICalAttendee[] | ICalAttendeeData[],
73
- alarms?: ICalAlarm[] | ICalAlarmData[],
74
- categories?: ICalCategory[] | ICalCategoryData[],
75
- status?: ICalEventStatus | null,
76
- busystatus?: ICalEventBusyStatus | null,
77
- priority?: number | null,
78
- url?: string | null,
79
- attachments?: string[],
80
- transparency?: ICalEventTransparency | null,
81
- created?: ICalDateTimeValue | null,
82
- lastModified?: ICalDateTimeValue | null,
56
+ alarms?: ICalAlarm[] | ICalAlarmData[];
57
+ allDay?: boolean;
58
+ attachments?: string[];
59
+ attendees?: ICalAttendee[] | ICalAttendeeData[];
60
+ busystatus?: ICalEventBusyStatus | null;
61
+ categories?: ICalCategory[] | ICalCategoryData[];
83
62
  class?: ICalEventClass | null;
84
- x?: {key: string, value: string}[] | [string, string][] | Record<string, string>;
85
- }
86
-
87
- interface ICalEventInternalData {
88
- id: string,
89
- sequence: number,
90
- start: ICalDateTimeValue,
91
- end: ICalDateTimeValue | null,
92
- recurrenceId: ICalDateTimeValue | null,
93
- timezone: string | null,
94
- stamp: ICalDateTimeValue,
95
- allDay: boolean,
96
- floating: boolean,
97
- repeating: ICalEventJSONRepeatingData | ICalRRuleStub | string | null,
98
- summary: string,
99
- location: ICalLocation | null,
100
- description: ICalDescription | null,
101
- organizer: ICalOrganizer | null,
102
- attendees: ICalAttendee[],
103
- alarms: ICalAlarm[],
104
- categories: ICalCategory[],
105
- status: ICalEventStatus | null,
106
- busystatus: ICalEventBusyStatus | null,
107
- priority: number | null,
108
- url: string | null,
109
- attachments: string[],
110
- transparency: ICalEventTransparency | null,
111
- created: ICalDateTimeValue | null,
112
- lastModified: ICalDateTimeValue | null,
113
- class: ICalEventClass | null,
114
- x: [string, string][];
63
+ created?: ICalDateTimeValue | null;
64
+ description?: ICalDescription | null | string;
65
+ end?: ICalDateTimeValue | null;
66
+ floating?: boolean;
67
+ id?: null | number | string;
68
+ lastModified?: ICalDateTimeValue | null;
69
+ location?: ICalLocation | null | string;
70
+ organizer?: ICalOrganizer | null | string;
71
+ priority?: null | number;
72
+ recurrenceId?: ICalDateTimeValue | null;
73
+ repeating?: ICalRepeatingOptions | ICalRRuleStub | null | string;
74
+ sequence?: number;
75
+ stamp?: ICalDateTimeValue;
76
+ start: ICalDateTimeValue;
77
+ status?: ICalEventStatus | null;
78
+ summary?: string;
79
+ timezone?: null | string;
80
+ transparency?: ICalEventTransparency | null;
81
+ url?: null | string;
82
+ x?:
83
+ | [string, string][]
84
+ | Record<string, string>
85
+ | { key: string; value: string }[];
115
86
  }
116
87
 
117
88
  export interface ICalEventJSONData {
118
- id: string,
119
- sequence: number,
120
- start: string,
121
- end: string | null,
122
- recurrenceId: string | null,
123
- timezone: string | null,
124
- stamp: string,
125
- allDay: boolean,
126
- floating: boolean,
127
- repeating: ICalEventJSONRepeatingData | string | null,
128
- summary: string,
129
- location: ICalLocation | null,
130
- description: ICalDescription | null,
131
- organizer: ICalOrganizer | null,
132
- attendees: ICalAttendee[],
133
- alarms: ICalAlarm[],
134
- categories: ICalCategory[],
135
- status: ICalEventStatus | null,
136
- busystatus: ICalEventBusyStatus | null,
137
- priority?: number | null,
138
- url: string | null,
139
- attachments: string[],
140
- transparency: ICalEventTransparency | null,
141
- created: string | null,
142
- lastModified: string | null,
143
- x: {key: string, value: string}[];
89
+ alarms: ICalAlarm[];
90
+ allDay: boolean;
91
+ attachments: string[];
92
+ attendees: ICalAttendee[];
93
+ busystatus: ICalEventBusyStatus | null;
94
+ categories: ICalCategory[];
95
+ created: null | string;
96
+ description: ICalDescription | null;
97
+ end: null | string;
98
+ floating: boolean;
99
+ id: string;
100
+ lastModified: null | string;
101
+ location: ICalLocation | null;
102
+ organizer: ICalOrganizer | null;
103
+ priority?: null | number;
104
+ recurrenceId: null | string;
105
+ repeating: ICalEventJSONRepeatingData | null | string;
106
+ sequence: number;
107
+ stamp: string;
108
+ start: string;
109
+ status: ICalEventStatus | null;
110
+ summary: string;
111
+ timezone: null | string;
112
+ transparency: ICalEventTransparency | null;
113
+ url: null | string;
114
+ x: { key: string; value: string }[];
144
115
  }
145
116
 
146
117
  export interface ICalEventJSONRepeatingData {
147
- freq: ICalEventRepeatingFreq;
148
- count?: number;
149
- interval?: number;
150
- until?: ICalDateTimeValue;
151
118
  byDay?: ICalWeekday[];
152
119
  byMonth?: number[];
153
120
  byMonthDay?: number[];
154
121
  bySetPos?: number[];
122
+ count?: number;
155
123
  exclude?: ICalDateTimeValue[];
124
+ freq: ICalEventRepeatingFreq;
125
+ interval?: number;
156
126
  startOfWeek?: ICalWeekday;
127
+ until?: ICalDateTimeValue;
157
128
  }
158
129
 
130
+ interface ICalEventInternalData {
131
+ alarms: ICalAlarm[];
132
+ allDay: boolean;
133
+ attachments: string[];
134
+ attendees: ICalAttendee[];
135
+ busystatus: ICalEventBusyStatus | null;
136
+ categories: ICalCategory[];
137
+ class: ICalEventClass | null;
138
+ created: ICalDateTimeValue | null;
139
+ description: ICalDescription | null;
140
+ end: ICalDateTimeValue | null;
141
+ floating: boolean;
142
+ id: string;
143
+ lastModified: ICalDateTimeValue | null;
144
+ location: ICalLocation | null;
145
+ organizer: ICalOrganizer | null;
146
+ priority: null | number;
147
+ recurrenceId: ICalDateTimeValue | null;
148
+ repeating: ICalEventJSONRepeatingData | ICalRRuleStub | null | string;
149
+ sequence: number;
150
+ stamp: ICalDateTimeValue;
151
+ start: ICalDateTimeValue;
152
+ status: ICalEventStatus | null;
153
+ summary: string;
154
+ timezone: null | string;
155
+ transparency: ICalEventTransparency | null;
156
+ url: null | string;
157
+ x: [string, string][];
158
+ }
159
159
 
160
160
  /**
161
161
  * Usually you get an {@link ICalEvent} object like this:
@@ -166,8 +166,8 @@ export interface ICalEventJSONRepeatingData {
166
166
  * ```
167
167
  */
168
168
  export default class ICalEvent {
169
- private readonly data: ICalEventInternalData;
170
169
  private readonly calendar: ICalCalendar;
170
+ private readonly data: ICalEventInternalData;
171
171
 
172
172
  /**
173
173
  * Constructor of [[`ICalEvent`]. The calendar reference is
@@ -178,33 +178,33 @@ export default class ICalEvent {
178
178
  */
179
179
  constructor(data: ICalEventData, calendar: ICalCalendar) {
180
180
  this.data = {
181
- id: uuid(),
182
- sequence: 0,
183
- start: new Date(),
184
- end: null,
185
- recurrenceId: null,
186
- timezone: null,
187
- stamp: new Date(),
181
+ alarms: [],
188
182
  allDay: false,
183
+ attachments: [],
184
+ attendees: [],
185
+ busystatus: null,
186
+ categories: [],
187
+ class: null,
188
+ created: null,
189
+ description: null,
190
+ end: null,
189
191
  floating: false,
190
- repeating: null,
191
- summary: '',
192
+ id: crypto.randomUUID(),
193
+ lastModified: null,
192
194
  location: null,
193
- description: null,
194
195
  organizer: null,
195
- attendees: [],
196
- alarms: [],
197
- categories: [],
198
- status: null,
199
- busystatus: null,
200
196
  priority: null,
201
- url: null,
202
- attachments: [],
197
+ recurrenceId: null,
198
+ repeating: null,
199
+ sequence: 0,
200
+ stamp: new Date(),
201
+ start: new Date(),
202
+ status: null,
203
+ summary: '',
204
+ timezone: null,
203
205
  transparency: null,
204
- created: null,
205
- lastModified: null,
206
- class: null,
207
- x: []
206
+ url: null,
207
+ x: [],
208
208
  };
209
209
 
210
210
  this.calendar = calendar;
@@ -216,7 +216,8 @@ export default class ICalEvent {
216
216
  if (data.sequence !== undefined) this.sequence(data.sequence);
217
217
  if (data.start) this.start(data.start);
218
218
  if (data.end !== undefined) this.end(data.end);
219
- if (data.recurrenceId !== undefined) this.recurrenceId(data.recurrenceId);
219
+ if (data.recurrenceId !== undefined)
220
+ this.recurrenceId(data.recurrenceId);
220
221
  if (data.timezone !== undefined) this.timezone(data.timezone);
221
222
  if (data.stamp !== undefined) this.stamp(data.stamp);
222
223
  if (data.allDay !== undefined) this.allDay(data.allDay);
@@ -234,109 +235,71 @@ export default class ICalEvent {
234
235
  if (data.priority !== undefined) this.priority(data.priority);
235
236
  if (data.url !== undefined) this.url(data.url);
236
237
  if (data.attachments !== undefined) this.attachments(data.attachments);
237
- if (data.transparency !== undefined) this.transparency(data.transparency);
238
+ if (data.transparency !== undefined)
239
+ this.transparency(data.transparency);
238
240
  if (data.created !== undefined) this.created(data.created);
239
- if (data.lastModified !== undefined) this.lastModified(data.lastModified);
241
+ if (data.lastModified !== undefined)
242
+ this.lastModified(data.lastModified);
240
243
  if (data.class !== undefined) this.class(data.class);
241
244
  if (data.x !== undefined) this.x(data.x);
242
245
  }
243
246
 
244
247
  /**
245
- * Get the event's ID
248
+ * Get all alarms
246
249
  * @since 0.2.0
247
250
  */
248
- id(): string;
249
-
251
+ alarms(): ICalAlarm[];
250
252
  /**
251
- * Use this method to set the event's ID.
252
- * If not set, a UUID will be generated randomly.
253
+ * Add one or multiple alarms
253
254
  *
254
- * @param id Event ID you want to set
255
- */
256
- id(id: string | number): this;
257
- id(id?: string | number): this | string {
258
- if (id === undefined) {
259
- return this.data.id;
260
- }
261
-
262
- this.data.id = String(id);
263
- return this;
264
- }
265
-
266
- /**
267
- * Get the event's ID
268
- * @since 0.2.0
269
- * @see {@link id}
270
- */
271
- uid(): string;
272
-
273
- /**
274
- * Use this method to set the event's ID.
275
- * If not set, a UUID will be generated randomly.
255
+ * ```javascript
256
+ * const event = ical().createEvent();
276
257
  *
277
- * @param id Event ID you want to set
278
- */
279
- uid(id: string | number): this;
280
- uid(id?: string | number): this | string {
281
- return id === undefined ? this.id() : this.id(id);
282
- }
283
-
284
- /**
285
- * Get the event's SEQUENCE number. Use this method to get the event's
286
- * revision sequence number of the calendar component within a sequence of revisions.
258
+ * cal.alarms([
259
+ * {type: ICalAlarmType.display, trigger: 600},
260
+ * {type: ICalAlarmType.audio, trigger: 300}
261
+ * ]);
287
262
  *
288
- * @since 0.2.6
289
- */
290
- sequence(): number;
291
-
292
- /**
293
- * Set the event's SEQUENCE number. For a new event, this should be zero.
294
- * Each time the organizer makes a significant revision, the sequence
295
- * number should be incremented.
263
+ * cal.alarms(); // --> [ICalAlarm, ICalAlarm]
264
+ ```
296
265
  *
297
- * @param sequence Sequence number or null to unset it
266
+ * @since 0.2.0
298
267
  */
299
- sequence(sequence: number): this;
300
- sequence(sequence?: number): this | number {
301
- if (sequence === undefined) {
302
- return this.data.sequence;
303
- }
304
-
305
- const s = parseInt(String(sequence), 10);
306
- if (isNaN(s)) {
307
- throw new Error('`sequence` must be a number!');
268
+ alarms(alarms: ICalAlarm[] | ICalAlarmData[]): this;
269
+ alarms(alarms?: ICalAlarm[] | ICalAlarmData[]): ICalAlarm[] | this {
270
+ if (!alarms) {
271
+ return this.data.alarms;
308
272
  }
309
273
 
310
- this.data.sequence = sequence;
274
+ alarms.forEach((alarm: ICalAlarm | ICalAlarmData) =>
275
+ this.createAlarm(alarm),
276
+ );
311
277
  return this;
312
278
  }
313
279
 
314
280
  /**
315
- * Get the event start time which is currently
316
- * set. Can be any supported date object.
317
- *
281
+ * Get the event's allDay flag
318
282
  * @since 0.2.0
319
283
  */
320
- start(): ICalDateTimeValue;
321
-
284
+ allDay(): boolean;
322
285
  /**
323
- * Set the appointment date of beginning, which is required for all events.
324
- * You can use any supported date object, see
325
- * [Readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
326
- * for details about supported values and timezone handling.
286
+ * Set the event's allDay flag.
287
+ *
288
+ * ```javascript
289
+ * event.allDay(true); // appointment is for the whole day
290
+ * ```
327
291
  *
328
292
  * ```typescript
329
293
  * import ical from 'ical-generator';
330
294
  *
331
295
  * const cal = ical();
332
296
  *
333
- * const event = cal.createEvent({
334
- * start: new Date('2020-01-01')
297
+ * cal.createEvent({
298
+ * start: new Date('2020-01-01'),
299
+ * summary: 'Very Important Day',
300
+ * allDay: true
335
301
  * });
336
302
  *
337
- * // overwrites old start date
338
- * event.start(new Date('2024-02-01'));
339
- *
340
303
  * cal.toString();
341
304
  * ```
342
305
  *
@@ -345,272 +308,254 @@ export default class ICalEvent {
345
308
  * VERSION:2.0
346
309
  * PRODID:-//sebbo.net//ical-generator//EN
347
310
  * BEGIN:VEVENT
348
- * UID:7e2aee64-b07a-4256-9b3e-e9eaa452bac8
311
+ * UID:1964fe8d-32c5-4f2a-bd62-7d9d7de5992b
349
312
  * SEQUENCE:0
350
- * DTSTAMP:20240212T190915Z
351
- * DTSTART:20240201T000000Z
352
- * SUMMARY:
313
+ * DTSTAMP:20240212T191956Z
314
+ * DTSTART;VALUE=DATE:20200101
315
+ * X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
316
+ * X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
317
+ * SUMMARY:Very Important Day
353
318
  * END:VEVENT
354
319
  * END:VCALENDAR
355
320
  * ```
356
321
  *
357
322
  * @since 0.2.0
358
323
  */
359
- start(start: ICalDateTimeValue): this;
360
- start(start?: ICalDateTimeValue): this | ICalDateTimeValue {
361
- if (start === undefined) {
362
- this.swapStartAndEndIfRequired();
363
- return this.data.start;
324
+ allDay(allDay: boolean): this;
325
+ allDay(allDay?: boolean): boolean | this {
326
+ if (allDay === undefined) {
327
+ return this.data.allDay;
364
328
  }
365
329
 
366
- this.data.start = checkDate(start, 'start');
330
+ this.data.allDay = Boolean(allDay);
367
331
  return this;
368
332
  }
369
333
 
370
334
  /**
371
- * Get the event end time which is currently
372
- * set. Can be any supported date object.
373
- *
374
- * @since 0.2.0
335
+ * Get all attachment urls
336
+ * @since 3.2.0-develop.1
375
337
  */
376
- end(): ICalDateTimeValue | null;
377
-
338
+ attachments(): string[];
378
339
  /**
379
- * Set the appointment date of end. You can use any supported date object, see
380
- * [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
381
- * for details about supported values and timezone handling.
340
+ * Add one or multiple alarms
382
341
  *
383
- * @since 0.2.0
342
+ * ```javascript
343
+ * const event = ical().createEvent();
344
+ *
345
+ * cal.attachments([
346
+ * 'https://files.sebbo.net/calendar/attachments/foo',
347
+ * 'https://files.sebbo.net/calendar/attachments/bar'
348
+ * ]);
349
+ *
350
+ * cal.attachments(); // --> [string, string]
351
+ ```
352
+ *
353
+ * 3.2.0-develop.1
384
354
  */
385
- end(end: ICalDateTimeValue | null): this;
386
- end(end?: ICalDateTimeValue | null): this | ICalDateTimeValue | null {
387
- if (end === undefined) {
388
- this.swapStartAndEndIfRequired();
389
- return this.data.end;
390
- }
391
- if (end === null) {
392
- this.data.end = null;
393
- return this;
355
+ attachments(attachments: string[]): this;
356
+ attachments(attachments?: string[]): string[] | this {
357
+ if (!attachments) {
358
+ return this.data.attachments;
394
359
  }
395
360
 
396
- this.data.end = checkDate(end, 'end');
361
+ attachments.forEach((attachment: string) =>
362
+ this.createAttachment(attachment),
363
+ );
397
364
  return this;
398
365
  }
399
366
 
400
367
  /**
401
- * Checks if the start date is after the end date and swaps them if necessary.
402
- * @private
403
- */
404
- private swapStartAndEndIfRequired(): void {
405
- if (this.data.start && this.data.end && toDate(this.data.start).getTime() > toDate(this.data.end).getTime()) {
406
- const t = this.data.start;
407
- this.data.start = this.data.end;
408
- this.data.end = t;
409
- }
410
- }
411
-
412
- /**
413
- * Get the event's recurrence id
368
+ * Get all attendees
414
369
  * @since 0.2.0
415
370
  */
416
- recurrenceId(): ICalDateTimeValue | null;
417
-
371
+ attendees(): ICalAttendee[];
418
372
  /**
419
- * Set the event's recurrence id. You can use any supported date object, see
420
- * [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
421
- * for details about supported values and timezone handling.
373
+ * Add multiple attendees to your event
374
+ *
375
+ * ```javascript
376
+ * const event = ical().createEvent();
377
+ *
378
+ * cal.attendees([
379
+ * {email: 'a@example.com', name: 'Person A'},
380
+ * {email: 'b@example.com', name: 'Person B'}
381
+ * ]);
382
+ *
383
+ * cal.attendees(); // --> [ICalAttendee, ICalAttendee]
384
+ * ```
422
385
  *
423
386
  * @since 0.2.0
424
387
  */
425
- recurrenceId(recurrenceId: ICalDateTimeValue | null): this;
426
- recurrenceId(recurrenceId?: ICalDateTimeValue | null): this | ICalDateTimeValue | null {
427
- if (recurrenceId === undefined) {
428
- return this.data.recurrenceId;
429
- }
430
- if (recurrenceId === null) {
431
- this.data.recurrenceId = null;
432
- return this;
388
+ attendees(attendees: (ICalAttendee | ICalAttendeeData | string)[]): this;
389
+ attendees(
390
+ attendees?: (ICalAttendee | ICalAttendeeData | string)[],
391
+ ): ICalAttendee[] | this {
392
+ if (!attendees) {
393
+ return this.data.attendees;
433
394
  }
434
395
 
435
- this.data.recurrenceId = checkDate(recurrenceId, 'recurrenceId');
396
+ attendees.forEach((attendee) => this.createAttendee(attendee));
436
397
  return this;
437
398
  }
438
399
 
439
400
  /**
440
- * Get the event's timezone.
441
- * @since 0.2.6
401
+ * Get the event's busy status
402
+ * @since 1.0.2
442
403
  */
443
- timezone(): string | null;
444
-
404
+ busystatus(): ICalEventBusyStatus | null;
445
405
  /**
446
- * Sets the time zone to be used for this event. If a time zone has been
447
- * defined in both the event and the calendar, the time zone of the event
448
- * is used.
449
- *
450
- * Please note that if the time zone is set, ical-generator assumes
451
- * that all times are already in the correct time zone. Alternatively,
452
- * a `moment-timezone` or a Luxon object can be passed with `setZone`,
453
- * ical-generator will then set the time zone itself.
454
- *
455
- * This and the 'floating' flag (see below) are mutually exclusive, and setting a timezone will unset the
456
- * 'floating' flag. If neither 'timezone' nor 'floating' are set, the date will be output with in UTC format
457
- * (see [date-time form #2 in section 3.3.5 of RFC 554](https://tools.ietf.org/html/rfc5545#section-3.3.5)).
458
- *
459
- * See [Readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones) for details about
460
- * supported values and timezone handling.
406
+ * Set the event's busy status. Will add the
407
+ * [`X-MICROSOFT-CDO-BUSYSTATUS`](https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcical/cd68eae7-ed65-4dd3-8ea7-ad585c76c736)
408
+ * attribute to your event.
461
409
  *
462
410
  * ```javascript
463
- * event.timezone('America/New_York');
411
+ * import ical, {ICalEventBusyStatus} from 'ical-generator';
412
+ * event.busystatus(ICalEventBusyStatus.BUSY);
464
413
  * ```
465
414
  *
466
- * @see https://github.com/sebbo2002/ical-generator#-date-time--timezones
467
- * @since 0.2.6
415
+ * @since 1.0.2
468
416
  */
469
- timezone(timezone: string | null): this;
470
- timezone(timezone?: string | null): this | string | null {
471
- if (timezone === undefined && this.data.timezone !== null) {
472
- return this.data.timezone;
473
- }
474
- if (timezone === undefined) {
475
- return this.calendar.timezone();
417
+ busystatus(busystatus: ICalEventBusyStatus | null): this;
418
+ busystatus(
419
+ busystatus?: ICalEventBusyStatus | null,
420
+ ): ICalEventBusyStatus | null | this {
421
+ if (busystatus === undefined) {
422
+ return this.data.busystatus;
476
423
  }
477
-
478
- this.data.timezone = timezone && timezone !== 'UTC' ? timezone.toString() : null;
479
- if (this.data.timezone) {
480
- this.data.floating = false;
424
+ if (busystatus === null) {
425
+ this.data.busystatus = null;
426
+ return this;
481
427
  }
482
428
 
429
+ this.data.busystatus = checkEnum(
430
+ ICalEventBusyStatus,
431
+ busystatus,
432
+ ) as ICalEventBusyStatus;
483
433
  return this;
484
434
  }
485
435
 
486
436
  /**
487
- * Get the event's timestamp
488
- * @since 0.2.0
489
- * @see {@link timestamp}
437
+ * Get all categories
438
+ * @since 0.3.0
490
439
  */
491
- stamp(): ICalDateTimeValue;
492
-
440
+ categories(): ICalCategory[];
493
441
  /**
494
- * Set the appointment date of creation. Defaults to the current time and date (`new Date()`). You can use
495
- * any supported date object, see [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
496
- * for details about supported values and timezone handling.
442
+ * Add categories to the event or return all selected categories.
497
443
  *
498
- * @since 0.2.0
499
- * @see {@link timestamp}
444
+ * ```javascript
445
+ * const event = ical().createEvent();
446
+ *
447
+ * cal.categories([
448
+ * {name: 'APPOINTMENT'},
449
+ * {name: 'MEETING'}
450
+ * ]);
451
+ *
452
+ * cal.categories(); // --> [ICalCategory, ICalCategory]
453
+ * ```
454
+ *
455
+ * @since 0.3.0
500
456
  */
501
- stamp(stamp: ICalDateTimeValue): this;
502
- stamp(stamp?: ICalDateTimeValue): this | ICalDateTimeValue {
503
- if (stamp === undefined) {
504
- return this.data.stamp;
457
+ categories(categories: (ICalCategory | ICalCategoryData)[]): this;
458
+ categories(
459
+ categories?: (ICalCategory | ICalCategoryData)[],
460
+ ): ICalCategory[] | this {
461
+ if (!categories) {
462
+ return this.data.categories;
505
463
  }
506
464
 
507
- this.data.stamp = checkDate(stamp, 'stamp');
465
+ categories.forEach((category) => this.createCategory(category));
508
466
  return this;
509
467
  }
510
-
511
468
  /**
512
- * Get the event's timestamp
513
- * @since 0.2.0
514
- * @see {@link stamp}
469
+ * Get the event's class
470
+ * @since 2.0.0
515
471
  */
516
- timestamp(): ICalDateTimeValue;
517
-
472
+ class(): ICalEventClass | null;
518
473
  /**
519
- * Set the appointment date of creation. Defaults to the current time and date (`new Date()`). You can use
520
- * any supported date object, see [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
521
- * for details about supported values and timezone handling.
474
+ * Set the event's class
522
475
  *
523
- * @since 0.2.0
524
- * @see {@link stamp}
476
+ * ```javascript
477
+ * import ical, { ICalEventClass } from 'ical-generator';
478
+ * event.class(ICalEventClass.PRIVATE);
479
+ * ```
480
+ *
481
+ * @since 2.0.0
525
482
  */
526
- timestamp(stamp: ICalDateTimeValue): this;
527
- timestamp(stamp?: ICalDateTimeValue): this | ICalDateTimeValue {
528
- if (stamp === undefined) {
529
- return this.stamp();
483
+ class(class_: ICalEventClass | null): this;
484
+ class(class_?: ICalEventClass | null): ICalEventClass | null | this {
485
+ if (class_ === undefined) {
486
+ return this.data.class;
487
+ }
488
+ if (class_ === null) {
489
+ this.data.class = null;
490
+ return this;
530
491
  }
531
492
 
532
- return this.stamp(stamp);
493
+ this.data.class = checkEnum(ICalEventClass, class_) as ICalEventClass;
494
+ return this;
533
495
  }
534
-
535
- /**
536
- * Get the event's allDay flag
537
- * @since 0.2.0
538
- */
539
- allDay(): boolean;
540
-
541
496
  /**
542
- * Set the event's allDay flag.
497
+ * Creates a new {@link ICalAlarm} and returns it. Use options to prefill
498
+ * the alarm's attributes. Calling this method without options will create
499
+ * an empty alarm.
543
500
  *
544
501
  * ```javascript
545
- * event.allDay(true); // → appointment is for the whole day
546
- * ```
547
- *
548
- * ```typescript
549
- * import ical from 'ical-generator';
550
- *
551
502
  * const cal = ical();
503
+ * const event = cal.createEvent();
504
+ * const alarm = event.createAlarm({type: ICalAlarmType.display, trigger: 300});
552
505
  *
553
- * cal.createEvent({
554
- * start: new Date('2020-01-01'),
555
- * summary: 'Very Important Day',
556
- * allDay: true
506
+ * // add another alarm
507
+ * event.createAlarm({
508
+ * type: ICalAlarmType.audio,
509
+ * trigger: 300, // 5min before event
557
510
  * });
558
- *
559
- * cal.toString();
560
- * ```
561
- *
562
- * ```text
563
- * BEGIN:VCALENDAR
564
- * VERSION:2.0
565
- * PRODID:-//sebbo.net//ical-generator//EN
566
- * BEGIN:VEVENT
567
- * UID:1964fe8d-32c5-4f2a-bd62-7d9d7de5992b
568
- * SEQUENCE:0
569
- * DTSTAMP:20240212T191956Z
570
- * DTSTART;VALUE=DATE:20200101
571
- * X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
572
- * X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE
573
- * SUMMARY:Very Important Day
574
- * END:VEVENT
575
- * END:VCALENDAR
576
511
  * ```
577
512
  *
578
- * @since 0.2.0
513
+ * @since 0.2.1
579
514
  */
580
- allDay(allDay: boolean): this;
581
- allDay(allDay?: boolean): this | boolean {
582
- if (allDay === undefined) {
583
- return this.data.allDay;
584
- }
585
-
586
- this.data.allDay = Boolean(allDay);
587
- return this;
515
+ createAlarm(data: ICalAlarm | ICalAlarmData): ICalAlarm {
516
+ const alarm =
517
+ data instanceof ICalAlarm ? data : new ICalAlarm(data, this);
518
+ this.data.alarms.push(alarm);
519
+ return alarm;
588
520
  }
589
521
 
590
522
  /**
591
- * Get the event's floating flag.
592
- * @since 0.2.0
523
+ * Adds an attachment to the event by adding the file URL to the calendar.
524
+ *
525
+ * `ical-generator` only supports external attachments. File attachments that
526
+ * are directly included in the file are not supported, because otherwise the
527
+ * calendar file could easily become unfavourably large.
528
+ *
529
+ * ```javascript
530
+ * const cal = ical();
531
+ * const event = cal.createEvent();
532
+ * event.createAttachment('https://files.sebbo.net/calendar/attachments/foo');
533
+ * ```
534
+ *
535
+ * @since 3.2.0-develop.1
593
536
  */
594
- floating(): boolean;
595
- floating(floating: boolean): this;
537
+ createAttachment(url: string): this {
538
+ this.data.attachments.push(url);
539
+ return this;
540
+ }
596
541
 
597
542
  /**
598
- * Set the event's floating flag. This unsets the event's timezone.
599
- * Events whose floating flag is set to true always take place at the
600
- * same time, regardless of the time zone.
543
+ * Creates a new {@link ICalAttendee} and returns it. Use options to prefill
544
+ * the attendee's attributes. Calling this method without options will create
545
+ * an empty attendee.
601
546
  *
602
- * ```typescript
547
+ * ```javascript
603
548
  * import ical from 'ical-generator';
604
549
  *
605
550
  * const cal = ical();
606
- *
607
- * cal.createEvent({
608
- * start: new Date('2020-01-01T20:00:00Z'),
609
- * summary: 'Always at 20:00 in every <Timezone',
610
- * floating: true
551
+ * const event = cal.createEvent({
552
+ * start: new Date()
611
553
  * });
612
554
  *
613
- * cal.toString();
555
+ * event.createAttendee({email: 'hui@example.com', name: 'Hui'});
556
+ *
557
+ * // add another attendee
558
+ * event.createAttendee('Buh <buh@example.net>');
614
559
  * ```
615
560
  *
616
561
  * ```text
@@ -618,117 +563,188 @@ export default class ICalEvent {
618
563
  * VERSION:2.0
619
564
  * PRODID:-//sebbo.net//ical-generator//EN
620
565
  * BEGIN:VEVENT
621
- * UID:5d7278f9-ada3-40ef-83d1-23c29ce0a763
566
+ * UID:b4944f07-98e4-4581-ac80-2589bb20273d
622
567
  * SEQUENCE:0
623
- * DTSTAMP:20240212T192214Z
624
- * DTSTART:20200101T200000
625
- * SUMMARY:Always at 20:00 in every <Timezone
568
+ * DTSTAMP:20240212T194232Z
569
+ * DTSTART:20240212T194232Z
570
+ * SUMMARY:
571
+ * ATTENDEE;ROLE=REQ-PARTICIPANT;CN="Hui":MAILTO:hui@example.com
572
+ * ATTENDEE;ROLE=REQ-PARTICIPANT;CN="Buh":MAILTO:buh@example.net
626
573
  * END:VEVENT
627
574
  * END:VCALENDAR
628
575
  * ```
629
576
  *
577
+ * As with the organizer, you can also add an explicit `mailto` address.
578
+ *
579
+ * ```javascript
580
+ * event.createAttendee({email: 'hui@example.com', name: 'Hui', mailto: 'another@mailto.com'});
581
+ *
582
+ * // overwrite an attendee's mailto address
583
+ * attendee.mailto('another@mailto.net');
584
+ * ```
585
+ *
630
586
  * @since 0.2.0
631
587
  */
632
- floating(floating?: boolean): this | boolean {
633
- if (floating === undefined) {
634
- return this.data.floating;
588
+ createAttendee(
589
+ data: ICalAttendee | ICalAttendeeData | string,
590
+ ): ICalAttendee {
591
+ if (data instanceof ICalAttendee) {
592
+ this.data.attendees.push(data);
593
+ return data;
635
594
  }
636
-
637
- this.data.floating = Boolean(floating);
638
- if (this.data.floating) {
639
- this.data.timezone = null;
595
+ if (typeof data === 'string') {
596
+ data = { email: data, ...checkNameAndMail('data', data) };
597
+ }
598
+
599
+ const attendee = new ICalAttendee(data, this);
600
+ this.data.attendees.push(attendee);
601
+ return attendee;
602
+ }
603
+ /**
604
+ * Creates a new {@link ICalCategory} and returns it. Use options to prefill the category's attributes.
605
+ * Calling this method without options will create an empty category.
606
+ *
607
+ * ```javascript
608
+ * const cal = ical();
609
+ * const event = cal.createEvent();
610
+ * const category = event.createCategory({name: 'APPOINTMENT'});
611
+ *
612
+ * // add another category
613
+ * event.createCategory({
614
+ * name: 'MEETING'
615
+ * });
616
+ * ```
617
+ *
618
+ * @since 0.3.0
619
+ */
620
+ createCategory(data: ICalCategory | ICalCategoryData): ICalCategory {
621
+ const category =
622
+ data instanceof ICalCategory ? data : new ICalCategory(data);
623
+ this.data.categories.push(category);
624
+ return category;
625
+ }
626
+
627
+ /**
628
+ * Get the event's creation date
629
+ * @since 0.3.0
630
+ */
631
+ created(): ICalDateTimeValue | null;
632
+ /**
633
+ * Set the event's creation date
634
+ * @since 0.3.0
635
+ */
636
+ created(created: ICalDateTimeValue | null): this;
637
+ created(
638
+ created?: ICalDateTimeValue | null,
639
+ ): ICalDateTimeValue | null | this {
640
+ if (created === undefined) {
641
+ return this.data.created;
642
+ }
643
+ if (created === null) {
644
+ this.data.created = null;
645
+ return this;
640
646
  }
641
647
 
648
+ this.data.created = checkDate(created, 'created');
642
649
  return this;
643
650
  }
644
651
 
645
652
  /**
646
- * Get the event's repeating options
653
+ * Get the event's description as an {@link ICalDescription} object.
647
654
  * @since 0.2.0
648
655
  */
649
- repeating(): ICalEventJSONRepeatingData | ICalRRuleStub | string | null;
650
-
656
+ description(): ICalDescription | null;
651
657
  /**
652
- * Set the event's repeating options by passing an {@link ICalRepeatingOptions} object.
658
+ * Set the events description by passing a plaintext string or
659
+ * an object containing both a plaintext and a html description.
660
+ * Only a few calendar apps support html descriptions and like in
661
+ * emails, supported HTML tags and styling is limited.
653
662
  *
654
663
  * ```javascript
655
- * event.repeating({
656
- * freq: 'MONTHLY', // required
657
- * count: 5,
658
- * interval: 2,
659
- * until: new Date('Jan 01 2014 00:00:00 UTC'),
660
- * byDay: ['su', 'mo'], // repeat only sunday and monday
661
- * byMonth: [1, 2], // repeat only in january and february,
662
- * byMonthDay: [1, 15], // repeat only on the 1st and 15th
663
- * bySetPos: 3, // repeat every 3rd sunday (will take the first element of the byDay array)
664
- * exclude: [new Date('Dec 25 2013 00:00:00 UTC')], // exclude these dates
665
- * excludeTimezone: 'Europe/Berlin', // timezone of exclude
666
- * wkst: 'SU' // Start the week on Sunday, default is Monday
667
- * });
668
- * ```
669
- *
670
- * **Example:**
671
- *
672
- *```typescript
673
- * import ical, { ICalEventRepeatingFreq } from 'ical-generator';
674
- *
675
- * const cal = ical();
676
- *
677
- * const event = cal.createEvent({
678
- * start: new Date('2020-01-01T20:00:00Z'),
679
- * summary: 'Repeating Event'
680
- * });
681
- * event.repeating({
682
- * freq: ICalEventRepeatingFreq.WEEKLY,
683
- * count: 4
664
+ * event.description({
665
+ * plain: 'Hello World!',
666
+ * html: '<p>Hello World!</p>'
684
667
  * });
685
- *
686
- * cal.toString();
687
668
  * ```
688
669
  *
689
670
  * ```text
690
- * BEGIN:VCALENDAR
691
- * VERSION:2.0
692
- * PRODID:-//sebbo.net//ical-generator//EN
693
- * BEGIN:VEVENT
694
- * UID:b80e6a68-c2cd-48f5-b94d-cecc7ce83871
695
- * SEQUENCE:0
696
- * DTSTAMP:20240212T193646Z
697
- * DTSTART:20200101T200000Z
698
- * RRULE:FREQ=WEEKLY;COUNT=4
699
- * SUMMARY:Repeating Event
700
- * END:VEVENT
701
- * END:VCALENDAR
671
+ * DESCRIPTION:Hello World!
672
+ * X-ALT-DESC;FMTTYPE=text/html:<p>Hello World!</p>
702
673
  * ```
703
674
  *
704
675
  * @since 0.2.0
705
676
  */
706
- repeating(repeating: ICalRepeatingOptions | null): this;
677
+ description(description: ICalDescription | null | string): this;
678
+ description(
679
+ description?: ICalDescription | null | string,
680
+ ): ICalDescription | null | this {
681
+ if (description === undefined) {
682
+ return this.data.description;
683
+ }
684
+ if (description === null) {
685
+ this.data.description = null;
686
+ return this;
687
+ }
688
+
689
+ if (typeof description === 'string') {
690
+ this.data.description = { plain: description };
691
+ } else {
692
+ this.data.description = description;
693
+ }
694
+ return this;
695
+ }
707
696
 
708
697
  /**
709
- * Set the event's repeating options by passing an [RRule object](https://github.com/jakubroztocil/rrule).
710
- * @since 2.0.0-develop.5
698
+ * Get the event end time which is currently
699
+ * set. Can be any supported date object.
700
+ *
701
+ * @since 0.2.0
702
+ */
703
+ end(): ICalDateTimeValue | null;
704
+ /**
705
+ * Set the appointment date of end. You can use any supported date object, see
706
+ * [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
707
+ * for details about supported values and timezone handling.
708
+ *
709
+ * @since 0.2.0
710
+ */
711
+ end(end: ICalDateTimeValue | null): this;
712
+ end(end?: ICalDateTimeValue | null): ICalDateTimeValue | null | this {
713
+ if (end === undefined) {
714
+ this.swapStartAndEndIfRequired();
715
+ return this.data.end;
716
+ }
717
+ if (end === null) {
718
+ this.data.end = null;
719
+ return this;
720
+ }
721
+
722
+ this.data.end = checkDate(end, 'end');
723
+ return this;
724
+ }
725
+
726
+ /**
727
+ * Get the event's floating flag.
728
+ * @since 0.2.0
729
+ */
730
+ floating(): boolean;
731
+ floating(floating: boolean): this;
732
+ /**
733
+ * Set the event's floating flag. This unsets the event's timezone.
734
+ * Events whose floating flag is set to true always take place at the
735
+ * same time, regardless of the time zone.
711
736
  *
712
737
  * ```typescript
713
738
  * import ical from 'ical-generator';
714
- * import { datetime, RRule } from 'rrule';
715
739
  *
716
740
  * const cal = ical();
717
741
  *
718
- * const event = cal.createEvent({
742
+ * cal.createEvent({
719
743
  * start: new Date('2020-01-01T20:00:00Z'),
720
- * summary: 'Repeating Event'
744
+ * summary: 'Always at 20:00 in every <Timezone',
745
+ * floating: true
721
746
  * });
722
747
  *
723
- * const rule = new RRule({
724
- * freq: RRule.WEEKLY,
725
- * interval: 5,
726
- * byweekday: [RRule.MO, RRule.FR],
727
- * dtstart: datetime(2012, 2, 1, 10, 30),
728
- * until: datetime(2012, 12, 31)
729
- * })
730
- * event.repeating(rule);
731
- *
732
748
  * cal.toString();
733
749
  * ```
734
750
  *
@@ -737,150 +753,81 @@ export default class ICalEvent {
737
753
  * VERSION:2.0
738
754
  * PRODID:-//sebbo.net//ical-generator//EN
739
755
  * BEGIN:VEVENT
740
- * UID:36585e40-8fa8-460d-af0c-88b6f434030b
756
+ * UID:5d7278f9-ada3-40ef-83d1-23c29ce0a763
741
757
  * SEQUENCE:0
742
- * DTSTAMP:20240212T193827Z
743
- * DTSTART:20200101T200000Z
744
- * RRULE:FREQ=WEEKLY;INTERVAL=5;BYDAY=MO,FR;UNTIL=20121231T000000Z
745
- * SUMMARY:Repeating Event
758
+ * DTSTAMP:20240212T192214Z
759
+ * DTSTART:20200101T200000
760
+ * SUMMARY:Always at 20:00 in every <Timezone
746
761
  * END:VEVENT
747
762
  * END:VCALENDAR
748
763
  * ```
764
+ *
765
+ * @since 0.2.0
749
766
  */
750
- repeating(repeating: ICalRRuleStub | null): this;
767
+ floating(floating?: boolean): boolean | this {
768
+ if (floating === undefined) {
769
+ return this.data.floating;
770
+ }
771
+
772
+ this.data.floating = Boolean(floating);
773
+ if (this.data.floating) {
774
+ this.data.timezone = null;
775
+ }
776
+
777
+ return this;
778
+ }
751
779
 
752
780
  /**
753
- * Set the events repeating options by passing a string which is inserted in the ical file.
754
- * @since 2.0.0-develop.5
781
+ * Get the event's ID
782
+ * @since 0.2.0
755
783
  */
756
- repeating(repeating: string | null): this;
757
-
784
+ id(): string;
758
785
  /**
759
- * @internal
786
+ * Use this method to set the event's ID.
787
+ * If not set, a UUID will be generated randomly.
788
+ *
789
+ * @param id Event ID you want to set
760
790
  */
761
- repeating(repeating: ICalRepeatingOptions | ICalRRuleStub | string | null): this;
762
- repeating(repeating?: ICalRepeatingOptions | ICalRRuleStub | string | null): this | ICalEventJSONRepeatingData | ICalRRuleStub | string | null {
763
- if (repeating === undefined) {
764
- return this.data.repeating;
765
- }
766
- if (!repeating) {
767
- this.data.repeating = null;
768
- return this;
769
- }
770
- if(isRRule(repeating) || typeof repeating === 'string') {
771
- this.data.repeating = repeating;
772
- return this;
773
- }
774
-
775
- this.data.repeating = {
776
- freq: checkEnum(ICalEventRepeatingFreq, repeating.freq) as ICalEventRepeatingFreq
777
- };
778
-
779
- if (repeating.count) {
780
- if (!isFinite(repeating.count)) {
781
- throw new Error('`repeating.count` must be a finite number!');
782
- }
783
-
784
- this.data.repeating.count = repeating.count;
791
+ id(id: number | string): this;
792
+ id(id?: number | string): string | this {
793
+ if (id === undefined) {
794
+ return this.data.id;
785
795
  }
786
796
 
787
- if (repeating.interval) {
788
- if (!isFinite(repeating.interval)) {
789
- throw new Error('`repeating.interval` must be a finite number!');
790
- }
797
+ this.data.id = String(id);
798
+ return this;
799
+ }
791
800
 
792
- this.data.repeating.interval = repeating.interval;
801
+ /**
802
+ * Get the event's last modification date
803
+ * @since 0.3.0
804
+ */
805
+ lastModified(): ICalDateTimeValue | null;
806
+ /**
807
+ * Set the event's last modification date
808
+ * @since 0.3.0
809
+ */
810
+ lastModified(lastModified: ICalDateTimeValue | null): this;
811
+ lastModified(
812
+ lastModified?: ICalDateTimeValue | null,
813
+ ): ICalDateTimeValue | null | this {
814
+ if (lastModified === undefined) {
815
+ return this.data.lastModified;
793
816
  }
794
-
795
- if (repeating.until !== undefined) {
796
- this.data.repeating.until = checkDate(repeating.until, 'repeating.until');
817
+ if (lastModified === null) {
818
+ this.data.lastModified = null;
819
+ return this;
797
820
  }
798
821
 
799
- if (repeating.byDay) {
800
- const byDayArray = Array.isArray(repeating.byDay) ? repeating.byDay : [repeating.byDay];
801
- this.data.repeating.byDay = byDayArray.map(day => checkEnum(ICalWeekday, day) as ICalWeekday);
802
- }
803
-
804
- if (repeating.byMonth) {
805
- const byMonthArray = Array.isArray(repeating.byMonth) ? repeating.byMonth : [repeating.byMonth];
806
- this.data.repeating.byMonth = byMonthArray.map(month => {
807
- if (typeof month !== 'number' || month < 1 || month > 12) {
808
- throw new Error('`repeating.byMonth` contains invalid value `' + month + '`!');
809
- }
810
-
811
- return month;
812
- });
813
- }
814
-
815
- if (repeating.byMonthDay) {
816
- const byMonthDayArray = Array.isArray(repeating.byMonthDay) ? repeating.byMonthDay : [repeating.byMonthDay];
817
-
818
-
819
- this.data.repeating.byMonthDay = byMonthDayArray.map(monthDay => {
820
- if (typeof monthDay !== 'number' || monthDay < -31 || monthDay > 31 || monthDay === 0) {
821
- throw new Error('`repeating.byMonthDay` contains invalid value `' + monthDay + '`!');
822
- }
823
-
824
- return monthDay;
825
- });
826
- }
827
-
828
- if (repeating.bySetPos) {
829
- if (!this.data.repeating.byDay) {
830
- throw '`repeating.bySetPos` must be used along with `repeating.byDay`!';
831
- }
832
- const bySetPosArray = Array.isArray(repeating.bySetPos) ? repeating.bySetPos : [repeating.bySetPos];
833
- this.data.repeating.bySetPos = bySetPosArray.map(bySetPos => {
834
- if (typeof bySetPos !== 'number' || bySetPos < -366 || bySetPos > 366 || bySetPos === 0) {
835
- throw '`repeating.bySetPos` contains invalid value `' + bySetPos + '`!';
836
- }
837
- return bySetPos;
838
- });
839
- }
840
-
841
- if (repeating.exclude) {
842
- const excludeArray = Array.isArray(repeating.exclude) ? repeating.exclude : [repeating.exclude];
843
- this.data.repeating.exclude = excludeArray.map((exclude, i) => {
844
- return checkDate(exclude, `repeating.exclude[${i}]`);
845
- });
846
- }
847
-
848
- if (repeating.startOfWeek) {
849
- this.data.repeating.startOfWeek = checkEnum(ICalWeekday, repeating.startOfWeek) as ICalWeekday;
850
- }
851
-
852
- return this;
853
- }
854
-
855
- /**
856
- * Get the event's summary
857
- * @since 0.2.0
858
- */
859
- summary(): string;
860
-
861
- /**
862
- * Set the event's summary.
863
- * Defaults to an empty string if nothing is set.
864
- *
865
- * @since 0.2.0
866
- */
867
- summary(summary: string): this;
868
- summary(summary?: string): this | string {
869
- if (summary === undefined) {
870
- return this.data.summary;
871
- }
872
-
873
- this.data.summary = summary ? String(summary) : '';
822
+ this.data.lastModified = checkDate(lastModified, 'lastModified');
874
823
  return this;
875
824
  }
876
825
 
877
-
878
826
  /**
879
827
  * Get the event's location
880
828
  * @since 0.2.0
881
829
  */
882
830
  location(): ICalLocation | null;
883
-
884
831
  /**
885
832
  * Set the event's location by passing a string (minimum) or
886
833
  * an {@link ICalLocationWithTitle} object which will also fill the iCal
@@ -925,25 +872,32 @@ export default class ICalEvent {
925
872
  *
926
873
  * @since 0.2.0
927
874
  */
928
- location(location: ICalLocation | string | null): this;
929
- location(location?: ICalLocation | string | null): this | ICalLocation | null {
875
+ location(location: ICalLocation | null | string): this;
876
+ location(
877
+ location?: ICalLocation | null | string,
878
+ ): ICalLocation | null | this {
930
879
  if (location === undefined) {
931
880
  return this.data.location;
932
881
  }
933
882
  if (typeof location === 'string') {
934
883
  this.data.location = {
935
- title: location
884
+ title: location,
936
885
  };
937
886
  return this;
938
887
  }
939
- if (location && (
940
- ('title' in location && !location.title) ||
941
- (location?.geo && (typeof location.geo.lat !== 'number' || !isFinite(location.geo.lat) || typeof location.geo.lon !== 'number' || !isFinite(location.geo.lon))) ||
942
- (!('title' in location) && !location?.geo)
943
- )) {
888
+ if (
889
+ location &&
890
+ (('title' in location && !location.title) ||
891
+ (location?.geo &&
892
+ (typeof location.geo.lat !== 'number' ||
893
+ !isFinite(location.geo.lat) ||
894
+ typeof location.geo.lon !== 'number' ||
895
+ !isFinite(location.geo.lon))) ||
896
+ (!('title' in location) && !location?.geo))
897
+ ) {
944
898
  throw new Error(
945
- '`location` isn\'t formatted correctly. See https://sebbo2002.github.io/ical-generator/'+
946
- 'develop/reference/classes/ICalEvent.html#location'
899
+ "`location` isn't formatted correctly. See https://sebbo2002.github.io/ical-generator/" +
900
+ 'develop/reference/classes/ICalEvent.html#location',
947
901
  );
948
902
  }
949
903
 
@@ -951,59 +905,11 @@ export default class ICalEvent {
951
905
  return this;
952
906
  }
953
907
 
954
-
955
- /**
956
- * Get the event's description as an {@link ICalDescription} object.
957
- * @since 0.2.0
958
- */
959
- description(): ICalDescription | null;
960
-
961
- /**
962
- * Set the events description by passing a plaintext string or
963
- * an object containing both a plaintext and a html description.
964
- * Only a few calendar apps support html descriptions and like in
965
- * emails, supported HTML tags and styling is limited.
966
- *
967
- * ```javascript
968
- * event.description({
969
- * plain: 'Hello World!',
970
- * html: '<p>Hello World!</p>'
971
- * });
972
- * ```
973
- *
974
- * ```text
975
- * DESCRIPTION:Hello World!
976
- * X-ALT-DESC;FMTTYPE=text/html:<p>Hello World!</p>
977
- * ```
978
- *
979
- * @since 0.2.0
980
- */
981
- description(description: ICalDescription | string | null): this;
982
- description(description?: ICalDescription | string | null): this | ICalDescription | null {
983
- if (description === undefined) {
984
- return this.data.description;
985
- }
986
- if (description === null) {
987
- this.data.description = null;
988
- return this;
989
- }
990
-
991
- if (typeof description === 'string') {
992
- this.data.description = {plain: description};
993
- }
994
- else {
995
- this.data.description = description;
996
- }
997
- return this;
998
- }
999
-
1000
-
1001
908
  /**
1002
909
  * Get the event's organizer
1003
910
  * @since 0.2.0
1004
911
  */
1005
912
  organizer(): ICalOrganizer | null;
1006
-
1007
913
  /**
1008
914
  * Set the event's organizer
1009
915
  *
@@ -1031,8 +937,10 @@ export default class ICalEvent {
1031
937
  *
1032
938
  * @since 0.2.0
1033
939
  */
1034
- organizer(organizer: ICalOrganizer | string | null): this;
1035
- organizer(organizer?: ICalOrganizer | string | null): this | ICalOrganizer | null {
940
+ organizer(organizer: ICalOrganizer | null | string): this;
941
+ organizer(
942
+ organizer?: ICalOrganizer | null | string,
943
+ ): ICalOrganizer | null | this {
1036
944
  if (organizer === undefined) {
1037
945
  return this.data.organizer;
1038
946
  }
@@ -1045,566 +953,551 @@ export default class ICalEvent {
1045
953
  return this;
1046
954
  }
1047
955
 
1048
-
1049
956
  /**
1050
- * Creates a new {@link ICalAttendee} and returns it. Use options to prefill
1051
- * the attendee's attributes. Calling this method without options will create
1052
- * an empty attendee.
1053
- *
1054
- * ```javascript
1055
- * import ical from 'ical-generator';
1056
- *
1057
- * const cal = ical();
1058
- * const event = cal.createEvent({
1059
- * start: new Date()
1060
- * });
1061
- *
1062
- * event.createAttendee({email: 'hui@example.com', name: 'Hui'});
1063
- *
1064
- * // add another attendee
1065
- * event.createAttendee('Buh <buh@example.net>');
1066
- * ```
1067
- *
1068
- * ```text
1069
- * BEGIN:VCALENDAR
1070
- * VERSION:2.0
1071
- * PRODID:-//sebbo.net//ical-generator//EN
1072
- * BEGIN:VEVENT
1073
- * UID:b4944f07-98e4-4581-ac80-2589bb20273d
1074
- * SEQUENCE:0
1075
- * DTSTAMP:20240212T194232Z
1076
- * DTSTART:20240212T194232Z
1077
- * SUMMARY:
1078
- * ATTENDEE;ROLE=REQ-PARTICIPANT;CN="Hui":MAILTO:hui@example.com
1079
- * ATTENDEE;ROLE=REQ-PARTICIPANT;CN="Buh":MAILTO:buh@example.net
1080
- * END:VEVENT
1081
- * END:VCALENDAR
1082
- * ```
1083
- *
1084
- * As with the organizer, you can also add an explicit `mailto` address.
1085
- *
1086
- * ```javascript
1087
- * event.createAttendee({email: 'hui@example.com', name: 'Hui', mailto: 'another@mailto.com'});
957
+ * Get the event's priority. A value of 1 represents
958
+ * the highest priority, 9 the lowest. 0 specifies an undefined
959
+ * priority.
1088
960
  *
1089
- * // overwrite an attendee's mailto address
1090
- * attendee.mailto('another@mailto.net');
1091
- * ```
961
+ * @since v2.0.0-develop.7
962
+ */
963
+ priority(): null | number;
964
+ /**
965
+ * Set the event's priority. A value of 1 represents
966
+ * the highest priority, 9 the lowest. 0 specifies an undefined
967
+ * priority.
1092
968
  *
1093
- * @since 0.2.0
969
+ * @since v2.0.0-develop.7
1094
970
  */
1095
- createAttendee(data: ICalAttendee | ICalAttendeeData | string): ICalAttendee {
1096
- if (data instanceof ICalAttendee) {
1097
- this.data.attendees.push(data);
1098
- return data;
971
+ priority(priority: null | number): this;
972
+ priority(priority?: null | number): null | number | this {
973
+ if (priority === undefined) {
974
+ return this.data.priority;
1099
975
  }
1100
- if (typeof data === 'string') {
1101
- data = { email: data, ...checkNameAndMail('data', data) };
976
+ if (priority === null) {
977
+ this.data.priority = null;
978
+ return this;
1102
979
  }
1103
980
 
1104
- const attendee = new ICalAttendee(data, this);
1105
- this.data.attendees.push(attendee);
1106
- return attendee;
1107
- }
981
+ if (priority < 0 || priority > 9) {
982
+ throw new Error(
983
+ '`priority` is invalid, musst be 0 ≤ priority ≤ 9.',
984
+ );
985
+ }
1108
986
 
987
+ this.data.priority = Math.round(priority);
988
+ return this;
989
+ }
1109
990
 
1110
991
  /**
1111
- * Get all attendees
992
+ * Get the event's recurrence id
1112
993
  * @since 0.2.0
1113
994
  */
1114
- attendees(): ICalAttendee[];
1115
-
995
+ recurrenceId(): ICalDateTimeValue | null;
1116
996
  /**
1117
- * Add multiple attendees to your event
1118
- *
1119
- * ```javascript
1120
- * const event = ical().createEvent();
1121
- *
1122
- * cal.attendees([
1123
- * {email: 'a@example.com', name: 'Person A'},
1124
- * {email: 'b@example.com', name: 'Person B'}
1125
- * ]);
1126
- *
1127
- * cal.attendees(); // --> [ICalAttendee, ICalAttendee]
1128
- * ```
997
+ * Set the event's recurrence id. You can use any supported date object, see
998
+ * [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
999
+ * for details about supported values and timezone handling.
1129
1000
  *
1130
1001
  * @since 0.2.0
1131
1002
  */
1132
- attendees(attendees: (ICalAttendee | ICalAttendeeData | string)[]): this;
1133
- attendees(attendees?: (ICalAttendee | ICalAttendeeData | string)[]): this | ICalAttendee[] {
1134
- if (!attendees) {
1135
- return this.data.attendees;
1003
+ recurrenceId(recurrenceId: ICalDateTimeValue | null): this;
1004
+ recurrenceId(
1005
+ recurrenceId?: ICalDateTimeValue | null,
1006
+ ): ICalDateTimeValue | null | this {
1007
+ if (recurrenceId === undefined) {
1008
+ return this.data.recurrenceId;
1009
+ }
1010
+ if (recurrenceId === null) {
1011
+ this.data.recurrenceId = null;
1012
+ return this;
1136
1013
  }
1137
1014
 
1138
- attendees.forEach(attendee => this.createAttendee(attendee));
1015
+ this.data.recurrenceId = checkDate(recurrenceId, 'recurrenceId');
1139
1016
  return this;
1140
1017
  }
1141
-
1142
-
1143
1018
  /**
1144
- * Creates a new {@link ICalAlarm} and returns it. Use options to prefill
1145
- * the alarm's attributes. Calling this method without options will create
1146
- * an empty alarm.
1019
+ * Get the event's repeating options
1020
+ * @since 0.2.0
1021
+ */
1022
+ repeating(): ICalEventJSONRepeatingData | ICalRRuleStub | null | string;
1023
+ /**
1024
+ * Set the event's repeating options by passing an {@link ICalRepeatingOptions} object.
1147
1025
  *
1148
1026
  * ```javascript
1149
- * const cal = ical();
1150
- * const event = cal.createEvent();
1151
- * const alarm = event.createAlarm({type: ICalAlarmType.display, trigger: 300});
1152
- *
1153
- * // add another alarm
1154
- * event.createAlarm({
1155
- * type: ICalAlarmType.audio,
1156
- * trigger: 300, // 5min before event
1027
+ * event.repeating({
1028
+ * freq: 'MONTHLY', // required
1029
+ * count: 5,
1030
+ * interval: 2,
1031
+ * until: new Date('Jan 01 2014 00:00:00 UTC'),
1032
+ * byDay: ['su', 'mo'], // repeat only sunday and monday
1033
+ * byMonth: [1, 2], // repeat only in january and february,
1034
+ * byMonthDay: [1, 15], // repeat only on the 1st and 15th
1035
+ * bySetPos: 3, // repeat every 3rd sunday (will take the first element of the byDay array)
1036
+ * exclude: [new Date('Dec 25 2013 00:00:00 UTC')], // exclude these dates
1037
+ * excludeTimezone: 'Europe/Berlin', // timezone of exclude
1038
+ * wkst: 'SU' // Start the week on Sunday, default is Monday
1157
1039
  * });
1158
1040
  * ```
1159
1041
  *
1160
- * @since 0.2.1
1161
- */
1162
- createAlarm(data: ICalAlarm | ICalAlarmData): ICalAlarm {
1163
- const alarm = data instanceof ICalAlarm ? data : new ICalAlarm(data, this);
1164
- this.data.alarms.push(alarm);
1165
- return alarm;
1166
- }
1167
-
1168
-
1169
- /**
1170
- * Get all alarms
1171
- * @since 0.2.0
1172
- */
1173
- alarms(): ICalAlarm[];
1174
-
1175
- /**
1176
- * Add one or multiple alarms
1042
+ * **Example:**
1177
1043
  *
1178
- * ```javascript
1179
- * const event = ical().createEvent();
1044
+ *```typescript
1045
+ * import ical, { ICalEventRepeatingFreq } from 'ical-generator';
1180
1046
  *
1181
- * cal.alarms([
1182
- * {type: ICalAlarmType.display, trigger: 600},
1183
- * {type: ICalAlarmType.audio, trigger: 300}
1184
- * ]);
1047
+ * const cal = ical();
1185
1048
  *
1186
- * cal.alarms(); // --> [ICalAlarm, ICalAlarm]
1187
- ```
1049
+ * const event = cal.createEvent({
1050
+ * start: new Date('2020-01-01T20:00:00Z'),
1051
+ * summary: 'Repeating Event'
1052
+ * });
1053
+ * event.repeating({
1054
+ * freq: ICalEventRepeatingFreq.WEEKLY,
1055
+ * count: 4
1056
+ * });
1057
+ *
1058
+ * cal.toString();
1059
+ * ```
1060
+ *
1061
+ * ```text
1062
+ * BEGIN:VCALENDAR
1063
+ * VERSION:2.0
1064
+ * PRODID:-//sebbo.net//ical-generator//EN
1065
+ * BEGIN:VEVENT
1066
+ * UID:b80e6a68-c2cd-48f5-b94d-cecc7ce83871
1067
+ * SEQUENCE:0
1068
+ * DTSTAMP:20240212T193646Z
1069
+ * DTSTART:20200101T200000Z
1070
+ * RRULE:FREQ=WEEKLY;COUNT=4
1071
+ * SUMMARY:Repeating Event
1072
+ * END:VEVENT
1073
+ * END:VCALENDAR
1074
+ * ```
1188
1075
  *
1189
1076
  * @since 0.2.0
1190
1077
  */
1191
- alarms(alarms: ICalAlarm[] | ICalAlarmData[]): this;
1192
- alarms(alarms?: ICalAlarm[] | ICalAlarmData[]): this | ICalAlarm[] {
1193
- if (!alarms) {
1194
- return this.data.alarms;
1195
- }
1196
-
1197
- alarms.forEach((alarm: ICalAlarm | ICalAlarmData) => this.createAlarm(alarm));
1198
- return this;
1199
- }
1200
-
1201
-
1078
+ repeating(repeating: ICalRepeatingOptions | null): this;
1202
1079
  /**
1203
- * Creates a new {@link ICalCategory} and returns it. Use options to prefill the category's attributes.
1204
- * Calling this method without options will create an empty category.
1080
+ * Set the event's repeating options by passing an [RRule object](https://github.com/jakubroztocil/rrule).
1081
+ * @since 2.0.0-develop.5
1082
+ *
1083
+ * ```typescript
1084
+ * import ical from 'ical-generator';
1085
+ * import { datetime, RRule } from 'rrule';
1205
1086
  *
1206
- * ```javascript
1207
1087
  * const cal = ical();
1208
- * const event = cal.createEvent();
1209
- * const category = event.createCategory({name: 'APPOINTMENT'});
1210
1088
  *
1211
- * // add another category
1212
- * event.createCategory({
1213
- * name: 'MEETING'
1089
+ * const event = cal.createEvent({
1090
+ * start: new Date('2020-01-01T20:00:00Z'),
1091
+ * summary: 'Repeating Event'
1214
1092
  * });
1093
+ *
1094
+ * const rule = new RRule({
1095
+ * freq: RRule.WEEKLY,
1096
+ * interval: 5,
1097
+ * byweekday: [RRule.MO, RRule.FR],
1098
+ * dtstart: datetime(2012, 2, 1, 10, 30),
1099
+ * until: datetime(2012, 12, 31)
1100
+ * })
1101
+ * event.repeating(rule);
1102
+ *
1103
+ * cal.toString();
1215
1104
  * ```
1216
1105
  *
1217
- * @since 0.3.0
1106
+ * ```text
1107
+ * BEGIN:VCALENDAR
1108
+ * VERSION:2.0
1109
+ * PRODID:-//sebbo.net//ical-generator//EN
1110
+ * BEGIN:VEVENT
1111
+ * UID:36585e40-8fa8-460d-af0c-88b6f434030b
1112
+ * SEQUENCE:0
1113
+ * DTSTAMP:20240212T193827Z
1114
+ * DTSTART:20200101T200000Z
1115
+ * RRULE:FREQ=WEEKLY;INTERVAL=5;BYDAY=MO,FR;UNTIL=20121231T000000Z
1116
+ * SUMMARY:Repeating Event
1117
+ * END:VEVENT
1118
+ * END:VCALENDAR
1119
+ * ```
1218
1120
  */
1219
- createCategory(data: ICalCategory | ICalCategoryData): ICalCategory {
1220
- const category = data instanceof ICalCategory ? data : new ICalCategory(data);
1221
- this.data.categories.push(category);
1222
- return category;
1223
- }
1224
-
1225
-
1121
+ repeating(repeating: ICalRRuleStub | null): this;
1226
1122
  /**
1227
- * Get all categories
1228
- * @since 0.3.0
1123
+ * Set the events repeating options by passing a string which is inserted in the ical file.
1124
+ * @since 2.0.0-develop.5
1229
1125
  */
1230
- categories(): ICalCategory[];
1231
-
1126
+ repeating(repeating: null | string): this;
1232
1127
  /**
1233
- * Add categories to the event or return all selected categories.
1234
- *
1235
- * ```javascript
1236
- * const event = ical().createEvent();
1237
- *
1238
- * cal.categories([
1239
- * {name: 'APPOINTMENT'},
1240
- * {name: 'MEETING'}
1241
- * ]);
1242
- *
1243
- * cal.categories(); // --> [ICalCategory, ICalCategory]
1244
- * ```
1245
- *
1246
- * @since 0.3.0
1128
+ * @internal
1247
1129
  */
1248
- categories(categories: (ICalCategory | ICalCategoryData)[]): this;
1249
- categories(categories?: (ICalCategory | ICalCategoryData)[]): this | ICalCategory[] {
1250
- if (!categories) {
1251
- return this.data.categories;
1130
+ repeating(
1131
+ repeating: ICalRepeatingOptions | ICalRRuleStub | null | string,
1132
+ ): this;
1133
+ repeating(
1134
+ repeating?: ICalRepeatingOptions | ICalRRuleStub | null | string,
1135
+ ): ICalEventJSONRepeatingData | ICalRRuleStub | null | string | this {
1136
+ if (repeating === undefined) {
1137
+ return this.data.repeating;
1138
+ }
1139
+ if (!repeating) {
1140
+ this.data.repeating = null;
1141
+ return this;
1142
+ }
1143
+ if (isRRule(repeating) || typeof repeating === 'string') {
1144
+ this.data.repeating = repeating;
1145
+ return this;
1252
1146
  }
1253
1147
 
1254
- categories.forEach(category => this.createCategory(category));
1255
- return this;
1256
- }
1148
+ this.data.repeating = {
1149
+ freq: checkEnum(
1150
+ ICalEventRepeatingFreq,
1151
+ repeating.freq,
1152
+ ) as ICalEventRepeatingFreq,
1153
+ };
1154
+
1155
+ if (repeating.count) {
1156
+ if (!isFinite(repeating.count)) {
1157
+ throw new Error('`repeating.count` must be a finite number!');
1158
+ }
1257
1159
 
1160
+ this.data.repeating.count = repeating.count;
1161
+ }
1258
1162
 
1259
- /**
1260
- * Get the event's status
1261
- * @since 0.2.0
1262
- */
1263
- status(): ICalEventStatus | null;
1163
+ if (repeating.interval) {
1164
+ if (!isFinite(repeating.interval)) {
1165
+ throw new Error(
1166
+ '`repeating.interval` must be a finite number!',
1167
+ );
1168
+ }
1264
1169
 
1265
- /**
1266
- * Set the event's status
1267
- *
1268
- * ```javascript
1269
- * import ical, {ICalEventStatus} from 'ical-generator';
1270
- * event.status(ICalEventStatus.CONFIRMED);
1271
- * ```
1272
- *
1273
- * @since 0.2.0
1274
- */
1275
- status(status: ICalEventStatus | null): this;
1276
- status(status?: ICalEventStatus | null): this | ICalEventStatus | null {
1277
- if (status === undefined) {
1278
- return this.data.status;
1170
+ this.data.repeating.interval = repeating.interval;
1171
+ }
1172
+
1173
+ if (repeating.until !== undefined) {
1174
+ this.data.repeating.until = checkDate(
1175
+ repeating.until,
1176
+ 'repeating.until',
1177
+ );
1279
1178
  }
1280
- if (status === null) {
1281
- this.data.status = null;
1282
- return this;
1179
+
1180
+ if (repeating.byDay) {
1181
+ const byDayArray = Array.isArray(repeating.byDay)
1182
+ ? repeating.byDay
1183
+ : [repeating.byDay];
1184
+ this.data.repeating.byDay = byDayArray.map(
1185
+ (day) => checkEnum(ICalWeekday, day) as ICalWeekday,
1186
+ );
1283
1187
  }
1284
1188
 
1285
- this.data.status = checkEnum(ICalEventStatus, status) as ICalEventStatus;
1286
- return this;
1287
- }
1189
+ if (repeating.byMonth) {
1190
+ const byMonthArray = Array.isArray(repeating.byMonth)
1191
+ ? repeating.byMonth
1192
+ : [repeating.byMonth];
1193
+ this.data.repeating.byMonth = byMonthArray.map((month) => {
1194
+ if (typeof month !== 'number' || month < 1 || month > 12) {
1195
+ throw new Error(
1196
+ '`repeating.byMonth` contains invalid value `' +
1197
+ month +
1198
+ '`!',
1199
+ );
1200
+ }
1288
1201
 
1202
+ return month;
1203
+ });
1204
+ }
1289
1205
 
1290
- /**
1291
- * Get the event's busy status
1292
- * @since 1.0.2
1293
- */
1294
- busystatus(): ICalEventBusyStatus | null;
1206
+ if (repeating.byMonthDay) {
1207
+ const byMonthDayArray = Array.isArray(repeating.byMonthDay)
1208
+ ? repeating.byMonthDay
1209
+ : [repeating.byMonthDay];
1210
+
1211
+ this.data.repeating.byMonthDay = byMonthDayArray.map((monthDay) => {
1212
+ if (
1213
+ typeof monthDay !== 'number' ||
1214
+ monthDay < -31 ||
1215
+ monthDay > 31 ||
1216
+ monthDay === 0
1217
+ ) {
1218
+ throw new Error(
1219
+ '`repeating.byMonthDay` contains invalid value `' +
1220
+ monthDay +
1221
+ '`!',
1222
+ );
1223
+ }
1295
1224
 
1296
- /**
1297
- * Set the event's busy status. Will add the
1298
- * [`X-MICROSOFT-CDO-BUSYSTATUS`](https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcical/cd68eae7-ed65-4dd3-8ea7-ad585c76c736)
1299
- * attribute to your event.
1300
- *
1301
- * ```javascript
1302
- * import ical, {ICalEventBusyStatus} from 'ical-generator';
1303
- * event.busystatus(ICalEventBusyStatus.BUSY);
1304
- * ```
1305
- *
1306
- * @since 1.0.2
1307
- */
1308
- busystatus(busystatus: ICalEventBusyStatus | null): this;
1309
- busystatus(busystatus?: ICalEventBusyStatus | null): this | ICalEventBusyStatus | null {
1310
- if (busystatus === undefined) {
1311
- return this.data.busystatus;
1225
+ return monthDay;
1226
+ });
1312
1227
  }
1313
- if (busystatus === null) {
1314
- this.data.busystatus = null;
1315
- return this;
1228
+
1229
+ if (repeating.bySetPos) {
1230
+ if (!this.data.repeating.byDay) {
1231
+ throw '`repeating.bySetPos` must be used along with `repeating.byDay`!';
1232
+ }
1233
+ const bySetPosArray = Array.isArray(repeating.bySetPos)
1234
+ ? repeating.bySetPos
1235
+ : [repeating.bySetPos];
1236
+ this.data.repeating.bySetPos = bySetPosArray.map((bySetPos) => {
1237
+ if (
1238
+ typeof bySetPos !== 'number' ||
1239
+ bySetPos < -366 ||
1240
+ bySetPos > 366 ||
1241
+ bySetPos === 0
1242
+ ) {
1243
+ throw (
1244
+ '`repeating.bySetPos` contains invalid value `' +
1245
+ bySetPos +
1246
+ '`!'
1247
+ );
1248
+ }
1249
+ return bySetPos;
1250
+ });
1251
+ }
1252
+
1253
+ if (repeating.exclude) {
1254
+ const excludeArray = Array.isArray(repeating.exclude)
1255
+ ? repeating.exclude
1256
+ : [repeating.exclude];
1257
+ this.data.repeating.exclude = excludeArray.map((exclude, i) => {
1258
+ return checkDate(exclude, `repeating.exclude[${i}]`);
1259
+ });
1260
+ }
1261
+
1262
+ if (repeating.startOfWeek) {
1263
+ this.data.repeating.startOfWeek = checkEnum(
1264
+ ICalWeekday,
1265
+ repeating.startOfWeek,
1266
+ ) as ICalWeekday;
1316
1267
  }
1317
1268
 
1318
- this.data.busystatus = checkEnum(ICalEventBusyStatus, busystatus) as ICalEventBusyStatus;
1319
1269
  return this;
1320
1270
  }
1321
1271
 
1322
-
1323
1272
  /**
1324
- * Get the event's priority. A value of 1 represents
1325
- * the highest priority, 9 the lowest. 0 specifies an undefined
1326
- * priority.
1273
+ * Get the event's SEQUENCE number. Use this method to get the event's
1274
+ * revision sequence number of the calendar component within a sequence of revisions.
1327
1275
  *
1328
- * @since v2.0.0-develop.7
1276
+ * @since 0.2.6
1329
1277
  */
1330
- priority(): number | null;
1331
-
1278
+ sequence(): number;
1332
1279
  /**
1333
- * Set the event's priority. A value of 1 represents
1334
- * the highest priority, 9 the lowest. 0 specifies an undefined
1335
- * priority.
1280
+ * Set the event's SEQUENCE number. For a new event, this should be zero.
1281
+ * Each time the organizer makes a significant revision, the sequence
1282
+ * number should be incremented.
1336
1283
  *
1337
- * @since v2.0.0-develop.7
1284
+ * @param sequence Sequence number or null to unset it
1338
1285
  */
1339
- priority(priority: number | null): this;
1340
- priority(priority?: number | null): this | number | null {
1341
- if (priority === undefined) {
1342
- return this.data.priority;
1343
- }
1344
- if (priority === null) {
1345
- this.data.priority = null;
1346
- return this;
1286
+ sequence(sequence: number): this;
1287
+ sequence(sequence?: number): number | this {
1288
+ if (sequence === undefined) {
1289
+ return this.data.sequence;
1347
1290
  }
1348
1291
 
1349
- if(priority < 0 || priority > 9) {
1350
- throw new Error('`priority` is invalid, musst be 0 ≤ priority ≤ 9.');
1292
+ const s = parseInt(String(sequence), 10);
1293
+ if (isNaN(s)) {
1294
+ throw new Error('`sequence` must be a number!');
1351
1295
  }
1352
1296
 
1353
- this.data.priority = Math.round(priority);
1297
+ this.data.sequence = sequence;
1354
1298
  return this;
1355
1299
  }
1356
1300
 
1357
-
1358
1301
  /**
1359
- * Get the event's URL
1302
+ * Get the event's timestamp
1360
1303
  * @since 0.2.0
1304
+ * @see {@link timestamp}
1361
1305
  */
1362
- url(): string | null;
1363
-
1306
+ stamp(): ICalDateTimeValue;
1364
1307
  /**
1365
- * Set the event's URL
1308
+ * Set the appointment date of creation. Defaults to the current time and date (`new Date()`). You can use
1309
+ * any supported date object, see [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
1310
+ * for details about supported values and timezone handling.
1311
+ *
1366
1312
  * @since 0.2.0
1313
+ * @see {@link timestamp}
1367
1314
  */
1368
- url(url: string | null): this;
1369
- url(url?: string | null): this | string | null {
1370
- if (url === undefined) {
1371
- return this.data.url;
1315
+ stamp(stamp: ICalDateTimeValue): this;
1316
+ stamp(stamp?: ICalDateTimeValue): ICalDateTimeValue | this {
1317
+ if (stamp === undefined) {
1318
+ return this.data.stamp;
1372
1319
  }
1373
1320
 
1374
- this.data.url = url ? String(url) : null;
1321
+ this.data.stamp = checkDate(stamp, 'stamp');
1375
1322
  return this;
1376
1323
  }
1377
1324
 
1378
1325
  /**
1379
- * Adds an attachment to the event by adding the file URL to the calendar.
1326
+ * Get the event start time which is currently
1327
+ * set. Can be any supported date object.
1380
1328
  *
1381
- * `ical-generator` only supports external attachments. File attachments that
1382
- * are directly included in the file are not supported, because otherwise the
1383
- * calendar file could easily become unfavourably large.
1329
+ * @since 0.2.0
1330
+ */
1331
+ start(): ICalDateTimeValue;
1332
+ /**
1333
+ * Set the appointment date of beginning, which is required for all events.
1334
+ * You can use any supported date object, see
1335
+ * [Readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
1336
+ * for details about supported values and timezone handling.
1337
+ *
1338
+ * ```typescript
1339
+ * import ical from 'ical-generator';
1384
1340
  *
1385
- * ```javascript
1386
1341
  * const cal = ical();
1387
- * const event = cal.createEvent();
1388
- * event.createAttachment('https://files.sebbo.net/calendar/attachments/foo');
1389
- * ```
1390
1342
  *
1391
- * @since 3.2.0-develop.1
1392
- */
1393
- createAttachment(url: string): this {
1394
- this.data.attachments.push(url);
1395
- return this;
1396
- }
1397
-
1398
-
1399
- /**
1400
- * Get all attachment urls
1401
- * @since 3.2.0-develop.1
1402
- */
1403
- attachments(): string[];
1404
-
1405
- /**
1406
- * Add one or multiple alarms
1343
+ * const event = cal.createEvent({
1344
+ * start: new Date('2020-01-01')
1345
+ * });
1407
1346
  *
1408
- * ```javascript
1409
- * const event = ical().createEvent();
1347
+ * // overwrites old start date
1348
+ * event.start(new Date('2024-02-01'));
1410
1349
  *
1411
- * cal.attachments([
1412
- * 'https://files.sebbo.net/calendar/attachments/foo',
1413
- * 'https://files.sebbo.net/calendar/attachments/bar'
1414
- * ]);
1350
+ * cal.toString();
1351
+ * ```
1415
1352
  *
1416
- * cal.attachments(); // --> [string, string]
1417
- ```
1353
+ * ```text
1354
+ * BEGIN:VCALENDAR
1355
+ * VERSION:2.0
1356
+ * PRODID:-//sebbo.net//ical-generator//EN
1357
+ * BEGIN:VEVENT
1358
+ * UID:7e2aee64-b07a-4256-9b3e-e9eaa452bac8
1359
+ * SEQUENCE:0
1360
+ * DTSTAMP:20240212T190915Z
1361
+ * DTSTART:20240201T000000Z
1362
+ * SUMMARY:
1363
+ * END:VEVENT
1364
+ * END:VCALENDAR
1365
+ * ```
1418
1366
  *
1419
- * 3.2.0-develop.1
1367
+ * @since 0.2.0
1420
1368
  */
1421
- attachments(attachments: string[]): this;
1422
- attachments(attachments?: string[]): this | string[] {
1423
- if (!attachments) {
1424
- return this.data.attachments;
1369
+ start(start: ICalDateTimeValue): this;
1370
+ start(start?: ICalDateTimeValue): ICalDateTimeValue | this {
1371
+ if (start === undefined) {
1372
+ this.swapStartAndEndIfRequired();
1373
+ return this.data.start;
1425
1374
  }
1426
1375
 
1427
- attachments.forEach((attachment: string) => this.createAttachment(attachment));
1376
+ this.data.start = checkDate(start, 'start');
1428
1377
  return this;
1429
1378
  }
1430
1379
 
1431
1380
  /**
1432
- * Get the event's transparency
1433
- * @since 1.7.3
1381
+ * Get the event's status
1382
+ * @since 0.2.0
1434
1383
  */
1435
- transparency(): ICalEventTransparency | null;
1436
-
1384
+ status(): ICalEventStatus | null;
1437
1385
  /**
1438
- * Set the event's transparency
1439
- *
1440
- * Set the field to `OPAQUE` if the person or resource is no longer
1441
- * available due to this event. If the calendar entry has no influence
1442
- * on availability, you can set the field to `TRANSPARENT`. This value
1443
- * is mostly used to find out if a person has time on a certain date or
1444
- * not (see `TRANSP` in iCal specification).
1386
+ * Set the event's status
1445
1387
  *
1446
1388
  * ```javascript
1447
- * import ical, {ICalEventTransparency} from 'ical-generator';
1448
- * event.transparency(ICalEventTransparency.OPAQUE);
1389
+ * import ical, {ICalEventStatus} from 'ical-generator';
1390
+ * event.status(ICalEventStatus.CONFIRMED);
1449
1391
  * ```
1450
1392
  *
1451
- * @since 1.7.3
1452
- */
1453
- transparency(transparency: ICalEventTransparency | null): this;
1454
- transparency(transparency?: ICalEventTransparency | null): this | ICalEventTransparency | null {
1455
- if (transparency === undefined) {
1456
- return this.data.transparency;
1457
- }
1458
- if (!transparency) {
1459
- this.data.transparency = null;
1460
- return this;
1461
- }
1462
-
1463
- this.data.transparency = checkEnum(ICalEventTransparency, transparency) as ICalEventTransparency;
1464
- return this;
1465
- }
1466
-
1467
-
1468
- /**
1469
- * Get the event's creation date
1470
- * @since 0.3.0
1471
- */
1472
- created(): ICalDateTimeValue | null;
1473
-
1474
- /**
1475
- * Set the event's creation date
1476
- * @since 0.3.0
1393
+ * @since 0.2.0
1477
1394
  */
1478
- created(created: ICalDateTimeValue | null): this;
1479
- created(created?: ICalDateTimeValue | null): this | ICalDateTimeValue | null {
1480
- if (created === undefined) {
1481
- return this.data.created;
1395
+ status(status: ICalEventStatus | null): this;
1396
+ status(status?: ICalEventStatus | null): ICalEventStatus | null | this {
1397
+ if (status === undefined) {
1398
+ return this.data.status;
1482
1399
  }
1483
- if (created === null) {
1484
- this.data.created = null;
1400
+ if (status === null) {
1401
+ this.data.status = null;
1485
1402
  return this;
1486
1403
  }
1487
1404
 
1488
- this.data.created = checkDate(created, 'created');
1405
+ this.data.status = checkEnum(
1406
+ ICalEventStatus,
1407
+ status,
1408
+ ) as ICalEventStatus;
1489
1409
  return this;
1490
1410
  }
1491
1411
 
1492
-
1493
1412
  /**
1494
- * Get the event's last modification date
1495
- * @since 0.3.0
1413
+ * Get the event's summary
1414
+ * @since 0.2.0
1496
1415
  */
1497
- lastModified(): ICalDateTimeValue | null;
1498
-
1416
+ summary(): string;
1499
1417
  /**
1500
- * Set the event's last modification date
1501
- * @since 0.3.0
1418
+ * Set the event's summary.
1419
+ * Defaults to an empty string if nothing is set.
1420
+ *
1421
+ * @since 0.2.0
1502
1422
  */
1503
- lastModified(lastModified: ICalDateTimeValue | null): this;
1504
- lastModified(lastModified?: ICalDateTimeValue | null): this | ICalDateTimeValue | null {
1505
- if (lastModified === undefined) {
1506
- return this.data.lastModified;
1507
- }
1508
- if (lastModified === null) {
1509
- this.data.lastModified = null;
1510
- return this;
1423
+ summary(summary: string): this;
1424
+ summary(summary?: string): string | this {
1425
+ if (summary === undefined) {
1426
+ return this.data.summary;
1511
1427
  }
1512
1428
 
1513
- this.data.lastModified = checkDate(lastModified, 'lastModified');
1429
+ this.data.summary = summary ? String(summary) : '';
1514
1430
  return this;
1515
1431
  }
1516
1432
 
1517
1433
  /**
1518
- * Get the event's class
1519
- * @since 2.0.0
1434
+ * Get the event's timestamp
1435
+ * @since 0.2.0
1436
+ * @see {@link stamp}
1520
1437
  */
1521
- class(): ICalEventClass | null;
1522
-
1438
+ timestamp(): ICalDateTimeValue;
1523
1439
  /**
1524
- * Set the event's class
1525
- *
1526
- * ```javascript
1527
- * import ical, { ICalEventClass } from 'ical-generator';
1528
- * event.class(ICalEventClass.PRIVATE);
1529
- * ```
1440
+ * Set the appointment date of creation. Defaults to the current time and date (`new Date()`). You can use
1441
+ * any supported date object, see [readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones)
1442
+ * for details about supported values and timezone handling.
1530
1443
  *
1531
- * @since 2.0.0
1444
+ * @since 0.2.0
1445
+ * @see {@link stamp}
1532
1446
  */
1533
- class(class_: ICalEventClass | null): this;
1534
- class(class_?: ICalEventClass | null): this | ICalEventClass | null {
1535
- if (class_ === undefined) {
1536
- return this.data.class;
1537
- }
1538
- if (class_ === null) {
1539
- this.data.class = null;
1540
- return this;
1447
+ timestamp(stamp: ICalDateTimeValue): this;
1448
+ timestamp(stamp?: ICalDateTimeValue): ICalDateTimeValue | this {
1449
+ if (stamp === undefined) {
1450
+ return this.stamp();
1541
1451
  }
1542
1452
 
1543
- this.data.class = checkEnum(ICalEventClass, class_) as ICalEventClass;
1544
- return this;
1453
+ return this.stamp(stamp);
1545
1454
  }
1546
-
1547
-
1548
1455
  /**
1549
- * Set X-* attributes. Woun't filter double attributes,
1550
- * which are also added by another method (e.g. summary),
1551
- * so these attributes may be inserted twice.
1552
- *
1553
- * ```javascript
1554
- * event.x([
1555
- * {
1556
- * key: "X-MY-CUSTOM-ATTR",
1557
- * value: "1337!"
1558
- * }
1559
- * ]);
1456
+ * Get the event's timezone.
1457
+ * @since 0.2.6
1458
+ */
1459
+ timezone(): null | string;
1460
+ /**
1461
+ * Sets the time zone to be used for this event. If a time zone has been
1462
+ * defined in both the event and the calendar, the time zone of the event
1463
+ * is used.
1560
1464
  *
1561
- * event.x([
1562
- * ["X-MY-CUSTOM-ATTR", "1337!"]
1563
- * ]);
1465
+ * Please note that if the time zone is set, ical-generator assumes
1466
+ * that all times are already in the correct time zone. Alternatively,
1467
+ * a `moment-timezone` or a Luxon object can be passed with `setZone`,
1468
+ * ical-generator will then set the time zone itself.
1564
1469
  *
1565
- * event.x({
1566
- * "X-MY-CUSTOM-ATTR": "1337!"
1567
- * });
1568
- * ```
1470
+ * This and the 'floating' flag (see below) are mutually exclusive, and setting a timezone will unset the
1471
+ * 'floating' flag. If neither 'timezone' nor 'floating' are set, the date will be output with in UTC format
1472
+ * (see [date-time form #2 in section 3.3.5 of RFC 554](https://tools.ietf.org/html/rfc5545#section-3.3.5)).
1569
1473
  *
1570
- * @since 1.9.0
1571
- */
1572
- x (keyOrArray: {key: string, value: string}[] | [string, string][] | Record<string, string>): this;
1573
-
1574
- /**
1575
- * Set a X-* attribute. Woun't filter double attributes,
1576
- * which are also added by another method (e.g. summary),
1577
- * so these attributes may be inserted twice.
1474
+ * See [Readme](https://github.com/sebbo2002/ical-generator#-date-time--timezones) for details about
1475
+ * supported values and timezone handling.
1578
1476
  *
1579
1477
  * ```javascript
1580
- * event.x("X-MY-CUSTOM-ATTR", "1337!");
1478
+ * event.timezone('America/New_York');
1581
1479
  * ```
1582
1480
  *
1583
- * @since 1.9.0
1584
- */
1585
- x (keyOrArray: string, value: string): this;
1586
-
1587
- /**
1588
- * Get all custom X-* attributes.
1589
- * @since 1.9.0
1481
+ * @see https://github.com/sebbo2002/ical-generator#-date-time--timezones
1482
+ * @since 0.2.6
1590
1483
  */
1591
- x (): {key: string, value: string}[];
1592
- x(keyOrArray?: ({ key: string, value: string })[] | [string, string][] | Record<string, string> | string, value?: string): this | void | ({ key: string, value: string })[] {
1593
- if (keyOrArray === undefined) {
1594
- return addOrGetCustomAttributes(this.data);
1484
+ timezone(timezone: null | string): this;
1485
+ timezone(timezone?: null | string): null | string | this {
1486
+ if (timezone === undefined && this.data.timezone !== null) {
1487
+ return this.data.timezone;
1595
1488
  }
1596
-
1597
- if (typeof keyOrArray === 'string' && typeof value === 'string') {
1598
- addOrGetCustomAttributes(this.data, keyOrArray, value);
1489
+ if (timezone === undefined) {
1490
+ return this.calendar.timezone();
1599
1491
  }
1600
- if (typeof keyOrArray === 'object') {
1601
- addOrGetCustomAttributes(this.data, keyOrArray);
1492
+
1493
+ this.data.timezone =
1494
+ timezone && timezone !== 'UTC' ? timezone.toString() : null;
1495
+ if (this.data.timezone) {
1496
+ this.data.floating = false;
1602
1497
  }
1603
1498
 
1604
1499
  return this;
1605
1500
  }
1606
-
1607
-
1608
1501
  /**
1609
1502
  * Return a shallow copy of the events's options for JSON stringification.
1610
1503
  * Third party objects like moment.js values or RRule objects are stringified
@@ -1621,31 +1514,32 @@ export default class ICalEvent {
1621
1514
  * @since 0.2.4
1622
1515
  */
1623
1516
  toJSON(): ICalEventJSONData {
1624
- let repeating: ICalEventJSONRepeatingData | string | null = null;
1625
- if(isRRule(this.data.repeating) || typeof this.data.repeating === 'string') {
1517
+ let repeating: ICalEventJSONRepeatingData | null | string = null;
1518
+ if (
1519
+ isRRule(this.data.repeating) ||
1520
+ typeof this.data.repeating === 'string'
1521
+ ) {
1626
1522
  repeating = this.data.repeating.toString();
1627
- }
1628
- else if(this.data.repeating) {
1523
+ } else if (this.data.repeating) {
1629
1524
  repeating = Object.assign({}, this.data.repeating, {
1525
+ exclude: this.data.repeating.exclude?.map((d) => toJSON(d)),
1630
1526
  until: toJSON(this.data.repeating.until) || undefined,
1631
- exclude: this.data.repeating.exclude?.map(d => toJSON(d)),
1632
1527
  });
1633
1528
  }
1634
1529
 
1635
1530
  this.swapStartAndEndIfRequired();
1636
1531
  return Object.assign({}, this.data, {
1637
- start: toJSON(this.data.start) || null,
1638
- end: toJSON(this.data.end) || null,
1639
- recurrenceId: toJSON(this.data.recurrenceId) || null,
1640
- stamp: toJSON(this.data.stamp) || null,
1641
1532
  created: toJSON(this.data.created) || null,
1533
+ end: toJSON(this.data.end) || null,
1642
1534
  lastModified: toJSON(this.data.lastModified) || null,
1535
+ recurrenceId: toJSON(this.data.recurrenceId) || null,
1643
1536
  repeating,
1644
- x: this.x()
1537
+ stamp: toJSON(this.data.stamp) || null,
1538
+ start: toJSON(this.data.start) || null,
1539
+ x: this.x(),
1645
1540
  });
1646
1541
  }
1647
1542
 
1648
-
1649
1543
  /**
1650
1544
  * Return generated event as a string.
1651
1545
  *
@@ -1665,39 +1559,64 @@ export default class ICalEvent {
1665
1559
  g += 'SEQUENCE:' + this.data.sequence + '\r\n';
1666
1560
 
1667
1561
  this.swapStartAndEndIfRequired();
1668
- g += 'DTSTAMP:' + formatDate(this.calendar.timezone(), this.data.stamp) + '\r\n';
1562
+ g +=
1563
+ 'DTSTAMP:' +
1564
+ formatDate(this.calendar.timezone(), this.data.stamp) +
1565
+ '\r\n';
1669
1566
  if (this.data.allDay) {
1670
- g += 'DTSTART;VALUE=DATE:' + formatDate(this.timezone(), this.data.start, true) + '\r\n';
1567
+ g +=
1568
+ 'DTSTART;VALUE=DATE:' +
1569
+ formatDate(this.timezone(), this.data.start, true) +
1570
+ '\r\n';
1671
1571
  if (this.data.end) {
1672
- g += 'DTEND;VALUE=DATE:' + formatDate(this.timezone(), this.data.end, true) + '\r\n';
1572
+ g +=
1573
+ 'DTEND;VALUE=DATE:' +
1574
+ formatDate(this.timezone(), this.data.end, true) +
1575
+ '\r\n';
1673
1576
  }
1674
1577
 
1675
1578
  g += 'X-MICROSOFT-CDO-ALLDAYEVENT:TRUE\r\n';
1676
1579
  g += 'X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT:TRUE\r\n';
1677
- }
1678
- else {
1679
- g += formatDateTZ(this.timezone(), 'DTSTART', this.data.start, this.data) + '\r\n';
1580
+ } else {
1581
+ g +=
1582
+ formatDateTZ(
1583
+ this.timezone(),
1584
+ 'DTSTART',
1585
+ this.data.start,
1586
+ this.data,
1587
+ ) + '\r\n';
1680
1588
  if (this.data.end) {
1681
- g += formatDateTZ(this.timezone(), 'DTEND', this.data.end, this.data) + '\r\n';
1589
+ g +=
1590
+ formatDateTZ(
1591
+ this.timezone(),
1592
+ 'DTEND',
1593
+ this.data.end,
1594
+ this.data,
1595
+ ) + '\r\n';
1682
1596
  }
1683
1597
  }
1684
1598
 
1685
1599
  // REPEATING
1686
- if(isRRule(this.data.repeating) || typeof this.data.repeating === 'string') {
1600
+ if (
1601
+ isRRule(this.data.repeating) ||
1602
+ typeof this.data.repeating === 'string'
1603
+ ) {
1687
1604
  let repeating = this.data.repeating
1688
1605
  .toString()
1689
1606
  .replace(/\r\n/g, '\n')
1690
1607
  .split('\n')
1691
- .filter(l => l && !l.startsWith('DTSTART:'))
1608
+ .filter((l) => l && !l.startsWith('DTSTART:'))
1692
1609
  .join('\r\n');
1693
1610
 
1694
- if(!repeating.includes('\r\n') && !repeating.startsWith('RRULE:')) {
1611
+ if (
1612
+ !repeating.includes('\r\n') &&
1613
+ !repeating.startsWith('RRULE:')
1614
+ ) {
1695
1615
  repeating = 'RRULE:' + repeating;
1696
1616
  }
1697
1617
 
1698
1618
  g += repeating.trim() + '\r\n';
1699
- }
1700
- else if (this.data.repeating) {
1619
+ } else if (this.data.repeating) {
1701
1620
  g += 'RRULE:FREQ=' + this.data.repeating.freq;
1702
1621
 
1703
1622
  if (this.data.repeating.count) {
@@ -1709,7 +1628,14 @@ export default class ICalEvent {
1709
1628
  }
1710
1629
 
1711
1630
  if (this.data.repeating.until) {
1712
- g += ';UNTIL=' + formatDate(this.calendar.timezone(), this.data.repeating.until, false, this.floating());
1631
+ g +=
1632
+ ';UNTIL=' +
1633
+ formatDate(
1634
+ this.calendar.timezone(),
1635
+ this.data.repeating.until,
1636
+ false,
1637
+ this.floating(),
1638
+ );
1713
1639
  }
1714
1640
 
1715
1641
  if (this.data.repeating.byDay) {
@@ -1737,23 +1663,52 @@ export default class ICalEvent {
1737
1663
  // REPEATING EXCLUSION
1738
1664
  if (this.data.repeating.exclude) {
1739
1665
  if (this.data.allDay) {
1740
- g += 'EXDATE;VALUE=DATE:' + this.data.repeating.exclude.map(excludedDate => {
1741
- return formatDate(this.calendar.timezone(), excludedDate, true);
1742
- }).join(',') + '\r\n';
1743
- }
1744
- else {
1666
+ g +=
1667
+ 'EXDATE;VALUE=DATE:' +
1668
+ this.data.repeating.exclude
1669
+ .map((excludedDate) => {
1670
+ return formatDate(
1671
+ this.calendar.timezone(),
1672
+ excludedDate,
1673
+ true,
1674
+ );
1675
+ })
1676
+ .join(',') +
1677
+ '\r\n';
1678
+ } else {
1745
1679
  g += 'EXDATE';
1746
1680
  if (this.timezone()) {
1747
- g += ';TZID=' + this.timezone() + ':' + this.data.repeating.exclude.map(excludedDate => {
1748
- // This isn't a 'floating' event because it has a timezone;
1749
- // but we use it to omit the 'Z' UTC specifier in formatDate()
1750
- return formatDate(this.timezone(), excludedDate, false, true);
1751
- }).join(',') + '\r\n';
1752
- }
1753
- else {
1754
- g += ':' + this.data.repeating.exclude.map(excludedDate => {
1755
- return formatDate(this.timezone(), excludedDate, false, this.floating());
1756
- }).join(',') + '\r\n';
1681
+ g +=
1682
+ ';TZID=' +
1683
+ this.timezone() +
1684
+ ':' +
1685
+ this.data.repeating.exclude
1686
+ .map((excludedDate) => {
1687
+ // This isn't a 'floating' event because it has a timezone;
1688
+ // but we use it to omit the 'Z' UTC specifier in formatDate()
1689
+ return formatDate(
1690
+ this.timezone(),
1691
+ excludedDate,
1692
+ false,
1693
+ true,
1694
+ );
1695
+ })
1696
+ .join(',') +
1697
+ '\r\n';
1698
+ } else {
1699
+ g +=
1700
+ ':' +
1701
+ this.data.repeating.exclude
1702
+ .map((excludedDate) => {
1703
+ return formatDate(
1704
+ this.timezone(),
1705
+ excludedDate,
1706
+ false,
1707
+ this.floating(),
1708
+ );
1709
+ })
1710
+ .join(',') +
1711
+ '\r\n';
1757
1712
  }
1758
1713
  }
1759
1714
  }
@@ -1761,7 +1716,13 @@ export default class ICalEvent {
1761
1716
 
1762
1717
  // RECURRENCE
1763
1718
  if (this.data.recurrenceId) {
1764
- g += formatDateTZ(this.timezone(), 'RECURRENCE-ID', this.data.recurrenceId, this.data) + '\r\n';
1719
+ g +=
1720
+ formatDateTZ(
1721
+ this.timezone(),
1722
+ 'RECURRENCE-ID',
1723
+ this.data.recurrenceId,
1724
+ this.data,
1725
+ ) + '\r\n';
1765
1726
  }
1766
1727
 
1767
1728
  // SUMMARY
@@ -1773,53 +1734,92 @@ export default class ICalEvent {
1773
1734
  }
1774
1735
 
1775
1736
  // LOCATION
1776
- if (this.data.location && 'title' in this.data.location && this.data.location.title) {
1777
- g += 'LOCATION:' + escape(
1778
- this.data.location.title +
1779
- (this.data.location.address ? '\n' + this.data.location.address : ''),
1780
- false
1781
- ) + '\r\n';
1737
+ if (
1738
+ this.data.location &&
1739
+ 'title' in this.data.location &&
1740
+ this.data.location.title
1741
+ ) {
1742
+ g +=
1743
+ 'LOCATION:' +
1744
+ escape(
1745
+ this.data.location.title +
1746
+ (this.data.location.address
1747
+ ? '\n' + this.data.location.address
1748
+ : ''),
1749
+ false,
1750
+ ) +
1751
+ '\r\n';
1782
1752
 
1783
1753
  if (this.data.location.radius && this.data.location.geo) {
1784
- g += 'X-APPLE-STRUCTURED-LOCATION;VALUE=URI;' +
1785
- (this.data.location.address ? 'X-ADDRESS=' + escape(this.data.location.address, false) + ';' : '') +
1786
- 'X-APPLE-RADIUS=' + escape(this.data.location.radius, false) + ';' +
1787
- 'X-TITLE=' + escape(this.data.location.title, false) +
1788
- ':geo:' + escape(this.data.location.geo?.lat, false) + ',' +
1789
- escape(this.data.location.geo?.lon, false) + '\r\n';
1754
+ g +=
1755
+ 'X-APPLE-STRUCTURED-LOCATION;VALUE=URI;' +
1756
+ (this.data.location.address
1757
+ ? 'X-ADDRESS=' +
1758
+ escape(this.data.location.address, false) +
1759
+ ';'
1760
+ : '') +
1761
+ 'X-APPLE-RADIUS=' +
1762
+ escape(this.data.location.radius, false) +
1763
+ ';' +
1764
+ 'X-TITLE=' +
1765
+ escape(this.data.location.title, false) +
1766
+ ':geo:' +
1767
+ escape(this.data.location.geo?.lat, false) +
1768
+ ',' +
1769
+ escape(this.data.location.geo?.lon, false) +
1770
+ '\r\n';
1790
1771
  }
1791
1772
  }
1792
1773
 
1793
1774
  // GEO
1794
1775
  if (this.data.location?.geo?.lat && this.data.location.geo.lon) {
1795
- g += 'GEO:' + escape(this.data.location.geo.lat, false) + ';' +
1796
- escape(this.data.location.geo.lon, false) + '\r\n';
1776
+ g +=
1777
+ 'GEO:' +
1778
+ escape(this.data.location.geo.lat, false) +
1779
+ ';' +
1780
+ escape(this.data.location.geo.lon, false) +
1781
+ '\r\n';
1797
1782
  }
1798
1783
 
1799
1784
  // DESCRIPTION
1800
1785
  if (this.data.description) {
1801
- g += 'DESCRIPTION:' + escape(this.data.description.plain, false) + '\r\n';
1786
+ g +=
1787
+ 'DESCRIPTION:' +
1788
+ escape(this.data.description.plain, false) +
1789
+ '\r\n';
1802
1790
 
1803
1791
  // HTML DESCRIPTION
1804
1792
  if (this.data.description.html) {
1805
- g += 'X-ALT-DESC;FMTTYPE=text/html:' + escape(this.data.description.html, false) + '\r\n';
1793
+ g +=
1794
+ 'X-ALT-DESC;FMTTYPE=text/html:' +
1795
+ escape(this.data.description.html, false) +
1796
+ '\r\n';
1806
1797
  }
1807
1798
  }
1808
1799
 
1809
1800
  // ORGANIZER
1810
1801
  if (this.data.organizer) {
1811
- g += 'ORGANIZER;CN="' + escape(this.data.organizer.name, true) + '"';
1802
+ g +=
1803
+ 'ORGANIZER;CN="' + escape(this.data.organizer.name, true) + '"';
1812
1804
 
1813
1805
  if (this.data.organizer.sentBy) {
1814
- g += ';SENT-BY="mailto:' + escape(this.data.organizer.sentBy, true) + '"';
1806
+ g +=
1807
+ ';SENT-BY="mailto:' +
1808
+ escape(this.data.organizer.sentBy, true) +
1809
+ '"';
1815
1810
  }
1816
1811
  if (this.data.organizer.email && this.data.organizer.mailto) {
1817
1812
  g += ';EMAIL=' + escape(this.data.organizer.email, false);
1818
1813
  }
1819
1814
 
1820
1815
  g += ':';
1821
- if(this.data.organizer.email) {
1822
- g += 'mailto:' + escape(this.data.organizer.mailto || this.data.organizer.email, false);
1816
+ if (this.data.organizer.email) {
1817
+ g +=
1818
+ 'mailto:' +
1819
+ escape(
1820
+ this.data.organizer.mailto || this.data.organizer.email,
1821
+ false,
1822
+ );
1823
1823
  }
1824
1824
 
1825
1825
  g += '\r\n';
@@ -1837,9 +1837,12 @@ export default class ICalEvent {
1837
1837
 
1838
1838
  // CATEGORIES
1839
1839
  if (this.data.categories.length > 0) {
1840
- g += 'CATEGORIES:' + this.data.categories
1841
- .map(category => category.toString())
1842
- .join() + '\r\n';
1840
+ g +=
1841
+ 'CATEGORIES:' +
1842
+ this.data.categories
1843
+ .map((category) => category.toString())
1844
+ .join() +
1845
+ '\r\n';
1843
1846
  }
1844
1847
 
1845
1848
  // URL
@@ -1849,7 +1852,7 @@ export default class ICalEvent {
1849
1852
 
1850
1853
  // ATTACHMENT
1851
1854
  if (this.data.attachments.length > 0) {
1852
- this.data.attachments.forEach(url => {
1855
+ this.data.attachments.forEach((url) => {
1853
1856
  g += 'ATTACH:' + escape(url, false) + '\r\n';
1854
1857
  });
1855
1858
  }
@@ -1861,7 +1864,10 @@ export default class ICalEvent {
1861
1864
 
1862
1865
  // BUSYSTATUS
1863
1866
  if (this.data.busystatus) {
1864
- g += 'X-MICROSOFT-CDO-BUSYSTATUS:' + this.data.busystatus.toUpperCase() + '\r\n';
1867
+ g +=
1868
+ 'X-MICROSOFT-CDO-BUSYSTATUS:' +
1869
+ this.data.busystatus.toUpperCase() +
1870
+ '\r\n';
1865
1871
  }
1866
1872
 
1867
1873
  // PRIORITY
@@ -1874,19 +1880,186 @@ export default class ICalEvent {
1874
1880
 
1875
1881
  // CREATED
1876
1882
  if (this.data.created) {
1877
- g += 'CREATED:' + formatDate(this.calendar.timezone(), this.data.created) + '\r\n';
1883
+ g +=
1884
+ 'CREATED:' +
1885
+ formatDate(this.calendar.timezone(), this.data.created) +
1886
+ '\r\n';
1878
1887
  }
1879
1888
 
1880
1889
  // LAST-MODIFIED
1881
1890
  if (this.data.lastModified) {
1882
- g += 'LAST-MODIFIED:' + formatDate(this.calendar.timezone(), this.data.lastModified) + '\r\n';
1891
+ g +=
1892
+ 'LAST-MODIFIED:' +
1893
+ formatDate(this.calendar.timezone(), this.data.lastModified) +
1894
+ '\r\n';
1883
1895
  }
1884
1896
 
1885
1897
  if (this.data.class) {
1886
- g+= 'CLASS:' + this.data.class.toUpperCase() + '\r\n';
1898
+ g += 'CLASS:' + this.data.class.toUpperCase() + '\r\n';
1887
1899
  }
1888
1900
 
1889
1901
  g += 'END:VEVENT\r\n';
1890
1902
  return g;
1891
1903
  }
1904
+
1905
+ /**
1906
+ * Get the event's transparency
1907
+ * @since 1.7.3
1908
+ */
1909
+ transparency(): ICalEventTransparency | null;
1910
+ /**
1911
+ * Set the event's transparency
1912
+ *
1913
+ * Set the field to `OPAQUE` if the person or resource is no longer
1914
+ * available due to this event. If the calendar entry has no influence
1915
+ * on availability, you can set the field to `TRANSPARENT`. This value
1916
+ * is mostly used to find out if a person has time on a certain date or
1917
+ * not (see `TRANSP` in iCal specification).
1918
+ *
1919
+ * ```javascript
1920
+ * import ical, {ICalEventTransparency} from 'ical-generator';
1921
+ * event.transparency(ICalEventTransparency.OPAQUE);
1922
+ * ```
1923
+ *
1924
+ * @since 1.7.3
1925
+ */
1926
+ transparency(transparency: ICalEventTransparency | null): this;
1927
+ transparency(
1928
+ transparency?: ICalEventTransparency | null,
1929
+ ): ICalEventTransparency | null | this {
1930
+ if (transparency === undefined) {
1931
+ return this.data.transparency;
1932
+ }
1933
+ if (!transparency) {
1934
+ this.data.transparency = null;
1935
+ return this;
1936
+ }
1937
+
1938
+ this.data.transparency = checkEnum(
1939
+ ICalEventTransparency,
1940
+ transparency,
1941
+ ) as ICalEventTransparency;
1942
+ return this;
1943
+ }
1944
+
1945
+ /**
1946
+ * Get the event's ID
1947
+ * @since 0.2.0
1948
+ * @see {@link id}
1949
+ */
1950
+ uid(): string;
1951
+ /**
1952
+ * Use this method to set the event's ID.
1953
+ * If not set, a UUID will be generated randomly.
1954
+ *
1955
+ * @param id Event ID you want to set
1956
+ */
1957
+ uid(id: number | string): this;
1958
+ uid(id?: number | string): string | this {
1959
+ return id === undefined ? this.id() : this.id(id);
1960
+ }
1961
+
1962
+ /**
1963
+ * Get the event's URL
1964
+ * @since 0.2.0
1965
+ */
1966
+ url(): null | string;
1967
+ /**
1968
+ * Set the event's URL
1969
+ * @since 0.2.0
1970
+ */
1971
+ url(url: null | string): this;
1972
+ url(url?: null | string): null | string | this {
1973
+ if (url === undefined) {
1974
+ return this.data.url;
1975
+ }
1976
+
1977
+ this.data.url = url ? String(url) : null;
1978
+ return this;
1979
+ }
1980
+
1981
+ /**
1982
+ * Set X-* attributes. Woun't filter double attributes,
1983
+ * which are also added by another method (e.g. summary),
1984
+ * so these attributes may be inserted twice.
1985
+ *
1986
+ * ```javascript
1987
+ * event.x([
1988
+ * {
1989
+ * key: "X-MY-CUSTOM-ATTR",
1990
+ * value: "1337!"
1991
+ * }
1992
+ * ]);
1993
+ *
1994
+ * event.x([
1995
+ * ["X-MY-CUSTOM-ATTR", "1337!"]
1996
+ * ]);
1997
+ *
1998
+ * event.x({
1999
+ * "X-MY-CUSTOM-ATTR": "1337!"
2000
+ * });
2001
+ * ```
2002
+ *
2003
+ * @since 1.9.0
2004
+ */
2005
+ x(
2006
+ keyOrArray:
2007
+ | [string, string][]
2008
+ | Record<string, string>
2009
+ | { key: string; value: string }[],
2010
+ ): this;
2011
+ /**
2012
+ * Set a X-* attribute. Woun't filter double attributes,
2013
+ * which are also added by another method (e.g. summary),
2014
+ * so these attributes may be inserted twice.
2015
+ *
2016
+ * ```javascript
2017
+ * event.x("X-MY-CUSTOM-ATTR", "1337!");
2018
+ * ```
2019
+ *
2020
+ * @since 1.9.0
2021
+ */
2022
+ x(keyOrArray: string, value: string): this;
2023
+ /**
2024
+ * Get all custom X-* attributes.
2025
+ * @since 1.9.0
2026
+ */
2027
+ x(): { key: string; value: string }[];
2028
+ x(
2029
+ keyOrArray?:
2030
+ | [string, string][]
2031
+ | Record<string, string>
2032
+ | string
2033
+ | { key: string; value: string }[],
2034
+ value?: string,
2035
+ ): this | void | { key: string; value: string }[] {
2036
+ if (keyOrArray === undefined) {
2037
+ return addOrGetCustomAttributes(this.data);
2038
+ }
2039
+
2040
+ if (typeof keyOrArray === 'string' && typeof value === 'string') {
2041
+ addOrGetCustomAttributes(this.data, keyOrArray, value);
2042
+ }
2043
+ if (typeof keyOrArray === 'object') {
2044
+ addOrGetCustomAttributes(this.data, keyOrArray);
2045
+ }
2046
+
2047
+ return this;
2048
+ }
2049
+
2050
+ /**
2051
+ * Checks if the start date is after the end date and swaps them if necessary.
2052
+ * @private
2053
+ */
2054
+ private swapStartAndEndIfRequired(): void {
2055
+ if (
2056
+ this.data.start &&
2057
+ this.data.end &&
2058
+ toDate(this.data.start).getTime() > toDate(this.data.end).getTime()
2059
+ ) {
2060
+ const t = this.data.start;
2061
+ this.data.start = this.data.end;
2062
+ this.data.end = t;
2063
+ }
2064
+ }
1892
2065
  }