From 6abc243e5d40bd60174a3fb1affc9517b0ac5617 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 19 Aug 2026 09:09:36 +0200 Subject: feat(render): iCalendar emitter, RFC 5545 Not a template job: folding, escaping, exclusive DTEND and stable UIDs are rules a logic-less template cannot enforce, and each fails silently in a subscriber's client rather than loudly at generation. DTEND is EXCLUSIVE for an all-day event (section 3.6.1). Wrong here shows every event a day short, everywhere. UIDs are YYYYMMDD-@colitur and stable across regenerations (section 3.8.4.7). Wrong here duplicates the whole year in every subscriber's phone, months later. Every line is CRLF-terminated and folded at 75 octets (section 3.1). No RRULE: a liturgical calendar is not a recurrence rule. Asserted, so nobody optimises it later. DTSTAMP is a parameter, not a clock read. RFC 5545 requires it and the obvious implementation reads the wall clock -- which violates the kernel's determinism rule and would make two feeds from identical data differ byte-for-byte, defeating reproducible builds and any reviewable diff on a published tree. Corrected one test literal against real engine output: DTSTAMP is a per-VEVENT property (section 3.8.7.2), not calendar-level, so the default-value line count is 365 (every event), not 1. Mutation-tested: a non-exclusive DTEND reddens the suite. --- lib/render/emit_ics.mli | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/render/emit_ics.mli (limited to 'lib/render/emit_ics.mli') diff --git a/lib/render/emit_ics.mli b/lib/render/emit_ics.mli new file mode 100644 index 0000000..270bd76 --- /dev/null +++ b/lib/render/emit_ics.mli @@ -0,0 +1,15 @@ +(* emit_ics.mli *) +(** RFC 5545 iCalendar. NOT a template job: folding, escaping, exclusive DTEND + and stable UIDs are rules a logic-less template cannot enforce, and getting + any of them wrong produces a feed that fails silently in a subscriber's + client (spec section 6). *) + +(** [year ?dtstamp ?calname view]. + + [dtstamp] defaults to [YYYY0101T000000Z] for the view's own year. It is a + PARAMETER, never a clock read: RFC 5545 requires DTSTAMP, the obvious + implementation reads the wall clock, and that would both violate the + kernel's determinism rule and make two feeds from identical data differ + byte-for-byte -- defeating reproducible builds and a reviewable git diff on + a published tree. Same data in, same bytes out. *) +val year : ?dtstamp:string -> ?calname:string -> Template.value -> string -- cgit v1.3