feat(recurring): implement RECURRENCE-ID override handling

- Add `overrides` field to `rem` type to hold single-event REMs from
  non-cancelled overrides
- Add `is_cancelled`, `build_override_rem`, and `collect_overrides` to
  process RECURRENCE-ID override events
- Replace `warn_unhandled_recurring` with `collect_overrides` in the
  collector pipeline
- Fix `separate_master_and_recurrence` partition logic (swapped
  `Left`/`Right`)
- Render override REMs appended to the master REM in `string_of_rem`
This commit is contained in:
2026-05-17 20:04:18 +02:00
parent a5e15fa84f
commit 106aff01bf
3 changed files with 66 additions and 18 deletions

View File

@@ -90,7 +90,7 @@ open Utils
snippet: 'REM Mon AT 09:00 FROM 2025-09-01 UNTIL 2025-10-31 MSG Standup\nOMIT 2025-10-13'
priorita: Subito
- id: P11
- id: P11
pattern: Override/cancellazioni per istanza
ics: "RECURRENCE-ID con contenuto modificato o STATUS:CANCELLED"
remind_support: espansione
@@ -326,11 +326,53 @@ RRULE: (`Weekly, (Some `Until (`Utc (2026-07-01 09:00:00 +00:00))), None, [])
| Some (_, recurs) -> debug_print_of_recurrence_and_skip ev recurs
| None -> Ok rem
let warn_unhandled_recurring rem ev : (Remind.rem, error) result =
if List.length rem.Remind.recurring > 0 then
Printf.eprintf "Warning: RECURRENCE-ID overrides present but not handled (master emitted as-is)\t\t\tUID: %s\n"
(Utils.get_uid ev);
Ok rem
let is_cancelled (ev : Icalendar.event) : bool =
List.exists
(function
| `Status (_, `Cancelled) -> true
| _ -> false)
ev.props
let build_override_rem (source : string) (override_ev : Icalendar.event) : (Remind.rem, error) result =
let rem = { Remind.empty with Remind.source } in
let collectors = [ collect_uuid; collect_summary; collect_start_end_duration ] in
ListLabels.fold_left ~init:(Ok rem) collectors ~f:(fun rem_or_error pred ->
match rem_or_error with
| Error e -> Error e
| Ok rem -> pred rem override_ev)
let collect_overrides rem _ev : (Remind.rem, error) result =
(* Process each RECURRENCE-ID override event stored in rem.recurring:
- add its RECURRENCE-ID date to rem.exdate (feeds the OMIT mechanism)
- for non-cancelled overrides, build a single REM and add to rem.overrides *)
let new_exdates, new_overrides =
ListLabels.fold_left ~init:([], []) rem.Remind.recurring ~f:(fun (exdates, overrides) override_ev ->
let recur_id_opt = Utils.get_recurrence_id override_ev in
let exdates =
match recur_id_opt with
| None ->
Printf.eprintf "Warning: override event has no RECURRENCE-ID\t\t\tUID: %s\n" (Utils.get_uid override_ev);
exdates
| Some date_or_dt -> date_or_dt :: exdates
in
let overrides =
if is_cancelled override_ev then overrides
else
match build_override_rem rem.Remind.source override_ev with
| Error _ ->
Printf.eprintf "Warning: could not build override REM\t\t\tUID: %s\n" (Utils.get_uid override_ev);
overrides
| Ok override_rem -> override_rem :: overrides
in
(exdates, overrides))
in
Ok
{
rem with
Remind.exdate = rem.Remind.exdate @ List.rev new_exdates;
Remind.overrides = List.rev new_overrides;
Remind.recurring = [];
}
let all_collectors : collector list =
[
@@ -338,9 +380,9 @@ let all_collectors : collector list =
collect_summary;
collect_start_end_duration;
collect_exdates;
collect_overrides;
yearly_simple_date;
simple_recurrence;
warn_unhandled_recurring;
]
let remind_of_event (source : string) (ev : Icalendar.event list) : (Remind.rem, error) result =