-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathREADME
More file actions
233 lines (178 loc) · 8.55 KB
/
Copy pathREADME
File metadata and controls
233 lines (178 loc) · 8.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
Text::JSCalendar - Convert between iCalendar and JSCalendar
DESCRIPTION
Text::JSCalendar provides bidirectional conversion between iCalendar
(RFC 5545) and JSCalendar, following the latest drafts:
- draft-ietf-calext-jscalendar-icalendar (mapping specification)
- draft-ietf-calext-jscalendarbis (JSCalendar data model)
- draft-ietf-calext-icalendar-jscalendar-extensions (new iCal properties)
METHODS
$jscal = Text::JSCalendar->new()
Create a converter instance. Caches timezone objects for performance.
@events = $jscal->vcalendarToEvents($ical_string)
Parse an iCalendar string (VCALENDAR with VEVENT or VTODO components)
and return a list of JSCalendar Event or Task hashrefs.
$ical_string = $jscal->eventsToVCalendar(@events)
Convert one or more JSCalendar Event/Task hashrefs to an iCalendar
string with VTIMEZONE components automatically included.
$normalized = Text::JSCalendar->NormaliseEvent($event)
Return a copy of the event with default-valued properties removed.
Useful for comparison and storage.
$bool = Text::JSCalendar->CompareEvents($event1, $event2)
Compare two events after normalization. Returns true if identical.
CONVERSION DESIGN
The module converts between two representations of the same data:
iCalendar (RFC 5545) JSCalendar (draft-jscalendarbis)
======================== ==============================
VEVENT <=> Event (@type: "Event")
VTODO <=> Task (@type: "Task")
DTSTART + DTEND/DURATION <=> start + duration + timeZone
RRULE <=> recurrenceRule
EXDATE / RDATE <=> recurrenceOverrides
RECURRENCE-ID <=> recurrenceOverrides (patches)
ATTENDEE / ORGANIZER <=> participants + replyTo
VALARM <=> alerts
ATTACH / URL / IMAGE <=> links
CONFERENCE <=> virtualLocations
CATEGORIES <=> keywords
RELATED-TO <=> relatedTo
Property Mapping
iCalendar Property JSCalendar Property
------------------------- ----------------------------
UID uid
SUMMARY title
DESCRIPTION description
STYLED-DESCRIPTION description + descriptionContentType
DTSTART start + timeZone
DTEND (computed as duration)
DURATION duration
DTSTART TZID (end differs) endTimeZone
STATUS status
TRANSP freeBusyStatus ("free"/"busy")
CLASS privacy ("private"/"confidential"/"public")
PRIORITY priority (0-9)
COLOR color
LOCATION locations[].name
GEO locations[].coordinates
CONFERENCE virtualLocations[].uri + features
SHOW-WITHOUT-TIME showWithoutTime
CREATED created
DTSTAMP / LAST-MODIFIED updated / lastModified
SEQUENCE sequence
PRODID prodId
ORGANIZER organizerCalendarAddress + participant
ATTENDEE participants[]
VALARM alerts[]
ATTACH links[] (rel: "enclosure")
URL links[]
IMAGE links[] (rel: "icon")
CATEGORIES keywords
RELATED-TO relatedTo
RRULE recurrenceRule
EXDATE recurrenceOverrides (excluded: true)
RDATE recurrenceOverrides (empty patch)
RECURRENCE-ID recurrenceOverrides (computed patch)
Task-specific (VTODO):
DUE due
PERCENT-COMPLETE percentComplete
ESTIMATED-DURATION estimatedDuration
COMPLETED / STATUS progress
Participant Mapping
ATTENDEE Parameter JSCalendar Property
------------------------- ----------------------------
CN name
EMAIL email
CUTYPE kind
PARTSTAT participationStatus
ROLE=CHAIR roles: [chair]
ROLE=OWNER roles: [owner]
ROLE=OPT-PARTICIPANT attendance: optional
ROLE=NON-PARTICIPANT attendance: none
RSVP expectReply
SCHEDULE-AGENT scheduleAgent
DELEGATED-FROM delegatedFrom
DELEGATED-TO delegatedTo
X-SEQUENCE scheduleSequence
X-DTSTAMP scheduleUpdated
APPLE ICALENDAR EXTENSIONS
Apple's Calendar app (macOS/iOS) uses several proprietary iCalendar
extensions. There is no formal Apple specification for these; our
implementation is based on reverse-engineering and community sources:
- X-APPLE-STRUCTURED-LOCATION format examples
https://github.com/collective/icalendar/issues/116
https://github.com/icalendar/icalendar/issues/108
- Apple iCalendar property analysis
https://spaceraccoon.dev/exploiting-icalendar-properties-enterprise-applications/
- iCalendar non-standard properties overview
https://en.wikipedia.org/wiki/ICalendar
- RFC 9073 (standardized alternative to Apple structured data)
https://www.rfc-editor.org/rfc/rfc9073.html
Reading Apple Extensions
X-APPLE-STRUCTURED-LOCATION
Parsed into locations[] with:
- geo: URI value -> coordinates
- X-TITLE parameter -> name
- X-ADDRESS parameter -> description
When both X-APPLE-STRUCTURED-LOCATION and GEO are present on the
same event, the Apple property takes precedence (it is a superset
of GEO with additional metadata).
X-APPLE-DEFAULT-ALARM
When a VALARM has X-APPLE-DEFAULT-ALARM:TRUE, the event's
useDefaultAlerts is set to true.
X-WR-ALARMUID
Used as a fallback alarm identifier when the standard UID property
is missing from a VALARM.
Writing Apple Extensions
When generating iCalendar output:
- Locations with coordinates AND a name or description are written as
X-APPLE-STRUCTURED-LOCATION (with X-TITLE and X-ADDRESS parameters)
instead of plain GEO. This ensures Apple Calendar displays the
location on a map with the correct title.
- Locations with coordinates but no additional metadata are written
as standard GEO properties.
- This approach ensures that:
1. Apple Calendar gets rich location data it can display
2. Other clients get standard GEO they can parse
3. Round-trips through our converter are stable
Properties we do NOT generate (low interop value):
- X-APPLE-TRAVEL-DURATION (no JSCalendar equivalent)
- X-APPLE-TRAVEL-ADVISORY-BEHAVIOR (no JSCalendar equivalent)
- X-APPLE-MAPKIT-HANDLE (binary plist, opaque)
TIMEZONE HANDLING
The module includes Text::JSCalendar::TimeZones which contains
auto-generated VTIMEZONE data for all IANA timezone identifiers.
Timezone lookup uses fuzzy matching (Text::LevenshteinXS) to handle
non-standard TZID values from various calendar implementations.
When generating iCalendar output, VTIMEZONE components are
automatically included for all timezones referenced by events.
TESTING
t/api.t - Gold-file comparison tests (parse + roundtrip)
t/icalfiles.t - Roundtrip normalization tests
t/cyrus-caldav.t - Live Cyrus CalDAV/JMAP integration tests
(set CYRUS_URL, CYRUS_USER, CYRUS_PASS to enable)
The Cyrus integration tests verify:
- CalDAV PUT/GET roundtrip for events with various properties
- End timezone handling
- GEO coordinate preservation
- CONFERENCE/virtualLocations
- VTODO/Task support
- JMAP CalendarEvent creation and comparison with our conversion
SPECIFICATIONS
RFC 5545 - Internet Calendaring (iCalendar)
RFC 7986 - New Properties for iCalendar
RFC 9073 - Event Publishing Extensions to iCalendar
draft-ietf-calext-jscalendarbis
JSCalendar: A JSON Representation of Calendar Data
draft-ietf-calext-jscalendar-icalendar
JSCalendar: Converting from and to iCalendar
draft-ietf-calext-icalendar-jscalendar-extensions
iCalendar Format Extensions for JSCalendar
AUTHOR
Bron Gondwana <brong@cpan.org>
LICENSE
Copyright 2019-2026 Fastmail Pty Ltd.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself; that is, either the GNU General
Public License as published by the Free Software Foundation (version 1,
or at your option any later version), or the Artistic License.
See http://dev.perl.org/licenses/ and the LICENSE file included with
this distribution for more information.