open Remind_sync open Utils type week_first_day = [ `Sunday | `Monday ] [@@deriving show] type simple_weekly = { count_or_until : Icalendar.count_or_until option; interval : int option; (** Optional interval for weekly recurrence, default is 1 *) byday : Icalendar.weekday list; week_start : week_first_day option; (** First day of the week for weekly recurrence *) } [@@deriving show] (** A simple weekly REM command *) type rem = { original_uuid : string; (** Original UID from the iCalendar event *) summary : string; (** Summary or title of the reminder *) date : Timedesc.Date.t; (** Date specification (day, month, year) *) end_date : Timedesc.Date.t option; (** Optional end date for a date range *) time : Timedesc.Time.t option; (** Optional time specification (hour, minute) *) duration : Timedesc.Span.t option; (** Optional duration for timed events *) yearly : (int * int) option; (** Optional simple yearly recurrence (month, day) *) weekly : simple_weekly option; (** Optional simple weekly recurrence *) recurring : Icalendar.event list; (** List of events that are part of the same recurring series: these are only the overrides, not the master event *) exdate : Icalendar.date_or_datetime list; (** List of excluded dates for recurring events *) } [@@deriving show] (** A complete REM command *) let empty = { original_uuid = ""; summary = ""; date = Timedesc.Date.Ymd.make_exn ~year:1970 ~month:1 ~day:1; end_date = None; time = None; duration = None; yearly = None; weekly = None; recurring = []; exdate = []; } let render_yearly month day summary = let month_str = month_of_int month |> string_of_month in spf "REM %s %d MSG %s\n" month_str day summary let render_weekly rem weekly = let b = Buffer.create 256 in List.iter begin fun weekday -> Buffer.add_string b "REM "; Buffer.add_string b (spf "INFO \"UID: %s\" " rem.original_uuid); Buffer.add_string b (spf "%s " (string_of_weekday weekday)); Buffer.add_string b (Timedesc.Date.to_rfc3339 rem.date); Buffer.add_string b " "; (match weekly.interval with | Some interval -> Buffer.add_string b (spf "*%d " (interval * 7)) | None -> Buffer.add_string b "*7 "); (match weekly.count_or_until with | Some (`Count count) -> begin (* We must compute the until date based on the count and the interval *) let wd = Timedesc.Date.weekday rem.date in let wd_int = Timedesc.Utils.tm_int_of_weekday wd in let day_to_subtract = match weekly.week_start with | Some `Sunday -> wd_int | Some `Monday -> wd_int - 1 | None -> wd_int (* Default to Sunday if not specified *) in let interval = Option.value ~default:1 weekly.interval in let until_date = Timedesc.Date.add ~days:((count * 7 * interval) - day_to_subtract) rem.date in Buffer.add_string b "UNTIL "; Buffer.add_string b (Timedesc.Date.to_rfc3339 until_date); Buffer.add_string b " " end | Some (`Until until_date) -> begin Buffer.add_string b "UNTIL "; let ts = timedesc_of_utc_or_timestamp_local until_date in Buffer.add_string b (Timedesc.Date.to_rfc3339 (Timedesc.date ts)); Buffer.add_string b " " end | None -> ()); (match rem.time with | Some time -> Buffer.add_string b " AT "; Buffer.add_string b (string_of_time time) | None -> ()); (match rem.duration with | Some duration -> Buffer.add_string b " DURATION "; Buffer.add_string b (string_of_span duration); Buffer.add_string b "" | None -> ()); Buffer.add_string b " MSG "; Buffer.add_string b rem.summary; Buffer.add_string b "\n" end weekly.byday; Buffer.contents b let string_of_rem rem = match rem.weekly with | Some weekly -> render_weekly rem weekly | None -> begin match rem.yearly with | Some (month, day) -> render_yearly month day rem.summary | None -> begin let b = Buffer.create 256 in Buffer.add_string b "REM "; Buffer.add_string b (spf "INFO \"UID: %s\" " rem.original_uuid); Buffer.add_string b (Timedesc.Date.to_rfc3339 rem.date); (match rem.time with | Some time -> Buffer.add_string b " AT "; Buffer.add_string b (string_of_time time) | None -> ()); (match rem.duration with | Some duration -> Buffer.add_string b " DURATION "; Buffer.add_string b (string_of_span duration); Buffer.add_string b "" | None -> ()); (match rem.end_date with | Some end_date -> Buffer.add_string b " THROUGH "; Buffer.add_string b (Timedesc.Date.to_rfc3339 end_date) | None -> ()); Buffer.add_string b " MSG "; Buffer.add_string b rem.summary; Buffer.add_string b "\n"; Buffer.contents b end end