open Remind_sync open Icalendar open Utils (* CASE ANALYSIS PREDICATES - id: P00 pattern: Ha un SUMMARY? ics: "SUMMARY:…" remind_support: nativo strategia: "REM MSG " snippet: 'REM 2025-12-25 MSG Natale' priorita: Subito - id: P01 pattern: All-day singolo ics: "DTSTART;VALUE=DATE, opzionale DTEND=giorno+1" remind_support: nativo strategia: "REM MSG " snippet: 'REM 2025-12-25 MSG Natale' priorita: Subito - id: P02 pattern: All-day multi-giorno ics: "DTSTART;VALUE=DATE + DTEND;VALUE=DATE esclusivo" remind_support: nativo+accorgimenti strategia: "espandi in eventi giornalieri; stesso SUMMARY" snippet: 'REM 2025-08-10 THROUGH 2025-08-15 MSG Ferie # oppure espansione per-giorno' priorita: Subito - id: P03 pattern: Evento a orario locale ics: "DTSTART;TZID=… + DTEND oppure DURATION" remind_support: nativo strategia: "REM AT [DURATION] MSG …" snippet: 'REM 2025-10-05 AT 09:00 DURATION 1:00 MSG Riunione' priorita: Subito - id: P04 pattern: Evento a orario in UTC ics: "DTSTART/DTEND con suffisso Z" remind_support: nativo+accorgimenti strategia: "converti a fuso locale prima di emettere AT" snippet: 'REM 2025-09-03 AT 06:45 MSG Treno' priorita: Subito - id: P05 pattern: Ricorrenza settimanale semplice ics: "RRULE:FREQ=WEEKLY;BYDAY=…;[UNTIL|COUNT]" remind_support: nativo strategia: "REM FROM [UNTIL ] AT MSG …" snippet: 'REM Mon Wed FROM 2025-09-01 UNTIL 2025-10-31 AT 09:00 MSG Standup' priorita: Subito - id: P06 pattern: Ricorrenza giornaliera semplice ics: "RRULE:FREQ=DAILY;[UNTIL|COUNT]" remind_support: nativo strategia: "REM FROM UNTIL AT EVERY 1 MSG …" snippet: 'REM FROM 2025-10-01 UNTIL 2025-10-10 AT 08:30 MSG Daily' priorita: Subito - id: P07 pattern: Ricorrenza mensile per giorno fisso ics: "RRULE:FREQ=MONTHLY;BYMONTHDAY=…" remind_support: nativo strategia: "REM AT FROM/UNTIL" snippet: 'REM 15 AT 10:00 FROM 2025-01-01 MSG Fatture' priorita: Dopo - id: P08 pattern: Ricorrenza “n-esimo weekday” del mese ics: "RRULE:FREQ=MONTHLY;BYDAY=MO;BYSETPOS=3" remind_support: espansione strategia: "materializza occorrenze in singoli REM o calcola in codice" snippet: '# genera REM per ciascuna data calcolata' priorita: Dopo - id: P09 pattern: Ricorrenza annuale semplice ics: "RRULE:FREQ=YEARLY;[BYMONTH][BYMONTHDAY]" remind_support: nativo strategia: "REM MSG …" snippet: 'REM Jul 29 MSG Compleanno' priorita: Dopo - id: P10 pattern: Eccezioni ics: "EXDATE (una o più), RDATE aggiuntive" remind_support: nativo+accorgimenti strategia: "usa OMIT per rimuovere date; aggiungi REM singoli per RDATE" snippet: 'REM Mon AT 09:00 FROM 2025-09-01 UNTIL 2025-10-31 MSG Standup\nOMIT 2025-10-13' priorita: Subito - id: P11 pattern: Override/cancellazioni per istanza ics: "RECURRENCE-ID con contenuto modificato o STATUS:CANCELLED" remind_support: espansione strategia: "OMIT la data dalla serie; aggiungi REM singolo con i campi override" snippet: '# serie + REM specifico per l’istanza' priorita: Subito - id: P12 pattern: DURATION al posto di DTEND ics: "DURATION:PT…" remind_support: nativo strategia: "mappa su DURATION in REM" snippet: 'REM 2025-10-05 AT 14:00 DURATION 2:30 MSG Workshop' priorita: Subito - id: P13 pattern: Allarmi ics: "VALARM DISPLAY/AUDIO/EMAIL; TRIGGER relativo" remind_support: parziale strategia: "mappa 1 allarme principale su WARN; multipli opzionali come REM duplicati HIDE" snippet: 'REM 2025-10-05 AT 09:00 WARN 15 MSG Riunione' priorita: Dopo - id: P14 pattern: Fusi orari dichiarati (VTIMEZONE, TZID diversi) ics: "VTIMEZONE + DTSTART;TZID=…" remind_support: nativo+accorgimenti strategia: "normalizza tutto al fuso locale del sistema prima dell’output" snippet: '# conversione in pre-processing' priorita: Subito - id: P15 pattern: Partecipanti/organizzatore ics: "ORGANIZER, ATTENDEE*, PARTSTAT…" remind_support: non previsto strategia: "appendi a DESCRIPTION/MSG come testo" snippet: '# nessuna semantica in Remind' priorita: Quando serve - id: P16 pattern: Allegati/URL esterni ics: "ATTACH, URL" remind_support: non previsto strategia: "conserva URL in coda al MSG" snippet: '# link nel testo' priorita: Quando serve - id: P17 pattern: Meeting online (Google/Teams metadati) ics: "X-GOOGLE-CONFERENCE, X-MICROSOFT-*" remind_support: non previsto strategia: "estrai solo URL di join nel MSG" snippet: '# riduci al link' priorita: Quando serve - id: P18 pattern: Visibilità/trasparenza ics: "CLASS, TRANSP" remind_support: non previsto strategia: "ignora o aggiungi prefisso [FREE]/[BUSY] nel MSG" snippet: '# opzionale' priorita: Ignora - id: P19 pattern: Stato/versioning ics: "STATUS, SEQUENCE, CREATED, LAST-MODIFIED" remind_support: non previsto strategia: "ignora; usa solo STATUS:CANCELLED per soppressioni" snippet: '# già coperto in P11' priorita: Ignora - id: P20 pattern: Categorie/etichette ics: "CATEGORIES:…" remind_support: parziale strategia: "prefisso nel MSG o uso TAG se ti serve filtrare" snippet: 'REM 2025-10-05 AT 09:00 TAG Work MSG [Work] Riunione' priorita: Dopo *) type event_description = [ `Collect_uuid | `Has_summary | `All_day_event | `Expand_recurrence | `Simple_weekly_recurrence ] [@@deriving show] type error = Invalid_date of string | Skip [@@deriving show] let invalid_date s e = Error (Invalid_date (spf "Invalid date: %s, error: %s" s (Remind_sync.Timedesc.Date.Ymd.show_error e))) let skip = Error Skip type collector = Remind.rem -> event -> (Remind.rem, error) result let collect_uuid rem ev : (Remind.rem, error) result = let uid = Utils.get_uid ev in Ok { rem with Remind.original_uuid = uid } let collect_summary rem ev : (Remind.rem, error) result = let summary_opt = List.find_map (function | `Summary (_, s) -> Some s | _ -> None) ev.props in match summary_opt with | Some s -> Ok { rem with Remind.summary = s } | None -> Ok { rem with Remind.summary = "" } let collect_start_end_duration rem ev : (Remind.rem, error) result = let _, dtstart = ev.dtstart in match dtstart with | `Date (year, month, day) -> ( match Timedesc.Date.Ymd.make ~year ~month ~day with | Error e -> invalid_date "DTSTART" e | Ok day_start -> begin match ev.dtend_or_duration with | None -> { rem with Remind.date = day_start } |> Result.ok | Some (`Dtend (_, `Datetime _)) -> skip | Some (`Dtend (_, `Date (year, month, day))) -> begin match Timedesc.Date.Ymd.make ~year ~month ~day with | Error e -> invalid_date "DTEND" e | Ok day_end -> let day_end = Timedesc.Date.add ~days:(-1) day_end in if Timedesc.Date.diff_days day_end day_start = 0 then Ok { rem with Remind.date = day_start; Remind.end_date = None } else Ok { rem with Remind.date = day_start; Remind.end_date = Some day_end } end | Some (`Duration (_, _duration)) -> skip end) | `Datetime datetime -> begin let start_td = Utils.timedesc_of_timestamp datetime in let rem = { rem with Remind.date = Timedesc.date start_td; Remind.time = Some (Timedesc.time start_td) } in match ev.dtend_or_duration with | None -> Ok rem | Some (`Dtend (_, date_or_datetime)) -> begin match date_or_datetime with | `Datetime datetime -> begin let end_td = Utils.timedesc_of_timestamp datetime in let duration = Timedesc.Span.sub (Timedesc.to_timestamp_single end_td) (Timedesc.to_timestamp_single start_td) in let rem = { rem with Remind.duration = Some duration } in Ok rem end | `Date (_year, _month, _day) -> skip end | Some (`Duration (_, duration)) -> let span = Timedesc.Utils.span_of_ptime_span duration in let rem = { rem with Remind.duration = Some span } in Ok rem end let expand_recurrence rem ev : (Remind.rem, error) result = if List.length rem.Remind.recurring > 0 then skip else Ok rem let simple_weekly_recurrence rem ev : (Remind.rem, error) result = match ev.rrule with (* type recur = [ `Byminute of int list | `Byday of (int * weekday) list | `Byhour of int list | `Bymonth of int list | `Bymonthday of int list | `Bysecond of int list | `Bysetposday of int list | `Byweek of int list | `Byyearday of int list | `Weekday of weekday ] [@@deriving show] type freq = [ `Daily | `Hourly | `Minutely | `Monthly | `Secondly | `Weekly | `Yearly ] [@@deriving show] type count_or_until = [ `Count of int | `Until of utc_or_timestamp_local (* TODO date or datetime *) ] [@@deriving show] type interval = int [@@deriving show] type recurrence = freq * count_or_until option * interval option * recur list [@@deriving show] QUESTE SONO **TUTTE** LE RRULE NEL MIO DATASET RRULE: (`Daily, (Some `Count (11)), None, []) RRULE: (`Daily, (Some `Until (`Utc (2008-05-11 11:15:00 +00:00))), None, [`Weekday (`Monday)]) RRULE: (`Daily, (Some `Until (`Utc (2026-02-04 13:30:00 +00:00))), (Some 1), []) RRULE: (`Weekly, (Some `Count (3)), None, [`Byday ([(0, `Wednesday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2009-07-31 18:00:00 +00:00))), None, [`Byday ([(0, `Tuesday); (0, `Friday)]); `Weekday (`Monday)]) RRULE: (`Weekly, (Some `Until (`Utc (2013-04-18 17:30:00 +00:00))), None, [`Byday ([(0, `Monday); (0, `Thursday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2014-12-20 10:30:00 +00:00))), None, [`Byday ([(0, `Saturday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2020-09-16 21:59:59 +00:00))), None, [`Byday ([(0, `Thursday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2021-08-25 21:59:59 +00:00))), None, [`Byday ([(0, `Wednesday)]); `Weekday (`Monday)]) RRULE: (`Weekly, (Some `Until (`Utc (2021-09-18 21:59:59 +00:00))), (Some 1), [`Byday ([(0, `Sunday)]); `Weekday (`Monday)]) RRULE: (`Weekly, (Some `Until (`Utc (2024-06-12 08:00:00 +00:00))), (Some 4), [`Weekday (`Sunday); `Byday ([(0, `Wednesday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2025-02-04 22:59:59 +00:00))), (Some 1), [`Weekday (`Sunday); `Byday ([(0, `Wednesday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2025-06-22 21:59:59 +00:00))), (Some 1), [`Weekday (`Monday); `Byday ([(0, `Monday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2026-02-24 22:59:59 +00:00))), (Some 2), [`Weekday (`Monday); `Byday ([(0, `Tuesday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2026-03-25 00:00:00 +00:00))), None, [`Weekday (`Monday); `Byday ([(0, `Wednesday)])]) RRULE: (`Weekly, (Some `Until (`Utc (2026-07-01 09:00:00 +00:00))), None, []) RRULE: (`Yearly, None, None, []) Il file RRULE_all.txt contiene tutte le RRULE del mio dataset! *) | Some (_, (freq, count_or_until, interval, recurs)) -> let _recur = (freq, count_or_until, interval, recurs) in let uid = Utils.get_uid ev in Printf.eprintf "RRULE: %s\t\t\tUID: %s\n" (Icalendar.show_recurrence _recur) uid; skip (* TODO: implementare *) | None -> Ok rem let all_collectors : (collector * event_description) list = [ (collect_uuid, `Collect_uuid); (collect_summary, `Has_summary); (collect_start_end_duration, `All_day_event); (expand_recurrence, `Expand_recurrence); (simple_weekly_recurrence, `Simple_weekly_recurrence); ] let remind_of_event (ev : Icalendar.event list) : (Remind.rem, error) result = let () = if List.length ev = 0 then failwith "No events provided" in let master, recurrence = if List.length ev > 1 then begin separate_master_and_recurrence ev end else begin let ev = List.hd ev in (ev, []) end in let rem = { Remind.empty with Remind.recurring = recurrence } in ListLabels.fold_left ~init:(Ok rem) all_collectors ~f:(fun rem_or_error (pred, _desc) -> match rem_or_error with | Error e -> Error e | Ok rem -> pred rem master)