iamcal 1.1.1 → 2.1.0
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/README.md +47 -11
- package/lib/component.d.ts +62 -0
- package/lib/component.d.ts.map +1 -0
- package/lib/component.js +208 -0
- package/lib/components/Calendar.d.ts +66 -0
- package/lib/components/Calendar.d.ts.map +1 -0
- package/lib/components/Calendar.js +121 -0
- package/lib/components/CalendarEvent.d.ts +109 -0
- package/lib/components/CalendarEvent.d.ts.map +1 -0
- package/lib/components/CalendarEvent.js +223 -0
- package/lib/components/TimeZone.d.ts +74 -0
- package/lib/components/TimeZone.d.ts.map +1 -0
- package/lib/components/TimeZone.js +127 -0
- package/lib/components/TimeZoneOffset.d.ts +103 -0
- package/lib/components/TimeZoneOffset.d.ts.map +1 -0
- package/lib/components/TimeZoneOffset.js +148 -0
- package/lib/components/index.d.ts +5 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/index.js +21 -0
- package/lib/date.d.ts +165 -0
- package/lib/date.d.ts.map +1 -0
- package/lib/date.js +373 -0
- package/lib/index.d.ts +6 -153
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +21 -469
- package/lib/io.d.ts +10 -8
- package/lib/io.d.ts.map +1 -1
- package/lib/io.js +18 -13
- package/lib/parse.d.ts +33 -16
- package/lib/parse.d.ts.map +1 -1
- package/lib/parse.js +59 -56
- package/lib/patterns.d.ts +36 -0
- package/lib/patterns.d.ts.map +1 -0
- package/lib/patterns.js +50 -0
- package/lib/property.d.ts +149 -0
- package/lib/property.d.ts.map +1 -0
- package/lib/property.js +450 -0
- package/package.json +48 -43
- package/src/component.ts +248 -0
- package/src/components/Calendar.ts +162 -0
- package/src/components/CalendarEvent.ts +270 -0
- package/src/components/TimeZone.ts +152 -0
- package/src/components/TimeZoneOffset.ts +187 -0
- package/src/components/index.ts +4 -0
- package/src/date.ts +395 -0
- package/src/index.ts +6 -576
- package/src/io.ts +17 -13
- package/src/parse.ts +72 -52
- package/src/patterns.ts +69 -0
- package/src/property.ts +492 -0
- package/lib/types.d.ts +0 -6
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -3
- package/src/types.ts +0 -5
package/src/date.ts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import * as patterns from './patterns'
|
|
2
|
+
import { getPropertyValueType, type Property } from './property'
|
|
3
|
+
|
|
4
|
+
export const ONE_SECOND_MS = 1000
|
|
5
|
+
export const ONE_MINUTE_MS = 60 * ONE_SECOND_MS
|
|
6
|
+
export const ONE_HOUR_MS = 60 * ONE_MINUTE_MS
|
|
7
|
+
export const ONE_DAY_MS = 24 * ONE_HOUR_MS
|
|
8
|
+
|
|
9
|
+
export interface CalendarDateOrTime {
|
|
10
|
+
/**
|
|
11
|
+
* Create a property from this date.
|
|
12
|
+
* @param name The name of the property.
|
|
13
|
+
*/
|
|
14
|
+
toProperty(name: string): Property
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the string value of this date.
|
|
18
|
+
* @returns An iCalendar date or date-time string.
|
|
19
|
+
*/
|
|
20
|
+
getValue(): string
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the date value of this date. For {@link CalendarDate} this is the
|
|
24
|
+
* time at the start of the day.
|
|
25
|
+
* @returns The date value of this date.
|
|
26
|
+
*/
|
|
27
|
+
getDate(): Date
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if this date represents a full day, as opposed to a date-time.
|
|
31
|
+
* @returns `true` if this object is a {@link CalendarDate}.
|
|
32
|
+
*/
|
|
33
|
+
isFullDay(): boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents a DATE value as defined by RFC5545.
|
|
38
|
+
* This is a date without a time, representing a whole day.
|
|
39
|
+
*/
|
|
40
|
+
export class CalendarDate implements CalendarDateOrTime {
|
|
41
|
+
private date: Date
|
|
42
|
+
|
|
43
|
+
constructor(date: Date | string | CalendarDateOrTime) {
|
|
44
|
+
if (typeof date === 'object') {
|
|
45
|
+
if (Object.prototype.toString.call(date) === '[object Date]') {
|
|
46
|
+
this.date = date as Date
|
|
47
|
+
} else {
|
|
48
|
+
this.date = (date as CalendarDateOrTime).getDate()
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
try {
|
|
52
|
+
this.date = parseDateString(date)
|
|
53
|
+
} catch {
|
|
54
|
+
this.date = new Date(date)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!this.date || isNaN(this.date.getTime())) {
|
|
59
|
+
throw new Error('Invalid date provided')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.date.setHours(0, 0, 0, 0)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
toProperty(name: string): Property {
|
|
66
|
+
return {
|
|
67
|
+
name,
|
|
68
|
+
params: ['VALUE=DATE'],
|
|
69
|
+
value: this.getValue(),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getValue(): string {
|
|
74
|
+
return toDateString(this.date)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getDate(): Date {
|
|
78
|
+
return new Date(this.date)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
isFullDay(): boolean {
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class CalendarDateTime implements CalendarDateOrTime {
|
|
87
|
+
private date: Date
|
|
88
|
+
|
|
89
|
+
constructor(date: Date | string | CalendarDateOrTime) {
|
|
90
|
+
if (typeof date === 'object') {
|
|
91
|
+
if (Object.prototype.toString.call(date) === '[object Date]') {
|
|
92
|
+
this.date = date as Date
|
|
93
|
+
} else {
|
|
94
|
+
this.date = (date as CalendarDateOrTime).getDate()
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
try {
|
|
98
|
+
this.date = parseDateTimeString(date)
|
|
99
|
+
} catch {
|
|
100
|
+
this.date = new Date(date)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!this.date || isNaN(this.date.getTime())) {
|
|
105
|
+
throw new Error('Invalid date provided')
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
toProperty(name: string): Property {
|
|
110
|
+
return {
|
|
111
|
+
name,
|
|
112
|
+
params: [],
|
|
113
|
+
value: this.getValue(),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getValue(): string {
|
|
118
|
+
return toDateTimeString(this.date)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getDate(): Date {
|
|
122
|
+
return new Date(this.date)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
isFullDay(): boolean {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Pad a number with leading zeros.
|
|
132
|
+
* @param num The number to pad with zeros.
|
|
133
|
+
* @param length How many digits the resulting string should have.
|
|
134
|
+
* @returns The padded string.
|
|
135
|
+
* @throws If the digits of `num` is greater than `length`.
|
|
136
|
+
* @throws If `num` is NaN, a decimal or negative.
|
|
137
|
+
* @throws If `length` is not NaN, a decimal or less than 1.
|
|
138
|
+
*/
|
|
139
|
+
export function padZeros(num: number, length: number): string {
|
|
140
|
+
if (isNaN(num)) throw new Error('Number must not be NaN')
|
|
141
|
+
if (isNaN(length)) throw new Error('Length must not be NaN')
|
|
142
|
+
if (num < 0) throw new Error('Number must not be negative')
|
|
143
|
+
if (length <= 0) throw new Error('Length must not be less than 1')
|
|
144
|
+
if (num % 1 !== 0) throw new Error('Number must be an integer')
|
|
145
|
+
if (length % 1 !== 0) throw new Error('Length must be an integer')
|
|
146
|
+
|
|
147
|
+
const digits = Math.floor(Math.log10(num)) + 1
|
|
148
|
+
if (num !== 0 && digits > length)
|
|
149
|
+
throw new Error('Number must not have more digits than length')
|
|
150
|
+
|
|
151
|
+
return String(num).padStart(length, '0')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Pad zeros for the year component of a date string.
|
|
156
|
+
* @param year The year, should be positive.
|
|
157
|
+
* @returns The 4-digit padded year.
|
|
158
|
+
*/
|
|
159
|
+
export function padYear(year: number): string {
|
|
160
|
+
return padZeros(year, 4)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Pad zeros for the month component of a date string.
|
|
165
|
+
* @param month The month, should be between 1 and 12.
|
|
166
|
+
* @returns The 2-digit padded month.
|
|
167
|
+
*/
|
|
168
|
+
export function padMonth(month: number): string {
|
|
169
|
+
return padZeros(month, 2)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Pad zeros for the day component of a date string.
|
|
174
|
+
* @param day The day, should be between 1 and 31.
|
|
175
|
+
* @returns The 2-digit padded day.
|
|
176
|
+
*/
|
|
177
|
+
export function padDay(day: number): string {
|
|
178
|
+
return padZeros(day, 2)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Pad zeros for the hours component of a time string.
|
|
183
|
+
* @param hours The hours, should be between 0 and 23.
|
|
184
|
+
* @returns The 2-digit padded hours.
|
|
185
|
+
*/
|
|
186
|
+
export function padHours(hours: number): string {
|
|
187
|
+
return padZeros(hours, 2)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Pad zeros for the minutes component of a time string.
|
|
192
|
+
* @param minutes The minutes, should be between 0 and 59.
|
|
193
|
+
* @returns The 2-digit padded minutes.
|
|
194
|
+
*/
|
|
195
|
+
export function padMinutes(minutes: number): string {
|
|
196
|
+
return padZeros(minutes, 2)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Pad zeros for the seconds component of a time string.
|
|
201
|
+
* @param seconds The seconds, should be between 0 and 59.
|
|
202
|
+
* @returns The 2-digit padded seconds.
|
|
203
|
+
*/
|
|
204
|
+
export function padSeconds(seconds: number): string {
|
|
205
|
+
return padZeros(seconds, 2)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Parse a date property into a {@link CalendarDateOrTime}.
|
|
210
|
+
* @param dateProperty The property to parse.
|
|
211
|
+
* @param defaultType The default value type to be used if the property does not have an explicit value type.
|
|
212
|
+
* @returns The parsed date as a {@link CalendarDateOrTime}.
|
|
213
|
+
* @throws If the value is invalid for the value type.
|
|
214
|
+
* @throws If the value type is not `DATE-TIME` or `DATE`.
|
|
215
|
+
*/
|
|
216
|
+
export function parseDateProperty(
|
|
217
|
+
dateProperty: Property,
|
|
218
|
+
defaultType: 'DATE-TIME' | 'DATE' = 'DATE-TIME'
|
|
219
|
+
): CalendarDateOrTime {
|
|
220
|
+
const value = dateProperty.value
|
|
221
|
+
const valueType = getPropertyValueType(dateProperty, defaultType)
|
|
222
|
+
|
|
223
|
+
if (valueType === 'DATE-TIME') {
|
|
224
|
+
return new CalendarDateTime(value)
|
|
225
|
+
} else if (valueType === 'DATE') {
|
|
226
|
+
return new CalendarDate(value)
|
|
227
|
+
} else {
|
|
228
|
+
throw new Error(`Illegal value type for date '${valueType}'`)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Format a date as an iCalendar compatible time string. The day of the date is
|
|
234
|
+
* ignored.
|
|
235
|
+
* @param date The date to convert to a time string.
|
|
236
|
+
* @returns The time of the date formatted according to the iCalendar specification.
|
|
237
|
+
* @throws If the date is invalid.
|
|
238
|
+
*/
|
|
239
|
+
export function toTimeString(date: Date): string {
|
|
240
|
+
if (isNaN(date.getTime())) throw new Error('Date is invalid')
|
|
241
|
+
return `${padHours(date.getHours())}${padMinutes(date.getMinutes())}${padSeconds(date.getSeconds())}`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Format a date as an iCalendar compatible date string.
|
|
246
|
+
* @param date The date to convert to a date string.
|
|
247
|
+
* @returns A date string formatted according to the iCalendar specification.
|
|
248
|
+
* @throws If the date is invalid.
|
|
249
|
+
*/
|
|
250
|
+
export function toDateString(date: Date): string {
|
|
251
|
+
if (isNaN(date.getTime())) throw new Error('Date is invalid')
|
|
252
|
+
return `${padYear(date.getFullYear())}${padMonth(date.getMonth() + 1)}${padDay(date.getDate())}`
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Format a date as an iCalendar compatible date-time string.
|
|
257
|
+
* @param date The date to convert to a date-time string.
|
|
258
|
+
* @returns A date-time string formatted according to the iCalendar specification.
|
|
259
|
+
* @throws If the date is invalid.
|
|
260
|
+
*/
|
|
261
|
+
export function toDateTimeString(date: Date): string {
|
|
262
|
+
if (isNaN(date.getTime())) throw new Error('Date is invalid')
|
|
263
|
+
return `${toDateString(date)}T${toTimeString(date)}`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Format a date as an iCalendar compatible date-time string. Offsets the date
|
|
268
|
+
* to UTC using `timeZoneOffset`.
|
|
269
|
+
* @param date The date in local time to convert to UTC and a date-time string.
|
|
270
|
+
* @param timeZoneOffset The timezone offset in minutes.
|
|
271
|
+
* @returns A UTC date-time string formatted according to the iCalendar specification.
|
|
272
|
+
* @throws If the date is invalid.
|
|
273
|
+
* @throws If the offset is invalid.
|
|
274
|
+
* @example
|
|
275
|
+
* // The timezone offset for CET (Central European Time)
|
|
276
|
+
* const timeZoneOffsetCET = -60 // +01:00
|
|
277
|
+
* const date = new Date('2025-08-07T12:00:00') // The time in CET
|
|
278
|
+
* // Returns "20250807T110000Z"
|
|
279
|
+
* const utcDate = toDateTimeStringUTC(date, timeZoneOffsetCET)
|
|
280
|
+
*/
|
|
281
|
+
export function toDateTimeStringUTC(
|
|
282
|
+
date: Date,
|
|
283
|
+
timeZoneOffset: number
|
|
284
|
+
): string {
|
|
285
|
+
if (isNaN(date.getTime())) throw new Error('Date is invalid')
|
|
286
|
+
if (isNaN(timeZoneOffset)) throw new Error('Time zone offset cannot be NaN')
|
|
287
|
+
const utcDate = new Date(date.getTime() + timeZoneOffset * ONE_MINUTE_MS)
|
|
288
|
+
return `${toDateString(utcDate)}T${toTimeString(utcDate)}Z`
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Parse a date-time string to a `Date`.
|
|
293
|
+
* @param dateTime A date-time string formatted according to the iCalendar specification.
|
|
294
|
+
* @returns The parsed date-time.
|
|
295
|
+
* @throws If the date is invalid.
|
|
296
|
+
*/
|
|
297
|
+
export function parseDateTimeString(dateTime: string): Date {
|
|
298
|
+
if (!patterns.valueTypeDateTime.test(dateTime)) {
|
|
299
|
+
throw new Error('Date-time has invalid format')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const year = parseInt(dateTime.substring(0, 4))
|
|
303
|
+
const monthIndex = parseInt(dateTime.substring(4, 6)) - 1
|
|
304
|
+
const day = parseInt(dateTime.substring(6, 8))
|
|
305
|
+
|
|
306
|
+
if (monthIndex < 0 || monthIndex > 11) {
|
|
307
|
+
throw new Error('Date-time is invalid')
|
|
308
|
+
}
|
|
309
|
+
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate()
|
|
310
|
+
if (day < 1 || day > daysInMonth) {
|
|
311
|
+
throw new Error('Date-time is invalid')
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const hours = parseInt(dateTime.substring(9, 11))
|
|
315
|
+
if (hours < 0 || hours > 23) {
|
|
316
|
+
throw new Error('Date-time is invalid')
|
|
317
|
+
}
|
|
318
|
+
const minutes = parseInt(dateTime.substring(11, 13))
|
|
319
|
+
if (minutes < 0 || minutes > 59) {
|
|
320
|
+
throw new Error('Date-time is invalid')
|
|
321
|
+
}
|
|
322
|
+
const seconds = parseInt(dateTime.substring(13, 15))
|
|
323
|
+
if (seconds < 0 || seconds > 59) {
|
|
324
|
+
throw new Error('Date-time is invalid')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (dateTime.endsWith('Z')) {
|
|
328
|
+
const time = Date.UTC(year, monthIndex, day, hours, minutes, seconds)
|
|
329
|
+
return new Date(time)
|
|
330
|
+
} else {
|
|
331
|
+
const parsedDate = new Date(
|
|
332
|
+
year,
|
|
333
|
+
monthIndex,
|
|
334
|
+
day,
|
|
335
|
+
hours,
|
|
336
|
+
minutes,
|
|
337
|
+
seconds
|
|
338
|
+
)
|
|
339
|
+
return parsedDate
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Parse a date string to a `Date`.
|
|
345
|
+
* @param date A date-time string formatted according to the iCalendar specification.
|
|
346
|
+
* @returns The parsed date.
|
|
347
|
+
* @throws If the date is invalid.
|
|
348
|
+
*/
|
|
349
|
+
export function parseDateString(date: string): Date {
|
|
350
|
+
if (!patterns.matchesWholeString(patterns.valueTypeDate, date)) {
|
|
351
|
+
throw new Error('Date has invalid format')
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const year = parseInt(date.substring(0, 4))
|
|
355
|
+
const monthIndex = parseInt(date.substring(4, 6)) - 1
|
|
356
|
+
const day = parseInt(date.substring(6, 8))
|
|
357
|
+
|
|
358
|
+
if (monthIndex < 0 || monthIndex > 11) {
|
|
359
|
+
throw new Error('Date is invalid')
|
|
360
|
+
}
|
|
361
|
+
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate()
|
|
362
|
+
if (day < 1 || day > daysInMonth) {
|
|
363
|
+
throw new Error('Date is invalid')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const parsedDate = new Date(year, monthIndex, day)
|
|
367
|
+
return parsedDate
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Convert `Date` objects to a {@link CalendarDateOrTime} object or return as
|
|
372
|
+
* is if `date` is already a {@link CalendarDateOrTime}.
|
|
373
|
+
* @param date The date object to convert.
|
|
374
|
+
* @param fullDay If `true`, a `Date` object is converted to {@link CalendarDate}, otherwise `Date` is converted to {@link CalendarDateTime}.
|
|
375
|
+
* @returns A {@link CalendarDateOrTime} as described above.
|
|
376
|
+
*/
|
|
377
|
+
export function convertDate<T extends CalendarDateOrTime>(
|
|
378
|
+
date: Date | T,
|
|
379
|
+
fullDay?: false
|
|
380
|
+
): T | CalendarDateTime
|
|
381
|
+
export function convertDate<T extends CalendarDateOrTime>(
|
|
382
|
+
date: Date | T,
|
|
383
|
+
fullDay: true
|
|
384
|
+
): T | CalendarDate
|
|
385
|
+
export function convertDate(
|
|
386
|
+
date: Date | CalendarDateOrTime,
|
|
387
|
+
fullDay: boolean = false
|
|
388
|
+
): CalendarDateOrTime {
|
|
389
|
+
if (Object.prototype.toString.call(date) === '[object Date]') {
|
|
390
|
+
if (fullDay) return new CalendarDate(date as Date)
|
|
391
|
+
else return new CalendarDateTime(date as Date)
|
|
392
|
+
} else {
|
|
393
|
+
return date as CalendarDateOrTime
|
|
394
|
+
}
|
|
395
|
+
}
|