ical-generator 10.0.1-develop.9 → 10.1.1-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/package.json CHANGED
@@ -6,25 +6,26 @@
6
6
  "description": "ical-generator is a small piece of code which generates ical calendar files",
7
7
  "devDependencies": {
8
8
  "@date-fns/tz": "^1.4.1",
9
- "@eslint/js": "^9.39.1",
9
+ "@eslint/js": "^10.0.1",
10
10
  "@istanbuljs/nyc-config-typescript": "^1.0.2",
11
11
  "@qiwi/semantic-release-gh-pages-plugin": "^5.4.3",
12
- "@sebbo2002/semantic-release-jsr": "^3.1.0",
12
+ "@sebbo2002/semantic-release-jsr": "^3.2.1",
13
13
  "@semantic-release/changelog": "^6.0.3",
14
14
  "@semantic-release/exec": "^7.1.0",
15
15
  "@semantic-release/git": "^10.0.1",
16
- "@semantic-release/npm": "^13.1.2",
16
+ "@semantic-release/npm": "^13.1.5",
17
17
  "@touch4it/ical-timezones": "^1.9.0",
18
18
  "@types/express": "^5.0.6",
19
19
  "@types/luxon": "^3.7.1",
20
20
  "@types/mocha": "^10.0.10",
21
- "c8": "^10.1.3",
22
- "dayjs": "^1.11.19",
23
- "eslint": "^9.39.1",
21
+ "c8": "^11.0.0",
22
+ "dayjs": "^1.11.20",
23
+ "eslint": "^10.0.3",
24
24
  "eslint-config-prettier": "^10.1.8",
25
- "eslint-plugin-jsonc": "^2.21.0",
26
- "eslint-plugin-perfectionist": "^4.15.1",
25
+ "eslint-plugin-jsonc": "^3.1.2",
26
+ "eslint-plugin-perfectionist": "^5.6.0",
27
27
  "esm": "^3.2.25",
28
+ "globals": "^17.4.0",
28
29
  "husky": "^9.1.7",
29
30
  "license-checker": "^25.0.1",
30
31
  "luxon": "^3.7.2",
@@ -32,17 +33,18 @@
32
33
  "mochawesome": "^7.1.4",
33
34
  "moment": "^2.30.1",
34
35
  "moment-timezone": "^0.6.0",
35
- "nyc": "^17.1.0",
36
- "prettier": "^3.7.4",
36
+ "nyc": "^18.0.0",
37
+ "prettier": "^3.8.1",
37
38
  "rrule": "^2.8.1",
38
- "semantic-release": "^25.0.2",
39
+ "semantic-release": "^25.0.3",
39
40
  "semantic-release-license": "^1.0.2",
40
41
  "source-map-support": "^0.5.21",
42
+ "temporal-polyfill": "^0.3.0",
41
43
  "tsup": "^8.5.1",
42
44
  "tsx": "^4.21.0",
43
- "typedoc": "^0.28.15",
45
+ "typedoc": "^0.28.17",
44
46
  "typescript": "^5.9.3",
45
- "typescript-eslint": "^8.48.0"
47
+ "typescript-eslint": "^8.57.0"
46
48
  },
47
49
  "engines": {
48
50
  "node": "20 || 22 || >=24"
@@ -131,5 +133,5 @@
131
133
  "test": "mocha"
132
134
  },
133
135
  "type": "module",
134
- "version": "10.0.1-develop.9"
136
+ "version": "10.1.1-develop.1"
135
137
  }
package/src/alarm.ts CHANGED
@@ -203,7 +203,7 @@ export default class ICalAlarm {
203
203
  return this;
204
204
  }
205
205
 
206
- let _attach = null;
206
+ let _attach: ICalAttachment | undefined;
207
207
  if (typeof attachment === 'string') {
208
208
  _attach = {
209
209
  mime: null,
package/src/index.ts CHANGED
@@ -113,6 +113,10 @@ export {
113
113
  type ICalOrganizer,
114
114
  type ICalRepeatingOptions,
115
115
  type ICalRRuleStub,
116
+ type ICalTemporalInstantStub,
117
+ type ICalTemporalPlainDateStub,
118
+ type ICalTemporalPlainDateTimeStub,
119
+ type ICalTemporalZonedDateTimeStub,
116
120
  type ICalTimezone,
117
121
  type ICalTZDateStub,
118
122
  ICalWeekday,
package/src/tools.ts CHANGED
@@ -9,6 +9,10 @@ import {
9
9
  type ICalMomentTimezoneStub,
10
10
  type ICalOrganizer,
11
11
  type ICalRRuleStub,
12
+ type ICalTemporalInstantStub,
13
+ type ICalTemporalPlainDateStub,
14
+ type ICalTemporalPlainDateTimeStub,
15
+ type ICalTemporalZonedDateTimeStub,
12
16
  type ICalTZDateStub,
13
17
  } from './types.ts';
14
18
 
@@ -19,17 +23,14 @@ export function addOrGetCustomAttributes(
19
23
  | Record<string, string>
20
24
  | { key: string; value: string }[],
21
25
  ): void;
22
-
23
26
  export function addOrGetCustomAttributes(
24
27
  data: { x: [string, string][] },
25
28
  keyOrArray: string,
26
29
  value: string,
27
30
  ): void;
28
-
29
31
  export function addOrGetCustomAttributes(data: {
30
32
  x: [string, string][];
31
33
  }): { key: string; value: string }[];
32
-
33
34
  export function addOrGetCustomAttributes(
34
35
  data: { x: [string, string][] },
35
36
  keyOrArray?:
@@ -100,6 +101,19 @@ export function checkDate(
100
101
  return value;
101
102
  }
102
103
 
104
+ // Temporal types - these are always valid if they exist
105
+ // Check this first to avoid false positives with other date libraries
106
+ if (
107
+ typeof value === 'object' &&
108
+ value !== null &&
109
+ (isTemporalZonedDateTime(value) ||
110
+ isTemporalPlainDateTime(value) ||
111
+ isTemporalPlainDate(value) ||
112
+ isTemporalInstant(value))
113
+ ) {
114
+ return value;
115
+ }
116
+
103
117
  // Luxon
104
118
  if (isLuxonDate(value) && value.isValid === true) {
105
119
  return value;
@@ -254,14 +268,14 @@ export function formatDate(
254
268
 
255
269
  // (!dateonly && !floating) || !timezone => utc
256
270
  let s =
257
- m.getUTCFullYear() +
271
+ m.getUTCFullYear().toString().padStart(4, '0') +
258
272
  String(m.getUTCMonth() + 1).padStart(2, '0') +
259
273
  m.getUTCDate().toString().padStart(2, '0');
260
274
 
261
275
  // (dateonly || floating) && timezone => tz
262
276
  if (timezone) {
263
277
  s =
264
- m.getFullYear() +
278
+ m.getFullYear().toString().padStart(2, '0') +
265
279
  String(m.getMonth() + 1).padStart(2, '0') +
266
280
  m.getDate().toString().padStart(2, '0');
267
281
  }
@@ -317,6 +331,66 @@ export function formatDate(
317
331
  ? 'T' + m.toFormat('HHmmss') + (floating || timezone ? '' : 'Z')
318
332
  : '')
319
333
  );
334
+ } else if (isTemporalZonedDateTime(d)) {
335
+ let t = d;
336
+
337
+ // try to convert to the specified timezone
338
+ if (timezone) {
339
+ t = d.withTimeZone(d.timeZoneId);
340
+ }
341
+ if (!timezone && d.timeZoneId !== 'UTC') {
342
+ t = d.withTimeZone('UTC');
343
+ }
344
+
345
+ return formatDate(
346
+ null,
347
+ t.toPlainDateTime(),
348
+ dateonly,
349
+
350
+ // keep floating if specified or if a timezone is set
351
+ // to remove the 'Z' UTC specifier from the output
352
+ floating || !!timezone,
353
+ );
354
+ } else if (isTemporalPlainDateTime(d)) {
355
+ // Temporal.PlainDateTime - floating time or convert to timezone
356
+ if (dateonly) {
357
+ return (
358
+ d.year.toString().padStart(4, '0') +
359
+ d.month.toString().padStart(2, '0') +
360
+ d.day.toString().padStart(2, '0')
361
+ );
362
+ }
363
+
364
+ if (timezone) {
365
+ // Convert to ZonedDateTime in the specified timezone
366
+ const zoned = d.toZonedDateTime(timezone);
367
+ return formatDate(timezone, zoned, dateonly, floating);
368
+ }
369
+
370
+ // Floating time - no timezone, no Z
371
+ return (
372
+ d.year.toString().padStart(4, '0') +
373
+ d.month.toString().padStart(2, '0') +
374
+ d.day.toString().padStart(2, '0') +
375
+ 'T' +
376
+ d.hour.toString().padStart(2, '0') +
377
+ d.minute.toString().padStart(2, '0') +
378
+ d.second.toString().padStart(2, '0') +
379
+ (floating || timezone ? '' : 'Z')
380
+ );
381
+ } else if (isTemporalPlainDate(d)) {
382
+ // Temporal.PlainDate - date only
383
+ return (
384
+ d.year.toString().padStart(4, '0') +
385
+ d.month.toString().padStart(2, '0') +
386
+ d.day.toString().padStart(2, '0') +
387
+ (!dateonly ? 'T000000' + (floating || timezone ? '' : 'Z') : '')
388
+ );
389
+ } else if (isTemporalInstant(d)) {
390
+ // Temporal.Instant - convert to ZonedDateTime first
391
+ const targetTz = timezone || 'UTC';
392
+ const zoned = d.toZonedDateTimeISO(targetTz);
393
+ return formatDate(timezone, zoned, dateonly, floating);
320
394
  } else {
321
395
  // @see https://day.js.org/docs/en/plugin/utc
322
396
 
@@ -384,7 +458,8 @@ export function isDayjs(value: ICalDateTimeValue): value is ICalDayJsStub {
384
458
  value !== null &&
385
459
  !(value instanceof Date) &&
386
460
  !isMoment(value) &&
387
- !isLuxonDate(value)
461
+ !isLuxonDate(value) &&
462
+ !isTemporal(value)
388
463
  );
389
464
  }
390
465
 
@@ -395,12 +470,17 @@ export function isLuxonDate(
395
470
  typeof value === 'object' &&
396
471
  value !== null &&
397
472
  'toJSDate' in value &&
398
- typeof value.toJSDate === 'function'
473
+ typeof value.toJSDate === 'function' &&
474
+ !isTemporal(value)
399
475
  );
400
476
  }
401
477
  export function isMoment(value: ICalDateTimeValue): value is ICalMomentStub {
402
- // @ts-expect-error _isAMomentObject is a private property
403
- return value != null && value._isAMomentObject != null;
478
+ return (
479
+ value != null &&
480
+ // @ts-expect-error _isAMomentObject is a private property
481
+ value._isAMomentObject != null &&
482
+ !isTemporal(value)
483
+ );
404
484
  }
405
485
  export function isMomentDuration(
406
486
  value: unknown,
@@ -428,6 +508,111 @@ export function isRRule(value: unknown): value is ICalRRuleStub {
428
508
  );
429
509
  }
430
510
 
511
+ export function isTemporal(
512
+ value: ICalDateTimeValue,
513
+ ): value is
514
+ | ICalTemporalInstantStub
515
+ | ICalTemporalPlainDateStub
516
+ | ICalTemporalPlainDateTimeStub
517
+ | ICalTemporalZonedDateTimeStub {
518
+ return (
519
+ isTemporalZonedDateTime(value) ||
520
+ isTemporalPlainDateTime(value) ||
521
+ isTemporalPlainDate(value) ||
522
+ isTemporalInstant(value)
523
+ );
524
+ }
525
+
526
+ export function isTemporalInstant(
527
+ value: ICalDateTimeValue,
528
+ ): value is ICalTemporalInstantStub {
529
+ return (
530
+ typeof value === 'object' &&
531
+ value !== null &&
532
+ !isTemporalZonedDateTime(value) &&
533
+ !isTemporalPlainDateTime(value) &&
534
+ !isTemporalPlainDate(value) &&
535
+ 'toZonedDateTimeISO' in value &&
536
+ typeof value.toZonedDateTimeISO === 'function' &&
537
+ !('year' in value) &&
538
+ !('timeZoneId' in value)
539
+ );
540
+ }
541
+
542
+ export function isTemporalPlainDate(
543
+ value: ICalDateTimeValue,
544
+ ): value is ICalTemporalPlainDateStub {
545
+ return (
546
+ typeof value === 'object' &&
547
+ value !== null &&
548
+ !isTemporalZonedDateTime(value) &&
549
+ !isTemporalPlainDateTime(value) &&
550
+ 'toPlainDateTime' in value &&
551
+ typeof value.toPlainDateTime === 'function' &&
552
+ 'year' in value &&
553
+ typeof value.year === 'number' &&
554
+ 'month' in value &&
555
+ typeof value.month === 'number' &&
556
+ 'day' in value &&
557
+ typeof value.day === 'number' &&
558
+ !('hour' in value) &&
559
+ !('timeZoneId' in value) &&
560
+ !('epochSeconds' in value)
561
+ );
562
+ }
563
+
564
+ export function isTemporalPlainDateTime(
565
+ value: ICalDateTimeValue,
566
+ ): value is ICalTemporalPlainDateTimeStub {
567
+ return (
568
+ typeof value === 'object' &&
569
+ value !== null &&
570
+ !isTemporalZonedDateTime(value) &&
571
+ 'toZonedDateTime' in value &&
572
+ typeof value.toZonedDateTime === 'function' &&
573
+ 'toPlainDate' in value &&
574
+ typeof value.toPlainDate === 'function' &&
575
+ 'year' in value &&
576
+ typeof value.year === 'number' &&
577
+ 'month' in value &&
578
+ typeof value.month === 'number' &&
579
+ 'day' in value &&
580
+ typeof value.day === 'number' &&
581
+ 'hour' in value &&
582
+ typeof value.hour === 'number' &&
583
+ 'minute' in value &&
584
+ typeof value.minute === 'number' &&
585
+ 'second' in value &&
586
+ typeof value.second === 'number' &&
587
+ !('timeZone' in value)
588
+ );
589
+ }
590
+
591
+ export function isTemporalZonedDateTime(
592
+ value: ICalDateTimeValue,
593
+ ): value is ICalTemporalZonedDateTimeStub {
594
+ return (
595
+ typeof value === 'object' &&
596
+ value !== null &&
597
+ 'timeZoneId' in value &&
598
+ typeof value.timeZoneId === 'string' &&
599
+ 'toPlainDateTime' in value &&
600
+ typeof value.toPlainDateTime === 'function' &&
601
+ 'year' in value &&
602
+ typeof value.year === 'number' &&
603
+ 'month' in value &&
604
+ typeof value.month === 'number' &&
605
+ 'day' in value &&
606
+ typeof value.day === 'number' &&
607
+ 'hour' in value &&
608
+ typeof value.hour === 'number' &&
609
+ 'minute' in value &&
610
+ typeof value.minute === 'number' &&
611
+ 'second' in value &&
612
+ typeof value.second === 'number'
613
+ );
614
+ }
615
+
431
616
  export function isTZDate(value: ICalDateTimeValue): value is ICalTZDateStub {
432
617
  return (
433
618
  value instanceof Date &&
@@ -445,6 +630,36 @@ export function toDate(value: ICalDateTimeValue): Date {
445
630
  return new Date(value);
446
631
  }
447
632
 
633
+ if (isTemporalZonedDateTime(value)) {
634
+ // Convert ZonedDateTime to Instant, then to Date
635
+ const instant = value.toInstant();
636
+ return new Date(instant.epochMilliseconds);
637
+ }
638
+
639
+ if (isTemporalPlainDateTime(value)) {
640
+ // PlainDateTime has no timezone, treat as UTC
641
+ return new Date(
642
+ Date.UTC(
643
+ value.year,
644
+ value.month - 1,
645
+ value.day,
646
+ value.hour,
647
+ value.minute,
648
+ value.second,
649
+ ),
650
+ );
651
+ }
652
+
653
+ if (isTemporalPlainDate(value)) {
654
+ // PlainDate has no time, treat as midnight UTC
655
+ return new Date(Date.UTC(value.year, value.month - 1, value.day));
656
+ }
657
+
658
+ if (isTemporalInstant(value)) {
659
+ // Instant has epochMilliseconds
660
+ return new Date(value.epochMilliseconds);
661
+ }
662
+
448
663
  if (isLuxonDate(value)) {
449
664
  return value.toJSDate();
450
665
  }
@@ -506,5 +721,16 @@ export function toJSON(
506
721
  return value;
507
722
  }
508
723
 
724
+ // Temporal.ZonedDateTime needs special handling to convert to UTC first
725
+ // as [Timezone] info is not supported as a string input format
726
+ if (isTemporalZonedDateTime(value)) {
727
+ return toJSON(value.withTimeZone('UTC').toPlainDateTime());
728
+ }
729
+
730
+ // Temporal types have toJSON() method that returns ISO strings
731
+ if (isTemporal(value)) {
732
+ return value.toJSON();
733
+ }
734
+
509
735
  return value.toJSON();
510
736
  }
package/src/types.ts CHANGED
@@ -20,9 +20,9 @@ export enum ICalWeekday {
20
20
 
21
21
  /**
22
22
  * ical-generator supports [native Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date),
23
- * [moment.js](https://momentjs.com/) (and [moment-timezone](https://momentjs.com/timezone/), [Day.js](https://day.js.org/en/) and
23
+ * [moment.js](https://momentjs.com/) (and [moment-timezone](https://momentjs.com/timezone/), [Day.js](https://day.js.org/en/),
24
24
  * [Luxon](https://moment.github.io/luxon/)'s [DateTime](https://moment.github.io/luxon/docs/class/src/datetime.js~DateTime.html)
25
- * objects. You can also pass a string which is then passed to javascript's Date internally.
25
+ * and [Temporal](https://tc39.es/proposal-temporal/docs/) objects. You can also pass a string which is then passed to javascript's Date internally.
26
26
  */
27
27
  export type ICalDateTimeValue =
28
28
  | Date
@@ -30,6 +30,10 @@ export type ICalDateTimeValue =
30
30
  | ICalLuxonDateTimeStub
31
31
  | ICalMomentStub
32
32
  | ICalMomentTimezoneStub
33
+ | ICalTemporalInstantStub
34
+ | ICalTemporalPlainDateStub
35
+ | ICalTemporalPlainDateTimeStub
36
+ | ICalTemporalZonedDateTimeStub
33
37
  | string;
34
38
 
35
39
  export interface ICalDayJsStub {
@@ -37,8 +41,8 @@ export interface ICalDayJsStub {
37
41
  isValid(): boolean;
38
42
  toDate(): Date;
39
43
  toJSON(): string;
40
- tz(zone?: string): ICalDayJsStub;
41
- utc(): ICalDayJsStub;
44
+ tz?(zone?: string): ICalDayJsStub;
45
+ utc?(): ICalDayJsStub;
42
46
  }
43
47
 
44
48
  export interface ICalDescription {
@@ -123,6 +127,50 @@ export interface ICalRRuleStub {
123
127
  toString(): string;
124
128
  }
125
129
 
130
+ export interface ICalTemporalInstantStub {
131
+ epochMilliseconds: number;
132
+ epochSeconds?: number;
133
+ toJSON(): string;
134
+ toString(): string;
135
+ toZonedDateTimeISO(timeZone: string): ICalTemporalZonedDateTimeStub;
136
+ }
137
+
138
+ export interface ICalTemporalPlainDateStub {
139
+ day: number;
140
+ month: number;
141
+ toJSON(): string;
142
+ toString(): string;
143
+ year: number;
144
+ }
145
+
146
+ export interface ICalTemporalPlainDateTimeStub {
147
+ day: number;
148
+ hour: number;
149
+ minute: number;
150
+ month: number;
151
+ second: number;
152
+ toJSON(): string;
153
+ toPlainDate(): ICalTemporalPlainDateStub;
154
+ toString(): string;
155
+ toZonedDateTime(timeZone: string): ICalTemporalZonedDateTimeStub;
156
+ year: number;
157
+ }
158
+
159
+ export interface ICalTemporalZonedDateTimeStub {
160
+ day: number;
161
+ hour: number;
162
+ minute: number;
163
+ month: number;
164
+ second: number;
165
+ timeZoneId: string;
166
+ toInstant(): ICalTemporalInstantStub;
167
+ toJSON(): string;
168
+ toPlainDateTime(): ICalTemporalPlainDateTimeStub;
169
+ toString(): string;
170
+ withTimeZone(timeZone: string): ICalTemporalZonedDateTimeStub;
171
+ year: number;
172
+ }
173
+
126
174
  export interface ICalTimezone {
127
175
  generator?: (timezone: string) => null | string;
128
176
  name: null | string;