Add the iCalendar round-trip
Parsing goes through icalendar's low-level parser, which keeps properties in order and keeps repeated ones. Writing is ours: that crate's writer escapes a whole property value as text, so CATEGORIES:Work,Personal would go out as one category named "Work,Personal" to every other client. Anything the model does not interpret is carried rather than dropped -- X-MOZ-LASTACK, X-EVOLUTION-ALARM-UID, ACKNOWLEDGED, the X-MICROSOFT-CDO set, unrecognised ATTENDEE parameters, and whole VTODO/VJOURNAL components. A calendar has several clients writing to it and this one is not the authority on which properties matter. VTIMEZONE is modelled properly, and TZID is stored exactly as written: Exchange names its zones "Pacific Standard Time", which no IANA lookup resolves, and normalising at parse time would make the document unrepresentable. Mapping to a real zone belongs at the point of use. Tested against a golden corpus captured from the live Baikal (seven producing clients over five years) and a published Outlook feed, scrubbed of private content with the structure left byte-for-byte. Eight hand-written fixtures cover what neither server had: DURATION, floating times, RDATE, DST boundaries, leap day, and the full escape set. The contract is that parse -> write -> parse is stable, plus a check that no property name loses occurrences across the trip, since a parser that dropped ATTENDEE entirely would round-trip perfectly and still be wrong.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:allday-exdate@fixtures.runway.invalid
|
||||
DTSTAMP:20260101T000000Z
|
||||
DTSTART;VALUE=DATE:20260105
|
||||
DTEND;VALUE=DATE:20260106
|
||||
SUMMARY:Bin day
|
||||
RRULE:FREQ=WEEKLY;BYDAY=MO
|
||||
EXDATE;VALUE=DATE:20260223,20260302
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
@@ -0,0 +1,12 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:dst-spring-forward@fixtures.runway.invalid
|
||||
DTSTAMP:20260301T090000Z
|
||||
DTSTART;TZID=America/Denver:20260302T090000
|
||||
DTEND;TZID=America/Denver:20260302T093000
|
||||
SUMMARY:Weekly across spring forward
|
||||
RRULE:FREQ=WEEKLY;BYDAY=MO;COUNT=4
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
@@ -0,0 +1,11 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:duration-not-dtend@fixtures.runway.invalid
|
||||
DTSTAMP:20260114T090000Z
|
||||
DTSTART;TZID=America/Denver:20260114T090000
|
||||
DURATION:PT1H30M
|
||||
SUMMARY:Ninety minutes expressed as a duration
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
@@ -0,0 +1,11 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:floating@fixtures.runway.invalid
|
||||
DTSTAMP:20260114T090000Z
|
||||
DTSTART:20260114T090000
|
||||
DTEND:20260114T100000
|
||||
SUMMARY:Nine in the morning\, wherever you are
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
@@ -0,0 +1,12 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:leap-day@fixtures.runway.invalid
|
||||
DTSTAMP:20240229T000000Z
|
||||
DTSTART;VALUE=DATE:20240229
|
||||
DTEND;VALUE=DATE:20240301
|
||||
SUMMARY:Leap day
|
||||
RRULE:FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=29
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
@@ -0,0 +1,14 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:rdate-exdate@fixtures.runway.invalid
|
||||
DTSTAMP:20260105T090000Z
|
||||
DTSTART;TZID=America/Denver:20260105T090000
|
||||
DTEND;TZID=America/Denver:20260105T093000
|
||||
SUMMARY:Weekly with an extra date and two skipped
|
||||
RRULE:FREQ=WEEKLY;BYDAY=MO;COUNT=8
|
||||
RDATE;TZID=America/Denver:20260110T090000
|
||||
EXDATE;TZID=America/Denver:20260119T090000,20260202T090000
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
@@ -0,0 +1,22 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:series-override@fixtures.runway.invalid
|
||||
DTSTAMP:20260105T090000Z
|
||||
DTSTART;TZID=America/Denver:20260105T090000
|
||||
DTEND;TZID=America/Denver:20260105T100000
|
||||
SUMMARY:Standup
|
||||
RRULE:FREQ=WEEKLY;BYDAY=MO
|
||||
SEQUENCE:0
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:series-override@fixtures.runway.invalid
|
||||
RECURRENCE-ID;TZID=America/Denver:20260119T090000
|
||||
DTSTAMP:20260112T142200Z
|
||||
DTSTART;TZID=America/Denver:20260119T140000
|
||||
DTEND;TZID=America/Denver:20260119T150000
|
||||
SUMMARY:Standup (moved to the afternoon)
|
||||
SEQUENCE:1
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
@@ -0,0 +1,16 @@
|
||||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Runway//Runway Test Fixture//EN
|
||||
BEGIN:VEVENT
|
||||
UID:escapes@fixtures.runway.invalid
|
||||
DTSTAMP:20260114T090000Z
|
||||
DTSTART;TZID=Europe/Zurich:20260114T090000
|
||||
DTEND;TZID=Europe/Zurich:20260114T100000
|
||||
SUMMARY:Lezione d'italiano — caffè\, cioccolato e “virgolette”
|
||||
DESCRIPTION:Line one\nLine two\; with a semicolon\nA backslash: \\\nA comm
|
||||
a\, and a colon: here
|
||||
LOCATION;LANGUAGE=it-CH:Zürich\, Hauptbahnhof
|
||||
ORGANIZER;CN="Rossi, Giulia (IT: Milano)":mailto:giulia.rossi@example.org
|
||||
CATEGORIES:Lezioni,Personale
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
Reference in New Issue
Block a user