Add a pure parser parserEvenements(flux, aujourdhui) backed by vendored
ical.js v2.2.1 (ESM, no build). Returns events at the frozen Event contract
{ uid, titre, description, lieu, debut, fin, categories, journeeEntiere }:
- register embedded VTIMEZONE before toJSDate (correct offset, e.g. Africa/Lagos)
- dedupe by UID, keep the master (RECURRENCE-ID exceptions ignored)
- filter to today and future (local start of day)
- coerce missing text fields to ""
Edge cases deferred to T3 (RRULE expansion, all-day hour) are documented.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
63 lines
2.1 KiB
JavaScript
63 lines
2.1 KiB
JavaScript
// Parseur iCalendar : flux brut → Event[] au contrat figé (T1).
|
|
// Fonction pure. `aujourdhui` est injecté (obligatoire) pour des tests
|
|
// déterministes. Voir docs/DECISIONS.md pour le contrat et les cas différés à T3.
|
|
//
|
|
// Event = {
|
|
// uid: string, titre: string, description: string, lieu: string,
|
|
// debut: Date, fin: Date, categories: string[], journeeEntiere: boolean
|
|
// }
|
|
|
|
import ICAL from "./vendor/ical.js";
|
|
|
|
export function parserEvenements(flux, aujourdhui) {
|
|
const composant = new ICAL.Component(ICAL.parse(flux));
|
|
|
|
// Enregistrer les fuseaux embarqués : sans cela toJSDate() calcule un
|
|
// mauvais offset pour les TZID non standards (ex. Africa/Lagos).
|
|
composant.getAllSubcomponents("vtimezone").forEach((vt) => {
|
|
const tzid = vt.getFirstPropertyValue("tzid");
|
|
if (tzid && !ICAL.TimezoneService.has(tzid)) {
|
|
ICAL.TimezoneService.register(vt);
|
|
}
|
|
});
|
|
|
|
// Dédoublonnage par UID. Les exceptions d'occurrence (RECURRENCE-ID) ne sont
|
|
// pas développées en T1 : on les ignore et on garde le maître.
|
|
const parUid = new Map();
|
|
for (const vevent of composant.getAllSubcomponents("vevent")) {
|
|
const ev = new ICAL.Event(vevent);
|
|
if (ev.isRecurrenceException()) continue;
|
|
if (parUid.has(ev.uid)) continue;
|
|
parUid.set(ev.uid, toEvent(ev, vevent));
|
|
}
|
|
|
|
const seuil = new Date(aujourdhui);
|
|
seuil.setHours(0, 0, 0, 0);
|
|
|
|
// Ordre du flux conservé (tri différé à la liste).
|
|
return [...parUid.values()].filter(
|
|
(event) => event.debut.getTime() >= seuil.getTime(),
|
|
);
|
|
}
|
|
|
|
function toEvent(ev, vevent) {
|
|
return {
|
|
uid: ev.uid,
|
|
titre: ev.summary ?? "",
|
|
description: ev.description ?? "",
|
|
lieu: ev.location ?? "",
|
|
debut: ev.startDate.toJSDate(),
|
|
fin: ev.endDate.toJSDate(),
|
|
categories: lireCategories(vevent),
|
|
journeeEntiere: ev.startDate.isDate === true,
|
|
};
|
|
}
|
|
|
|
// CATEGORIES peut prendre deux formes (valeurs séparées par virgule et/ou
|
|
// propriétés répétées) : getAllProperties + getValues couvre les deux.
|
|
function lireCategories(vevent) {
|
|
return vevent
|
|
.getAllProperties("categories")
|
|
.flatMap((prop) => prop.getValues());
|
|
}
|