ical-generator 8.1.2-develop.8 → 9.0.0-develop.1

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