iamcal 1.0.2 → 1.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 +7 -3
- package/lib/index.d.ts +130 -16
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +356 -20
- package/lib/parse.d.ts +5 -0
- package/lib/parse.d.ts.map +1 -1
- package/lib/parse.js +25 -1
- package/lib/types.d.ts +6 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +3 -0
- package/package.json +16 -5
- package/src/index.ts +445 -33
- package/src/parse.ts +24 -0
- package/src/types.ts +5 -0
package/src/index.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
params: string[]
|
|
4
|
-
value: string
|
|
5
|
-
}
|
|
1
|
+
import { parseDate, toDateString, toDateTimeString } from './parse'
|
|
2
|
+
import { Property } from './types'
|
|
6
3
|
|
|
7
4
|
// Max line length as defined by RFC 5545 3.1.
|
|
8
5
|
const MAX_LINE_LENGTH = 75
|
|
9
6
|
|
|
10
7
|
export class Component {
|
|
11
8
|
name: string
|
|
12
|
-
properties:
|
|
13
|
-
components:
|
|
9
|
+
properties: Property[]
|
|
10
|
+
components: Component[]
|
|
14
11
|
|
|
15
|
-
constructor(name: string, properties?:
|
|
12
|
+
constructor(name: string, properties?: Property[], components?: Component[]) {
|
|
16
13
|
this.name = name
|
|
17
14
|
if (properties) {
|
|
18
15
|
this.properties = properties
|
|
@@ -40,7 +37,7 @@ export class Component {
|
|
|
40
37
|
// Wrap lines
|
|
41
38
|
while (line.length > MAX_LINE_LENGTH) {
|
|
42
39
|
lines.push(line.substring(0, MAX_LINE_LENGTH))
|
|
43
|
-
line =
|
|
40
|
+
line = ' ' + line.substring(MAX_LINE_LENGTH)
|
|
44
41
|
}
|
|
45
42
|
lines.push(line)
|
|
46
43
|
}
|
|
@@ -66,11 +63,11 @@ export class Component {
|
|
|
66
63
|
return null
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
setProperty(name: string, value: string) {
|
|
66
|
+
setProperty(name: string, value: string): this {
|
|
70
67
|
for (const property of this.properties) {
|
|
71
68
|
if (property.name === name) {
|
|
72
69
|
property.value = value
|
|
73
|
-
return
|
|
70
|
+
return this
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
73
|
this.properties.push({
|
|
@@ -78,6 +75,14 @@ export class Component {
|
|
|
78
75
|
params: [],
|
|
79
76
|
value: value,
|
|
80
77
|
})
|
|
78
|
+
return this
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
removePropertiesWithName(name: string) {
|
|
82
|
+
const index = this.properties.findIndex(p => p.name === name)
|
|
83
|
+
if (index === -1) return this
|
|
84
|
+
// Remove property at index
|
|
85
|
+
this.properties.splice(index, 1)
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
getPropertyParams(name: string): string[] | null {
|
|
@@ -89,79 +94,486 @@ export class Component {
|
|
|
89
94
|
return null
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
setPropertyParams(name: string, params: string[]) {
|
|
97
|
+
setPropertyParams(name: string, params: string[]): this {
|
|
93
98
|
for (const property of this.properties) {
|
|
94
99
|
if (property.name === name) {
|
|
95
100
|
property.params = params
|
|
96
101
|
}
|
|
97
102
|
}
|
|
103
|
+
return this
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
addComponent(component: Component): this {
|
|
107
|
+
this.components.push(component)
|
|
108
|
+
return this
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Remove a component from this component
|
|
113
|
+
* @returns `true` if the component was removed. `false` if the component was not present
|
|
114
|
+
*/
|
|
115
|
+
removeComponent(component: Component): boolean {
|
|
116
|
+
const index = this.components.indexOf(component)
|
|
117
|
+
if (index === -1) return false
|
|
118
|
+
|
|
119
|
+
// Remove element at index from list
|
|
120
|
+
this.components.splice(index, 1)
|
|
121
|
+
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getComponents(name: string): Component[] {
|
|
126
|
+
const components: Component[] = []
|
|
127
|
+
|
|
128
|
+
for (let component of this.components) {
|
|
129
|
+
if (component.name === name) {
|
|
130
|
+
components.push(component)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return components
|
|
98
135
|
}
|
|
99
136
|
}
|
|
100
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Represents a VCALENDAR component, the root component of an iCalendar file.
|
|
140
|
+
*/
|
|
101
141
|
export class Calendar extends Component {
|
|
102
142
|
name = 'VCALENDAR'
|
|
103
143
|
|
|
104
|
-
|
|
144
|
+
/**
|
|
145
|
+
* @param prodid A unique identifier of the program creating the calendar.
|
|
146
|
+
*
|
|
147
|
+
* Example: `-//Google Inc//Google Calendar 70.9054//EN`
|
|
148
|
+
*/
|
|
149
|
+
constructor(prodid: string)
|
|
150
|
+
/**
|
|
151
|
+
* @param prodid A unique identifier of the program creating the calendar.
|
|
152
|
+
*
|
|
153
|
+
* Example: `-//Google Inc//Google Calendar 70.9054//EN`
|
|
154
|
+
* @param version The version of the iCalendar specification that this calendar uses.
|
|
155
|
+
*/
|
|
156
|
+
constructor(prodid: string, version: string)
|
|
157
|
+
/**
|
|
158
|
+
* @param component A VCALENDAR component.
|
|
159
|
+
*/
|
|
160
|
+
constructor(component: Component)
|
|
161
|
+
constructor(a?: string | Component, b?: string) {
|
|
162
|
+
var component: Component
|
|
163
|
+
if (a instanceof Component) {
|
|
164
|
+
component = a as Component
|
|
165
|
+
} else {
|
|
166
|
+
const prodid = a as string
|
|
167
|
+
const version = (b as string) ?? '2.0'
|
|
168
|
+
component = new Component('VCALENDAR')
|
|
169
|
+
component.setProperty('PRODID', prodid)
|
|
170
|
+
component.setProperty('VERSION', version)
|
|
171
|
+
}
|
|
105
172
|
super(component.name, component.properties, component.components)
|
|
106
173
|
}
|
|
107
174
|
|
|
108
|
-
events():
|
|
109
|
-
|
|
175
|
+
events(): CalendarEvent[] {
|
|
176
|
+
return this.getComponents('VEVENT').map(c => new CalendarEvent(c))
|
|
177
|
+
}
|
|
110
178
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
179
|
+
removeEvent(event: CalendarEvent): boolean
|
|
180
|
+
removeEvent(uid: string): boolean
|
|
181
|
+
removeEvent(a: CalendarEvent | string): boolean {
|
|
182
|
+
if (a instanceof CalendarEvent) {
|
|
183
|
+
const event = a as CalendarEvent
|
|
184
|
+
return this.removeComponent(event)
|
|
185
|
+
} else {
|
|
186
|
+
const uid = a as string
|
|
187
|
+
for (const event of this.events()) {
|
|
188
|
+
if (event.uid() !== uid) continue
|
|
189
|
+
return this.removeComponent(event)
|
|
114
190
|
}
|
|
115
191
|
}
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
prodId(): string {
|
|
196
|
+
return this.getProperty('PRODID')!.value
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
setProdId(value: string): this {
|
|
200
|
+
return this.setProperty('PRODID', value)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
version(): string {
|
|
204
|
+
return this.getProperty('VERSION')!.value
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
setVersion(value: string): this {
|
|
208
|
+
return this.setProperty('VERSION', value)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
calScale(): string | undefined {
|
|
212
|
+
return this.getProperty('CALSCALE')?.value
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setCalScale(value: string): this {
|
|
216
|
+
return this.setProperty('CALSCALE', value)
|
|
217
|
+
}
|
|
116
218
|
|
|
117
|
-
|
|
219
|
+
removeCalScale() {
|
|
220
|
+
this.removePropertiesWithName('CALSCALE')
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
method(): string | undefined {
|
|
224
|
+
return this.getProperty('METHOD')?.value
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setMethod(value: string) {
|
|
228
|
+
this.setProperty('METHOD', value)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
removeMethod() {
|
|
232
|
+
this.removePropertiesWithName('METHOD')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
calendarName(): string | undefined {
|
|
236
|
+
return this.getProperty('X-WR-CALNAME')?.value
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
setCalendarName(value: string) {
|
|
240
|
+
this.setProperty('X-WR-CALNAME', value)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
removeCalendarName() {
|
|
244
|
+
this.removePropertiesWithName('X-WR-CALNAME')
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
calendarDescription(): string | undefined {
|
|
248
|
+
return this.getProperty('X-WR-CALDESC')?.value
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
setCalendarDescription(value: string): this {
|
|
252
|
+
return this.setProperty('X-WR-CALDESC', value)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
removeCalendarDescription() {
|
|
256
|
+
this.removePropertiesWithName('X-WR-CALDESC')
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Represents a VTIMEZONE component, containing time zone definitions.
|
|
262
|
+
*/
|
|
263
|
+
export class TimeZone extends Component {
|
|
264
|
+
constructor(id: string)
|
|
265
|
+
constructor(component: Component)
|
|
266
|
+
constructor(a: string | Component) {
|
|
267
|
+
var component: Component
|
|
268
|
+
if (a instanceof Component) {
|
|
269
|
+
component = a as Component
|
|
270
|
+
} else {
|
|
271
|
+
const tzid = a as string
|
|
272
|
+
component = new Component('VTIMEZONE')
|
|
273
|
+
component.setProperty('TZID', tzid)
|
|
274
|
+
}
|
|
275
|
+
super(component.name, component.properties, component.components)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
id(): string {
|
|
279
|
+
return this.getProperty('TZID')!.value
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
setId(value: string): this {
|
|
283
|
+
return this.setProperty('TZID', value)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
lastMod(): Date | undefined {
|
|
287
|
+
const text = this.getProperty('LAST-MOD')
|
|
288
|
+
if (!text) return
|
|
289
|
+
return parseDate(text)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
setLastMod(value: Date): this {
|
|
293
|
+
return this.setProperty('LAST-MOD', value.toISOString())
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
removeLastMod() {
|
|
297
|
+
this.removePropertiesWithName('LAST-MOD')
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
url(): string | undefined {
|
|
301
|
+
return this.getProperty('TZURL')?.value
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
setUrl(value: string): this {
|
|
305
|
+
return this.setProperty('TZURL', value)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
removeUrl() {
|
|
309
|
+
this.removePropertiesWithName('TZURL')
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Get all time offsets. */
|
|
313
|
+
offsets(): TimeZoneOffset[] {
|
|
314
|
+
const offsets: TimeZoneOffset[] = []
|
|
315
|
+
this.components.forEach(component => {
|
|
316
|
+
if (component.name === 'STANDARD' || component.name === 'DAYLIGHT') {
|
|
317
|
+
offsets.push(new TimeZoneOffset(component))
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
return offsets
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Get standard/winter time offsets. */
|
|
324
|
+
standardOffsets(): TimeZoneOffset[] {
|
|
325
|
+
return this.getComponents('STANDARD').map(c => new TimeZoneOffset(c))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Get daylight savings time offsets. */
|
|
329
|
+
daylightOffsets(): TimeZoneOffset[] {
|
|
330
|
+
return this.getComponents('DAYLIGHT').map(c => new TimeZoneOffset(c))
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export type OffsetType = 'DAYLIGHT' | 'STANDARD'
|
|
335
|
+
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
|
336
|
+
export type Offset = `${'-' | '+'}${Digit}${Digit}${Digit}${Digit}`
|
|
337
|
+
/** Represents a STANDARD or DAYLIGHT component, defining a time zone offset. */
|
|
338
|
+
class TimeZoneOffset extends Component {
|
|
339
|
+
/**
|
|
340
|
+
*
|
|
341
|
+
* @param type If this is a STANDARD or DAYLIGHT component.
|
|
342
|
+
* @param start From when this offset is active.
|
|
343
|
+
* @param offsetFrom The offset that is in use prior to this time zone observance.
|
|
344
|
+
* @param offsetTo The offset that is in use during this time zone observance.
|
|
345
|
+
*/
|
|
346
|
+
constructor(type: OffsetType, start: Date, offsetFrom: Offset, offsetTo: Offset)
|
|
347
|
+
constructor(component: Component)
|
|
348
|
+
constructor(a: OffsetType | Component, b?: Date, c?: Offset, d?: Offset) {
|
|
349
|
+
var component: Component
|
|
350
|
+
if (a instanceof Component) {
|
|
351
|
+
component = a as Component
|
|
352
|
+
} else {
|
|
353
|
+
const name = a as OffsetType
|
|
354
|
+
const start = b as Date
|
|
355
|
+
const offsetFrom = c as Offset
|
|
356
|
+
const offsetTo = d as Offset
|
|
357
|
+
component = new Component(name)
|
|
358
|
+
component.setProperty('DTSTART', toDateTimeString(start))
|
|
359
|
+
component.setProperty('TZOFFSETFROM', offsetFrom)
|
|
360
|
+
component.setProperty('TZOFFSETTO', offsetTo)
|
|
361
|
+
}
|
|
362
|
+
super(component.name, component.properties, component.components)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
start(): Date {
|
|
366
|
+
return parseDate(this.getProperty('DTSTART')!)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
setStart(value: Date, fullDay: boolean = false): this {
|
|
370
|
+
if (fullDay) {
|
|
371
|
+
this.setProperty('DTSTART', toDateString(value))
|
|
372
|
+
this.setPropertyParams('DTSTART', ['VALUE=DATE'])
|
|
373
|
+
} else {
|
|
374
|
+
this.setProperty('DTSTART', toDateTimeString(value))
|
|
375
|
+
}
|
|
376
|
+
return this
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
offsetFrom(): Offset {
|
|
380
|
+
return this.getProperty('TZOFFSETFROM')!.value as Offset
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
setOffsetFrom(value: Offset): this {
|
|
384
|
+
return this.setProperty('TZOFFSETFROM', value)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
offsetTo(): Offset {
|
|
388
|
+
return this.getProperty('TZOFFSETTO')!.value as Offset
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
setOffsetTo(value: Offset): this {
|
|
392
|
+
return this.setProperty('TZOFFSETTO', value)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
comment(): string | undefined {
|
|
396
|
+
return this.getProperty('COMMENT')?.value
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
setComment(value: string): this {
|
|
400
|
+
return this.setProperty('COMMENT', value)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
removeComment() {
|
|
404
|
+
this.removePropertiesWithName('COMMENT')
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
timeZoneName(): string | undefined {
|
|
408
|
+
return this.getProperty('TZNAME')?.value
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
setTimeZoneName(value: string): this {
|
|
412
|
+
return this.setProperty('TZNAME', value)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
removeTimeZoneName() {
|
|
416
|
+
this.removePropertiesWithName('TZNAME')
|
|
118
417
|
}
|
|
119
418
|
}
|
|
120
419
|
|
|
121
420
|
export class CalendarEvent extends Component {
|
|
122
421
|
name = 'VEVENT'
|
|
123
422
|
|
|
124
|
-
constructor(
|
|
423
|
+
constructor(uid: string, dtstamp: Date)
|
|
424
|
+
constructor(component: Component)
|
|
425
|
+
constructor(a: string | Component, b?: Date) {
|
|
426
|
+
var component: Component
|
|
427
|
+
if (b) {
|
|
428
|
+
const uid = a as string
|
|
429
|
+
const dtstamp = b as Date
|
|
430
|
+
component = new Component('VEVENT')
|
|
431
|
+
component.setProperty('UID', uid)
|
|
432
|
+
component.setProperty('DTSTAMP', toDateTimeString(dtstamp))
|
|
433
|
+
} else {
|
|
434
|
+
component = a as Component
|
|
435
|
+
}
|
|
125
436
|
super(component.name, component.properties, component.components)
|
|
126
437
|
}
|
|
127
438
|
|
|
439
|
+
stamp(): Date {
|
|
440
|
+
return parseDate(this.getProperty('DTSTAMP')!)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
setStamp(value: Date, fullDay: boolean = false): this {
|
|
444
|
+
if (fullDay) {
|
|
445
|
+
this.setProperty('DTSTAMP', toDateString(value))
|
|
446
|
+
this.setPropertyParams('DTSTAMP', ['VALUE=DATE'])
|
|
447
|
+
} else {
|
|
448
|
+
this.setProperty('DTSTAMP', toDateTimeString(value))
|
|
449
|
+
}
|
|
450
|
+
return this
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
uid(): string {
|
|
454
|
+
return this.getProperty('UID')!.value
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
setUid(value: string): this {
|
|
458
|
+
return this.setProperty('UID', value)
|
|
459
|
+
}
|
|
460
|
+
|
|
128
461
|
summary(): string {
|
|
129
462
|
return this.getProperty('SUMMARY')!.value
|
|
130
463
|
}
|
|
131
464
|
|
|
132
|
-
setSummary(value: string) {
|
|
133
|
-
this.setProperty(
|
|
465
|
+
setSummary(value: string): this {
|
|
466
|
+
return this.setProperty('SUMMARY', value)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
removeSummary() {
|
|
470
|
+
this.removePropertiesWithName('SUMMARY')
|
|
134
471
|
}
|
|
135
472
|
|
|
136
473
|
description(): string {
|
|
137
474
|
return this.getProperty('DESCRIPTION')!.value
|
|
138
475
|
}
|
|
139
476
|
|
|
140
|
-
setDescription(value: string) {
|
|
141
|
-
this.setProperty(
|
|
477
|
+
setDescription(value: string): this {
|
|
478
|
+
return this.setProperty('DESCRIPTION', value)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
removeDescription() {
|
|
482
|
+
this.removePropertiesWithName('DESCRIPTION')
|
|
142
483
|
}
|
|
143
484
|
|
|
144
485
|
location(): string | undefined {
|
|
145
486
|
return this.getProperty('LOCATION')?.value
|
|
146
487
|
}
|
|
147
488
|
|
|
148
|
-
setLocation(value: string) {
|
|
149
|
-
this.setProperty(
|
|
489
|
+
setLocation(value: string): this {
|
|
490
|
+
return this.setProperty('LOCATION', value)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
removeLocation() {
|
|
494
|
+
this.removePropertiesWithName('LOCATION')
|
|
150
495
|
}
|
|
151
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Get the start of the event.
|
|
499
|
+
* If set as a full day the time will be at the start of the day.
|
|
500
|
+
*/
|
|
152
501
|
start(): Date {
|
|
153
|
-
return
|
|
502
|
+
return parseDate(this.getProperty('DTSTART')!)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
setStart(value: Date, fullDay: boolean = false): this {
|
|
506
|
+
if (fullDay) {
|
|
507
|
+
this.setProperty('DTSTART', toDateString(value))
|
|
508
|
+
this.setPropertyParams('DTSTART', ['VALUE=DATE'])
|
|
509
|
+
} else {
|
|
510
|
+
this.setProperty('DTSTART', toDateTimeString(value))
|
|
511
|
+
}
|
|
512
|
+
return this
|
|
154
513
|
}
|
|
155
514
|
|
|
156
|
-
|
|
157
|
-
this.
|
|
515
|
+
removeStart() {
|
|
516
|
+
this.removePropertiesWithName('DTSTART')
|
|
158
517
|
}
|
|
159
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Get the end of the event.
|
|
521
|
+
* If set as a full day the time will be at the end of the day.
|
|
522
|
+
*/
|
|
160
523
|
end(): Date {
|
|
161
|
-
|
|
524
|
+
const endDate = parseDate(this.getProperty('DTEND')!)
|
|
525
|
+
if (this.getPropertyParams('DTEND')?.includes('VALUE=DATE')) {
|
|
526
|
+
const ONE_DAY = 24 * 60 * 60 * 1000
|
|
527
|
+
const endTime = new Date(endDate.getTime() + ONE_DAY)
|
|
528
|
+
return endTime
|
|
529
|
+
} else {
|
|
530
|
+
return endDate
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
setEnd(value: Date, fullDay: boolean = false): this {
|
|
535
|
+
if (fullDay) {
|
|
536
|
+
this.setProperty('DTEND', toDateString(value))
|
|
537
|
+
this.setPropertyParams('DTEND', ['VALUE=DATE'])
|
|
538
|
+
} else {
|
|
539
|
+
this.setProperty('DTEND', toDateTimeString(value))
|
|
540
|
+
}
|
|
541
|
+
return this
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
removeEnd() {
|
|
545
|
+
this.removePropertiesWithName('DTEND')
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
created(): Date | undefined {
|
|
549
|
+
const property = this.getProperty('CREATED')
|
|
550
|
+
if (!property) return
|
|
551
|
+
return parseDate(property)
|
|
162
552
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.setProperty(
|
|
553
|
+
|
|
554
|
+
setCreated(value: Date): this {
|
|
555
|
+
return this.setProperty('CREATED', toDateTimeString(value))
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
removeCreated() {
|
|
559
|
+
this.removePropertiesWithName('CREATED')
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
geo(): [number, number] | undefined {
|
|
563
|
+
const text = this.getProperty('GEO')?.value
|
|
564
|
+
if (!text) return
|
|
565
|
+
const pattern = /^[+-]?\d+(\.\d+)?,[+-]?\d+(\.\d+)?$/
|
|
566
|
+
if (!pattern.test(text)) throw new Error(`Failed to parse GEO property: ${text}`)
|
|
567
|
+
const [longitude, latitude] = text.split(',')
|
|
568
|
+
return [parseFloat(longitude), parseFloat(latitude)]
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
setGeo(latitude: number, longitude: number): this {
|
|
572
|
+
const text = `${latitude},${longitude}`
|
|
573
|
+
return this
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
removeGeo() {
|
|
577
|
+
this.removePropertiesWithName('GEO')
|
|
166
578
|
}
|
|
167
579
|
}
|
package/src/parse.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import readline from 'readline'
|
|
2
2
|
import { Readable } from 'stream'
|
|
3
3
|
import { Calendar, CalendarEvent, Component } from '.'
|
|
4
|
+
import { Property } from './types'
|
|
4
5
|
|
|
5
6
|
export class DeserializationError extends Error {
|
|
6
7
|
name = 'DeserializationError'
|
|
@@ -162,4 +163,27 @@ export async function parseEvent(text: string): Promise<CalendarEvent> {
|
|
|
162
163
|
if (component.name !== "VEVENT")
|
|
163
164
|
throw new DeserializationError("Not an event")
|
|
164
165
|
return new CalendarEvent(component)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function parseDate(dateProperty: Property): Date {
|
|
169
|
+
const value = dateProperty.value.trim()
|
|
170
|
+
if (dateProperty.params.includes('VALUE=DATE')) {
|
|
171
|
+
// Parse date only
|
|
172
|
+
return new Date(`${value.substring(0, 4)}-${value.substring(4, 6)}-${value.substring(6, 8)}`)
|
|
173
|
+
} else {
|
|
174
|
+
// Parse date and time
|
|
175
|
+
return new Date(`${value.substring(0, 4)}-${value.substring(4, 6)}-${value.substring(6, 8)} ${value.substring(9, 11)}:${value.substring(11, 13)}:${value.substring(13, 15)}`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function toTimeString(date: Date): string {
|
|
180
|
+
return `${date.getHours().toString().padStart(2, '0')}${date.getMinutes().toString().padStart(2, '0')}${date.getSeconds().toString().padStart(2, '0')}`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function toDateString(date: Date): string {
|
|
184
|
+
return `${date.getFullYear().toString().padStart(4, '0')}${date.getMonth().toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function toDateTimeString(date: Date): string {
|
|
188
|
+
return `${toDateString(date)}T${toTimeString(date)}`
|
|
165
189
|
}
|