@pipobscure/ical 0.0.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/LICENSE +190 -0
- package/README.md +661 -0
- package/dist/alarm.d.ts +35 -0
- package/dist/alarm.d.ts.map +1 -0
- package/dist/alarm.js +87 -0
- package/dist/alarm.js.map +1 -0
- package/dist/calendar.d.ts +52 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +121 -0
- package/dist/calendar.js.map +1 -0
- package/dist/component.d.ts +48 -0
- package/dist/component.d.ts.map +1 -0
- package/dist/component.js +170 -0
- package/dist/component.js.map +1 -0
- package/dist/event.d.ts +74 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +263 -0
- package/dist/event.js.map +1 -0
- package/dist/freebusy.d.ts +45 -0
- package/dist/freebusy.d.ts.map +1 -0
- package/dist/freebusy.js +111 -0
- package/dist/freebusy.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/journal.d.ts +48 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +148 -0
- package/dist/journal.js.map +1 -0
- package/dist/parse.d.ts +27 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +140 -0
- package/dist/parse.js.map +1 -0
- package/dist/property-registry.d.ts +31 -0
- package/dist/property-registry.d.ts.map +1 -0
- package/dist/property-registry.js +83 -0
- package/dist/property-registry.js.map +1 -0
- package/dist/property.d.ts +36 -0
- package/dist/property.d.ts.map +1 -0
- package/dist/property.js +135 -0
- package/dist/property.js.map +1 -0
- package/dist/timezone.d.ts +55 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +141 -0
- package/dist/timezone.js.map +1 -0
- package/dist/todo.d.ts +67 -0
- package/dist/todo.d.ts.map +1 -0
- package/dist/todo.js +220 -0
- package/dist/todo.js.map +1 -0
- package/dist/tokenize.d.ts +22 -0
- package/dist/tokenize.d.ts.map +1 -0
- package/dist/tokenize.js +95 -0
- package/dist/tokenize.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/value-types.d.ts +81 -0
- package/dist/value-types.d.ts.map +1 -0
- package/dist/value-types.js +445 -0
- package/dist/value-types.js.map +1 -0
- package/package.json +45 -0
- package/src/alarm.ts +105 -0
- package/src/calendar.ts +153 -0
- package/src/component.ts +193 -0
- package/src/event.ts +307 -0
- package/src/freebusy.ts +133 -0
- package/src/index.ts +85 -0
- package/src/journal.ts +174 -0
- package/src/parse.ts +166 -0
- package/src/property-registry.ts +124 -0
- package/src/property.ts +163 -0
- package/src/timezone.ts +169 -0
- package/src/todo.ts +253 -0
- package/src/tokenize.ts +99 -0
- package/src/types.ts +135 -0
- package/src/value-types.ts +498 -0
package/README.md
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
# @pipobscure/ical
|
|
2
|
+
|
|
3
|
+
A complete RFC 5545 iCalendar parser and serializer for Node.js, written in TypeScript.
|
|
4
|
+
|
|
5
|
+
**Tolerant on input.** Accepts real-world calendar data from Google Calendar, Apple Calendar, Microsoft Outlook, Thunderbird, and other producers that deviate from the specification — bare LF line endings, missing VCALENDAR wrappers, lowercase property names, non-standard parameters, folded lines in unusual places.
|
|
6
|
+
|
|
7
|
+
**Strict on output.** Generated iCalendar data uses canonical CRLF line endings, RFC 5545-compliant 75-octet UTF-8 line folding, proper parameter quoting, and required-property validation that throws before producing invalid data.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @pipobscure/ical
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Node.js 22 or later. The package is pure ESM.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
### Parsing
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { parse } from '@pipobscure/ical';
|
|
27
|
+
|
|
28
|
+
const cal = parse(icsString);
|
|
29
|
+
|
|
30
|
+
for (const event of cal.events) {
|
|
31
|
+
console.log(event.summary);
|
|
32
|
+
console.log(event.dtstart); // ICalDateTime | ICalDate | null
|
|
33
|
+
console.log(event.dtend);
|
|
34
|
+
console.log(event.rrules); // ICalRecur[]
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Building
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { Calendar, Event, Alarm } from '@pipobscure/ical';
|
|
42
|
+
|
|
43
|
+
const cal = Calendar.create('-//My App//My App v1.0//EN');
|
|
44
|
+
|
|
45
|
+
const event = new Event();
|
|
46
|
+
event.uid = crypto.randomUUID();
|
|
47
|
+
event.dtstamp = new Date();
|
|
48
|
+
event.dtstart = new Date('2025-06-01T09:00:00Z');
|
|
49
|
+
event.dtend = new Date('2025-06-01T10:00:00Z');
|
|
50
|
+
event.summary = 'Team standup';
|
|
51
|
+
event.description = 'Daily sync with the engineering team.';
|
|
52
|
+
|
|
53
|
+
cal.addEvent(event);
|
|
54
|
+
|
|
55
|
+
console.log(cal.toString()); // RFC 5545-compliant ICS string
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Multiple calendars in a single file
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { parseAll } from '@pipobscure/ical';
|
|
62
|
+
|
|
63
|
+
const calendars = parseAll(icsString);
|
|
64
|
+
for (const cal of calendars) {
|
|
65
|
+
console.log(cal.prodid, cal.events.length);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## API reference
|
|
72
|
+
|
|
73
|
+
### Parsing functions
|
|
74
|
+
|
|
75
|
+
#### `parse(src: string): Calendar`
|
|
76
|
+
|
|
77
|
+
Parses an iCalendar string and returns a single `Calendar` object. If the input contains more than one `VCALENDAR` block, the first one is returned. If the input contains no `VCALENDAR` wrapper at all, the loose components are wrapped in a synthetic `Calendar`.
|
|
78
|
+
|
|
79
|
+
The parser is fully tolerant:
|
|
80
|
+
- Accepts `CRLF`, `LF`, or bare `CR` line endings.
|
|
81
|
+
- Handles RFC 5545 line folding with space or tab continuation.
|
|
82
|
+
- Normalises property names and parameter names to uppercase.
|
|
83
|
+
- Strips outer quotes from quoted parameter values.
|
|
84
|
+
- Stores unknown properties as plain text without failing.
|
|
85
|
+
- Stores unknown components as generic `Component` objects.
|
|
86
|
+
|
|
87
|
+
#### `parseAll(src: string): Calendar[]`
|
|
88
|
+
|
|
89
|
+
Parses an iCalendar string and returns all `VCALENDAR` blocks as an array. If no `VCALENDAR` blocks are present, returns a single-element array wrapping the loose content.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### Calendar
|
|
94
|
+
|
|
95
|
+
The top-level container component, corresponding to `VCALENDAR`.
|
|
96
|
+
|
|
97
|
+
#### Factory
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
Calendar.create(prodid: string, method?: string): Calendar
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Creates a new `Calendar` with `VERSION:2.0` already set. `method` is optional (e.g. `'REQUEST'`, `'REPLY'`, `'CANCEL'`).
|
|
104
|
+
|
|
105
|
+
#### Properties
|
|
106
|
+
|
|
107
|
+
| Property | Type | Notes |
|
|
108
|
+
|----------|------|-------|
|
|
109
|
+
| `version` | `string \| null` | Always `'2.0'` for new calendars |
|
|
110
|
+
| `prodid` | `string \| null` | Required for serialisation |
|
|
111
|
+
| `calscale` | `string \| null` | Defaults to `GREGORIAN` if omitted |
|
|
112
|
+
| `method` | `string \| null` | iTIP method |
|
|
113
|
+
|
|
114
|
+
#### Sub-component accessors
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
cal.events: Event[]
|
|
118
|
+
cal.todos: Todo[]
|
|
119
|
+
cal.journals: Journal[]
|
|
120
|
+
cal.freebusys: FreeBusy[]
|
|
121
|
+
cal.timezones: Timezone[]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
#### Add methods (fluent, return `this`)
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
cal.addEvent(event: Event): this
|
|
128
|
+
cal.addTodo(todo: Todo): this
|
|
129
|
+
cal.addJournal(journal: Journal): this
|
|
130
|
+
cal.addFreebusy(fb: FreeBusy): this
|
|
131
|
+
cal.addTimezone(tz: Timezone): this
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Lookup helpers
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
cal.getByUid(uid: string): Event | Todo | Journal | undefined
|
|
138
|
+
cal.getTimezone(tzid: string): Timezone | undefined
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Serialisation
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
cal.toString(): string
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Throws if `PRODID` or `VERSION` is missing.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### Event
|
|
152
|
+
|
|
153
|
+
Corresponds to `VEVENT`.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const event = new Event();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Required properties
|
|
160
|
+
|
|
161
|
+
| Property | Type | Notes |
|
|
162
|
+
|----------|------|-------|
|
|
163
|
+
| `uid` | `string \| null` | Unique identifier; required for serialisation |
|
|
164
|
+
| `dtstamp` | `ICalDateTime \| null` | Accepts `Date` or `ICalDateTime` |
|
|
165
|
+
|
|
166
|
+
#### Date and time properties
|
|
167
|
+
|
|
168
|
+
| Property | Type |
|
|
169
|
+
|----------|------|
|
|
170
|
+
| `dtstart` | `ICalDateTime \| ICalDate \| null` |
|
|
171
|
+
| `dtend` | `ICalDateTime \| ICalDate \| null` |
|
|
172
|
+
| `duration` | `ICalDuration \| null` |
|
|
173
|
+
| `recurrenceId` | `ICalDateTime \| ICalDate \| null` |
|
|
174
|
+
| `created` | `ICalDateTime \| null` — accepts `Date` |
|
|
175
|
+
| `lastModified` | `ICalDateTime \| null` — accepts `Date` |
|
|
176
|
+
|
|
177
|
+
Setting a date-only `ICalDate` (or a plain object with `type: 'date'`) automatically adds the `VALUE=DATE` parameter on serialisation.
|
|
178
|
+
|
|
179
|
+
Setting a JavaScript `Date` converts it to a UTC `ICalDateTime`.
|
|
180
|
+
|
|
181
|
+
#### Descriptive properties
|
|
182
|
+
|
|
183
|
+
| Property | Type |
|
|
184
|
+
|----------|------|
|
|
185
|
+
| `summary` | `string \| null` |
|
|
186
|
+
| `description` | `string \| null` |
|
|
187
|
+
| `location` | `string \| null` |
|
|
188
|
+
| `url` | `string \| null` |
|
|
189
|
+
| `status` | `'TENTATIVE' \| 'CONFIRMED' \| 'CANCELLED' \| string \| null` |
|
|
190
|
+
| `transp` | `'OPAQUE' \| 'TRANSPARENT' \| string \| null` |
|
|
191
|
+
| `klass` | `'PUBLIC' \| 'PRIVATE' \| 'CONFIDENTIAL' \| string \| null` |
|
|
192
|
+
| `priority` | `number \| null` — 0–9 |
|
|
193
|
+
| `sequence` | `number \| null` |
|
|
194
|
+
| `geo` | `ICalGeo \| null` |
|
|
195
|
+
| `organizer` | `string \| null` — calendar address URI |
|
|
196
|
+
|
|
197
|
+
#### Multi-value properties
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
event.attendees: Property[]
|
|
201
|
+
event.addAttendee(calAddress: string, params?: Record<string, string>): this
|
|
202
|
+
|
|
203
|
+
event.categories: ICalValue[]
|
|
204
|
+
event.categories = ['Work', 'Meeting']; // setter accepts string[]
|
|
205
|
+
|
|
206
|
+
event.comments: Property[]
|
|
207
|
+
event.addComment(v: string): this
|
|
208
|
+
|
|
209
|
+
event.contacts: Property[]
|
|
210
|
+
event.addContact(v: string): this
|
|
211
|
+
|
|
212
|
+
event.exdates: ICalValue[]
|
|
213
|
+
event.addExdate(v: ICalDateTime | ICalDate): this
|
|
214
|
+
|
|
215
|
+
event.rdates: ICalValue[]
|
|
216
|
+
event.addRdate(v: ICalDateTime | ICalDate): this
|
|
217
|
+
|
|
218
|
+
event.rrules: ICalRecur[]
|
|
219
|
+
event.addRrule(v: ICalRecur): this
|
|
220
|
+
|
|
221
|
+
event.alarms: Alarm[]
|
|
222
|
+
event.addAlarm(alarm: Alarm): this
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### Serialisation
|
|
226
|
+
|
|
227
|
+
Throws if `UID` or `DTSTAMP` is missing.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### Todo
|
|
232
|
+
|
|
233
|
+
Corresponds to `VTODO`.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const todo = new Todo();
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Same required properties as `Event` (`uid`, `dtstamp`). Date and time properties:
|
|
240
|
+
|
|
241
|
+
| Property | Type |
|
|
242
|
+
|----------|------|
|
|
243
|
+
| `dtstart` | `ICalDateTime \| ICalDate \| null` |
|
|
244
|
+
| `due` | `ICalDateTime \| ICalDate \| null` |
|
|
245
|
+
| `completed` | `ICalDateTime \| null` — only DateTime, not date-only |
|
|
246
|
+
| `duration` | `ICalDuration \| null` — mutually exclusive with `due` |
|
|
247
|
+
|
|
248
|
+
Additional descriptive properties: `summary`, `description`, `location`, `status` (`NEEDS-ACTION` / `COMPLETED` / `IN-PROCESS` / `CANCELLED`), `priority`, `percentComplete` (0–100), `sequence`, `klass`, `url`, `geo`, `organizer`, `created`, `lastModified`.
|
|
249
|
+
|
|
250
|
+
Multi-value: `attendees`, `categories`, `rrules`, `alarms` — same API as Event.
|
|
251
|
+
|
|
252
|
+
Serialisation throws if both `DUE` and `DURATION` are present.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### Journal
|
|
257
|
+
|
|
258
|
+
Corresponds to `VJOURNAL`.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const journal = new Journal();
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Required: `uid`, `dtstamp`. Date properties: `dtstart`. Multiple descriptions are supported:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
journal.descriptions: Property[]
|
|
268
|
+
journal.addDescription(v: string): this
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Other properties: `summary`, `status` (`DRAFT` / `FINAL` / `CANCELLED`), `klass`, `url`, `organizer`, `sequence`, `created`, `lastModified`. Multi-value: `attendees`, `categories`, `rrules`.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
### FreeBusy
|
|
276
|
+
|
|
277
|
+
Corresponds to `VFREEBUSY`.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const fb = new FreeBusy();
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Required: `uid`, `dtstamp`. Span: `dtstart`, `dtend` (both `ICalDateTime` only, not date-only). Participants: `organizer`, `attendees`, `addAttendee()`. Other: `url`, `comment`.
|
|
284
|
+
|
|
285
|
+
#### FREEBUSY periods
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
fb.freebusyProperties: Property[] // all FREEBUSY properties with their FBTYPE params
|
|
289
|
+
fb.periods: ICalPeriod[] // flat list of all period values
|
|
290
|
+
|
|
291
|
+
fb.addFreebusy(periods: ICalPeriod[], fbtype?: string): this
|
|
292
|
+
// fbtype defaults to 'BUSY'. Common values: 'FREE', 'BUSY', 'BUSY-UNAVAILABLE', 'BUSY-TENTATIVE'
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
### Timezone
|
|
298
|
+
|
|
299
|
+
Corresponds to `VTIMEZONE`.
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
const tz = new Timezone();
|
|
303
|
+
tz.tzid = 'America/New_York';
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Properties: `tzid` (required), `tzurl`, `lastModified`.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
tz.standardRules: TimezoneRule[]
|
|
310
|
+
tz.daylightRules: TimezoneRule[]
|
|
311
|
+
tz.addStandard(rule: Standard): this
|
|
312
|
+
tz.addDaylight(rule: Daylight): this
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### Standard and Daylight rules
|
|
316
|
+
|
|
317
|
+
Both `Standard` and `Daylight` extend `TimezoneRule`:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const std = new Standard();
|
|
321
|
+
std.dtstart = { type: 'date-time', year: 1970, month: 1, day: 1, hour: 2, minute: 0, second: 0, utc: false };
|
|
322
|
+
std.tzoffsetfrom = { sign: '+', hours: 5, minutes: 0 };
|
|
323
|
+
std.tzoffsetto = { sign: '-', hours: 5, minutes: 0 };
|
|
324
|
+
std.tzname = 'EST';
|
|
325
|
+
std.rrule = RECUR.parse('FREQ=YEARLY;BYDAY=1SU;BYMONTH=11');
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`TimezoneRule` properties: `dtstart`, `tzoffsetfrom`, `tzoffsetto`, `tzname`, `rrule`.
|
|
329
|
+
|
|
330
|
+
Serialisation throws if `TZID` is missing or if there are no `STANDARD`/`DAYLIGHT` sub-components.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
### Alarm
|
|
335
|
+
|
|
336
|
+
Corresponds to `VALARM`.
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
const alarm = new Alarm();
|
|
340
|
+
alarm.action = 'DISPLAY';
|
|
341
|
+
alarm.trigger = DURATION.parse('-PT15M'); // 15 minutes before
|
|
342
|
+
alarm.description = 'Reminder';
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Properties: `action` (required), `trigger` (required — `ICalDuration` or `ICalDateTime`), `description`, `summary`, `repeat`, `duration`. Multi-value: `attendees`, `addAttendee()`.
|
|
346
|
+
|
|
347
|
+
Serialisation throws if `ACTION` or `TRIGGER` is missing.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### Component (base class)
|
|
352
|
+
|
|
353
|
+
All component classes extend `Component`. The base class is available for advanced use and for working with unknown component types returned by the parser.
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// Property access
|
|
357
|
+
comp.getProperty(name: string): Property | undefined
|
|
358
|
+
comp.getProperties(name: string): Property[]
|
|
359
|
+
comp.getValue(name: string): ICalValue | null | undefined
|
|
360
|
+
comp.getValues(name: string): ICalValue[]
|
|
361
|
+
|
|
362
|
+
// Property mutation
|
|
363
|
+
comp.setProperty(name: string, value: ICalValue, params?: Record<string, string>): void
|
|
364
|
+
comp.appendProperty(name: string, value: ICalValue, params?: Record<string, string>): void
|
|
365
|
+
comp.addProperty(prop: Property): void
|
|
366
|
+
comp.removeProperty(name: string): void
|
|
367
|
+
|
|
368
|
+
// Sub-components
|
|
369
|
+
comp.addComponent(comp: Component): void
|
|
370
|
+
comp.getComponents(type: string): Component[]
|
|
371
|
+
|
|
372
|
+
comp.toString(): string
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
### Property
|
|
378
|
+
|
|
379
|
+
A parsed iCalendar property with name, value, and parameters.
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
class Property {
|
|
383
|
+
readonly name: string
|
|
384
|
+
readonly value: ICalValue | readonly ICalValue[]
|
|
385
|
+
readonly params: Readonly<Record<string, string | readonly string[]>>
|
|
386
|
+
readonly rawValue: string
|
|
387
|
+
|
|
388
|
+
get scalar: ICalValue | null // first value, or null if empty
|
|
389
|
+
get list: ICalValue[] // always an array
|
|
390
|
+
get text: string | null // scalar cast to string
|
|
391
|
+
get number: number | null // scalar cast to number
|
|
392
|
+
get boolean: boolean | null // scalar cast to boolean
|
|
393
|
+
|
|
394
|
+
toContentLine(): string // full RFC 5545 content line
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Factory function for advanced use:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
parseProperty(
|
|
402
|
+
name: string,
|
|
403
|
+
rawValue: string,
|
|
404
|
+
params: Readonly<Record<string, string | readonly string[]>>
|
|
405
|
+
): Property
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
### Value types
|
|
411
|
+
|
|
412
|
+
All structured iCalendar values are represented as plain objects with a discriminating `type` field. The full union type is:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
type ICalValue = string | number | boolean | ICalStructured
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
where `ICalStructured` is:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
type ICalStructured =
|
|
422
|
+
| ICalDate
|
|
423
|
+
| ICalDateTime
|
|
424
|
+
| ICalTime
|
|
425
|
+
| ICalDuration
|
|
426
|
+
| ICalPeriod
|
|
427
|
+
| ICalRecur
|
|
428
|
+
| ICalUtcOffset
|
|
429
|
+
| ICalGeo
|
|
430
|
+
| ICalBinary
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
#### ICalDate
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
interface ICalDate {
|
|
437
|
+
type: 'date';
|
|
438
|
+
readonly year: number;
|
|
439
|
+
readonly month: number; // 1-12
|
|
440
|
+
readonly day: number; // 1-31
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### ICalDateTime
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
interface ICalDateTime {
|
|
448
|
+
type: 'date-time';
|
|
449
|
+
readonly year: number;
|
|
450
|
+
readonly month: number;
|
|
451
|
+
readonly day: number;
|
|
452
|
+
readonly hour: number;
|
|
453
|
+
readonly minute: number;
|
|
454
|
+
readonly second: number;
|
|
455
|
+
readonly utc: boolean; // true if trailing 'Z'
|
|
456
|
+
readonly tzid?: string; // from TZID parameter
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### ICalTime
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
interface ICalTime {
|
|
464
|
+
type: 'time';
|
|
465
|
+
readonly hour: number;
|
|
466
|
+
readonly minute: number;
|
|
467
|
+
readonly second: number;
|
|
468
|
+
readonly utc: boolean;
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### ICalDuration
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
interface ICalDuration {
|
|
476
|
+
type: 'duration';
|
|
477
|
+
readonly negative: boolean;
|
|
478
|
+
readonly weeks?: number;
|
|
479
|
+
readonly days?: number;
|
|
480
|
+
readonly hours?: number;
|
|
481
|
+
readonly minutes?: number;
|
|
482
|
+
readonly seconds?: number;
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
#### ICalPeriod
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
interface ICalPeriod {
|
|
490
|
+
type: 'period';
|
|
491
|
+
readonly start: ICalDateTime;
|
|
492
|
+
readonly end: ICalDateTime | ICalDuration;
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### ICalRecur
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
type RecurFreq = 'SECONDLY' | 'MINUTELY' | 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY' | 'YEARLY';
|
|
500
|
+
type Weekday = 'SU' | 'MO' | 'TU' | 'WE' | 'TH' | 'FR' | 'SA';
|
|
501
|
+
|
|
502
|
+
interface ByDayRule {
|
|
503
|
+
readonly day: Weekday;
|
|
504
|
+
readonly ordwk?: number; // e.g. 2MO means the second Monday
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
interface ICalRecur {
|
|
508
|
+
type: 'recur';
|
|
509
|
+
readonly freq: RecurFreq;
|
|
510
|
+
readonly until?: ICalDateTime | ICalDate;
|
|
511
|
+
readonly count?: number;
|
|
512
|
+
readonly interval?: number;
|
|
513
|
+
readonly bysecond?: readonly number[];
|
|
514
|
+
readonly byminute?: readonly number[];
|
|
515
|
+
readonly byhour?: readonly number[];
|
|
516
|
+
readonly byday?: readonly ByDayRule[];
|
|
517
|
+
readonly bymonthday?: readonly number[];
|
|
518
|
+
readonly byyearday?: readonly number[];
|
|
519
|
+
readonly byweekno?: readonly number[];
|
|
520
|
+
readonly bymonth?: readonly number[];
|
|
521
|
+
readonly bysetpos?: readonly number[];
|
|
522
|
+
readonly wkst?: Weekday;
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### ICalUtcOffset
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
interface ICalUtcOffset {
|
|
530
|
+
type: 'utc-offset';
|
|
531
|
+
readonly sign: '+' | '-';
|
|
532
|
+
readonly hours: number;
|
|
533
|
+
readonly minutes: number;
|
|
534
|
+
readonly seconds?: number;
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
#### ICalGeo
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
interface ICalGeo {
|
|
542
|
+
type: 'geo';
|
|
543
|
+
readonly latitude: number;
|
|
544
|
+
readonly longitude: number;
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### ICalBinary
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
interface ICalBinary {
|
|
552
|
+
type: 'binary';
|
|
553
|
+
readonly data: Uint8Array;
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
### Value codecs
|
|
560
|
+
|
|
561
|
+
Each RFC 5545 value type has a codec with `parse` and `serialize` methods. Codecs are available as named exports and via the `CODECS` lookup map.
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
import { TEXT, BOOLEAN, INTEGER, FLOAT,
|
|
565
|
+
URI, CAL_ADDRESS, BINARY,
|
|
566
|
+
UTC_OFFSET, DATE, DATE_TIME, TIME,
|
|
567
|
+
DURATION, PERIOD, RECUR, GEO, CODECS } from '@pipobscure/ical';
|
|
568
|
+
|
|
569
|
+
// Direct codec use
|
|
570
|
+
const dt = DATE_TIME.parse('20250101T100000Z');
|
|
571
|
+
const str = DATE_TIME.serialize(dt); // '20250101T100000Z'
|
|
572
|
+
|
|
573
|
+
const dur = DURATION.parse('-PT1H30M');
|
|
574
|
+
const secs = DURATION.toSeconds(dur); // -5400
|
|
575
|
+
|
|
576
|
+
const recur = RECUR.parse('FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10');
|
|
577
|
+
const rule = RECUR.serialize(recur);
|
|
578
|
+
|
|
579
|
+
// Codec lookup by RFC 5545 value type name
|
|
580
|
+
const codec = CODECS['DATE-TIME'];
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
#### Helper methods on DATE and DATE_TIME
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
DATE.fromDate(d: Date): ICalDate
|
|
587
|
+
DATE.toDate(val: ICalDate): Date
|
|
588
|
+
|
|
589
|
+
DATE_TIME.fromDate(d: Date, tzid?: string): ICalDateTime
|
|
590
|
+
DATE_TIME.toDate(val: ICalDateTime): Date
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### Helper method on DURATION
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
DURATION.toSeconds(val: ICalDuration): number
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## Line folding
|
|
602
|
+
|
|
603
|
+
The serialiser folds long lines at 75 UTF-8 octets as required by RFC 5545. Folded continuation lines begin with a single space. Multi-byte UTF-8 characters and surrogate pairs are handled correctly — the fold never splits a multi-byte sequence.
|
|
604
|
+
|
|
605
|
+
When parsing, both space and tab continuation characters are accepted, and the continuation character is stripped from the unfolded value.
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## TEXT escaping
|
|
610
|
+
|
|
611
|
+
The TEXT codec automatically escapes and unescapes the characters required by RFC 5545:
|
|
612
|
+
|
|
613
|
+
| Character | Escaped form |
|
|
614
|
+
|-----------|-------------|
|
|
615
|
+
| `\` (backslash) | `\\` |
|
|
616
|
+
| `;` (semicolon) | `\;` |
|
|
617
|
+
| `,` (comma) | `\,` |
|
|
618
|
+
| newline | `\n` |
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## Error handling
|
|
623
|
+
|
|
624
|
+
The parser never throws on malformed input. Unknown properties and components are silently stored. Structural errors are represented as generic `Component` objects.
|
|
625
|
+
|
|
626
|
+
The serialiser throws `Error` when required properties are absent:
|
|
627
|
+
|
|
628
|
+
| Component | Required properties |
|
|
629
|
+
|-----------|-------------------|
|
|
630
|
+
| `Calendar` | `PRODID`, `VERSION` |
|
|
631
|
+
| `Event` | `UID`, `DTSTAMP` |
|
|
632
|
+
| `Todo` | `UID`, `DTSTAMP`; `DUE` and `DURATION` are mutually exclusive |
|
|
633
|
+
| `Journal` | `UID`, `DTSTAMP` |
|
|
634
|
+
| `FreeBusy` | `UID`, `DTSTAMP` |
|
|
635
|
+
| `Timezone` | `TZID`; at least one `STANDARD` or `DAYLIGHT` rule |
|
|
636
|
+
| `Alarm` | `ACTION`, `TRIGGER` |
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## Vendor compatibility
|
|
641
|
+
|
|
642
|
+
The parser has been tested against real-world ICS files produced by:
|
|
643
|
+
|
|
644
|
+
- **Apple Calendar** — `X-WR-CALNAME`, `X-APPLE-STRUCTURED-LOCATION`, `X-APPLE-TRAVEL-ADVISORY`, date-only `VALUE=DATE` events
|
|
645
|
+
- **Google Calendar** — TZID parameters on RRULE/EXDATE, multiple RRULE properties, bare LF line endings
|
|
646
|
+
- **Microsoft Outlook** — `METHOD:REQUEST`, missing `DTSTAMP`, `X-MICROSOFT-*` properties, `ENCODING=QUOTED-PRINTABLE` parameter
|
|
647
|
+
- **Mozilla Thunderbird** — Folded `DESCRIPTION` lines, long UID strings, `X-MOZ-SNOOZE-TIME`, `X-MOZ-GENERATION`
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## TypeScript
|
|
652
|
+
|
|
653
|
+
The package ships TypeScript declaration files alongside the compiled JavaScript. No `@types/` package is needed.
|
|
654
|
+
|
|
655
|
+
All exported types are pure interfaces and type aliases — there are no classes in the type exports, only in the value exports.
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## License
|
|
660
|
+
|
|
661
|
+
MIT
|
package/dist/alarm.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VALARM component (RFC 5545 §3.6.6)
|
|
3
|
+
*
|
|
4
|
+
* ACTION is required. TRIGGER is required.
|
|
5
|
+
* Three alarm types: AUDIO, DISPLAY, EMAIL.
|
|
6
|
+
*/
|
|
7
|
+
import { Component } from './component.js';
|
|
8
|
+
import { Property } from './property.js';
|
|
9
|
+
import type { ICalDuration, ICalDateTime } from './types.js';
|
|
10
|
+
export type AlarmAction = 'AUDIO' | 'DISPLAY' | 'EMAIL' | string;
|
|
11
|
+
export declare class Alarm extends Component {
|
|
12
|
+
constructor();
|
|
13
|
+
get action(): AlarmAction | null;
|
|
14
|
+
set action(v: AlarmAction);
|
|
15
|
+
/** TRIGGER: relative (DURATION) or absolute (DATE-TIME). */
|
|
16
|
+
get trigger(): ICalDuration | ICalDateTime | null;
|
|
17
|
+
set trigger(v: ICalDuration | ICalDateTime);
|
|
18
|
+
get description(): string | null;
|
|
19
|
+
set description(v: string);
|
|
20
|
+
get summary(): string | null;
|
|
21
|
+
set summary(v: string);
|
|
22
|
+
get repeat(): number | null;
|
|
23
|
+
set repeat(v: number);
|
|
24
|
+
get duration(): ICalDuration | null;
|
|
25
|
+
set duration(v: ICalDuration);
|
|
26
|
+
get attendees(): Property[];
|
|
27
|
+
addAttendee(calAddress: string, params?: Record<string, string>): this;
|
|
28
|
+
toString(): string;
|
|
29
|
+
static fromRaw(props: ReadonlyArray<{
|
|
30
|
+
name: string;
|
|
31
|
+
params: Record<string, string>;
|
|
32
|
+
value: string;
|
|
33
|
+
}>): Alarm;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=alarm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alarm.d.ts","sourceRoot":"","sources":["../src/alarm.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAiB,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjE,qBAAa,KAAM,SAAQ,SAAS;;IAOlC,IAAI,MAAM,IAAI,WAAW,GAAG,IAAI,CAE/B;IACD,IAAI,MAAM,CAAC,CAAC,EAAE,WAAW,EAExB;IAED,4DAA4D;IAC5D,IAAI,OAAO,IAAI,YAAY,GAAG,YAAY,GAAG,IAAI,CAQhD;IACD,IAAI,OAAO,CAAC,CAAC,EAAE,YAAY,GAAG,YAAY,EAIzC;IAID,IAAI,WAAW,IAAI,MAAM,GAAG,IAAI,CAE/B;IACD,IAAI,WAAW,CAAC,CAAC,EAAE,MAAM,EAExB;IAED,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAE3B;IACD,IAAI,OAAO,CAAC,CAAC,EAAE,MAAM,EAEpB;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAE1B;IACD,IAAI,MAAM,CAAC,CAAC,EAAE,MAAM,EAEnB;IAED,IAAI,QAAQ,IAAI,YAAY,GAAG,IAAI,CAKlC;IACD,IAAI,QAAQ,CAAC,CAAC,EAAE,YAAY,EAE3B;IAED,IAAI,SAAS,IAAI,QAAQ,EAAE,CAE1B;IAED,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,IAAI;IAOjE,QAAQ,IAAI,MAAM;IAQ3B,MAAM,CAAC,OAAO,CACZ,KAAK,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GACpF,KAAK;CAOT"}
|