feat(recurrence): add daily recurrence support
- Add `simple_daily` type and `daily` field to `rem`
- Implement `render_daily`, `add_interval_daily`, and `add_until_daily`
- Extend `simple_recurrence` collector to handle `FREQ=DAILY` alongside
`FREQ=WEEKLY`
- Remove dead `expand_recurrence` collector
- Mark P06 pattern as implemented (✅)
This commit is contained in:
@@ -50,7 +50,7 @@ open Utils
|
|||||||
snippet: 'REM Mon Wed FROM 2025-09-01 UNTIL 2025-10-31 AT 09:00 MSG Standup'
|
snippet: 'REM Mon Wed FROM 2025-09-01 UNTIL 2025-10-31 AT 09:00 MSG Standup'
|
||||||
priorita: Subito
|
priorita: Subito
|
||||||
|
|
||||||
- id: P06
|
- id: P06 ✅
|
||||||
pattern: Ricorrenza giornaliera semplice
|
pattern: Ricorrenza giornaliera semplice
|
||||||
ics: "RRULE:FREQ=DAILY;[UNTIL|COUNT]"
|
ics: "RRULE:FREQ=DAILY;[UNTIL|COUNT]"
|
||||||
remind_support: nativo
|
remind_support: nativo
|
||||||
@@ -226,9 +226,6 @@ let collect_exdates rem ev : (Remind.rem, error) result =
|
|||||||
let exdates = Utils.get_exdates ev in
|
let exdates = Utils.get_exdates ev in
|
||||||
Ok { rem with Remind.exdate = exdates }
|
Ok { rem with Remind.exdate = exdates }
|
||||||
|
|
||||||
let expand_recurrence rem _ev : (Remind.rem, error) result =
|
|
||||||
if List.length rem.Remind.recurring > 0 then skip else Ok rem
|
|
||||||
|
|
||||||
let yearly_simple_date rem ev : (Remind.rem, error) result =
|
let yearly_simple_date rem ev : (Remind.rem, error) result =
|
||||||
match ev.rrule with
|
match ev.rrule with
|
||||||
| Some (_, (`Yearly, None, None, [])) ->
|
| Some (_, (`Yearly, None, None, [])) ->
|
||||||
@@ -292,11 +289,12 @@ RRULE: (`Weekly, (Some `Until (`Utc (2026-07-01 09:00:00 +00:00))), None, [])
|
|||||||
*)
|
*)
|
||||||
match ev.rrule with
|
match ev.rrule with
|
||||||
| Some (_, (`Yearly, None, None, [])) -> Ok rem (* handled in yearly_simple_date *)
|
| Some (_, (`Yearly, None, None, [])) -> Ok rem (* handled in yearly_simple_date *)
|
||||||
| Some (_, (`Weekly, count_or_until, interval, recurs)) ->
|
| Some (_, ((`Weekly as freq), count_or_until, interval, recurs))
|
||||||
|
| Some (_, ((`Daily as freq), count_or_until, interval, recurs)) ->
|
||||||
begin if List.length rem.recurring > 0 || List.length rem.exdate > 0 then (
|
begin if List.length rem.recurring > 0 || List.length rem.exdate > 0 then (
|
||||||
Printf.eprintf "Warning: skipping complex recurrence with EXDATE/RDATE/overrides, not supported\t\t\tUID: %s\n"
|
Printf.eprintf "Warning: skipping complex recurrence with EXDATE/RDATE/overrides, not supported\t\t\tUID: %s\n"
|
||||||
(Utils.get_uid ev);
|
(Utils.get_uid ev);
|
||||||
debug_print_of_recurrence_and_skip ev (`Weekly, count_or_until, interval, recurs))
|
debug_print_of_recurrence_and_skip ev (freq, count_or_until, interval, recurs))
|
||||||
else
|
else
|
||||||
let days =
|
let days =
|
||||||
ListLabels.filter_map recurs ~f:(function
|
ListLabels.filter_map recurs ~f:(function
|
||||||
@@ -310,21 +308,21 @@ RRULE: (`Weekly, (Some `Until (`Utc (2026-07-01 09:00:00 +00:00))), None, [])
|
|||||||
| `Weekday `Monday -> Some `Monday
|
| `Weekday `Monday -> Some `Monday
|
||||||
| _ -> None)
|
| _ -> None)
|
||||||
in
|
in
|
||||||
Ok { rem with Remind.weekly = Some { count_or_until; interval; byday = days; week_start } }
|
match freq with
|
||||||
|
| `Daily -> Ok { rem with Remind.weekly = None; Remind.daily = Some { count_or_until; interval; week_start } }
|
||||||
|
| `Weekly ->
|
||||||
|
Ok
|
||||||
|
{
|
||||||
|
rem with
|
||||||
|
Remind.daily = None;
|
||||||
|
Remind.weekly = Some { count_or_until; interval; byday = days; week_start };
|
||||||
|
}
|
||||||
end
|
end
|
||||||
| Some (_, recurs) -> debug_print_of_recurrence_and_skip ev recurs
|
| Some (_, recurs) -> debug_print_of_recurrence_and_skip ev recurs
|
||||||
| None -> Ok rem
|
| None -> Ok rem
|
||||||
|
|
||||||
let all_collectors : collector list =
|
let all_collectors : collector list =
|
||||||
[
|
[ collect_uuid; collect_summary; collect_start_end_duration; collect_exdates; yearly_simple_date; simple_recurrence ]
|
||||||
collect_uuid;
|
|
||||||
collect_summary;
|
|
||||||
collect_start_end_duration;
|
|
||||||
collect_exdates;
|
|
||||||
expand_recurrence;
|
|
||||||
yearly_simple_date;
|
|
||||||
simple_recurrence;
|
|
||||||
]
|
|
||||||
|
|
||||||
let remind_of_event (source : string) (ev : Icalendar.event list) : (Remind.rem, error) result =
|
let remind_of_event (source : string) (ev : Icalendar.event list) : (Remind.rem, error) result =
|
||||||
let () = if List.length ev = 0 then failwith "No events provided" in
|
let () = if List.length ev = 0 then failwith "No events provided" in
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ type simple_weekly = {
|
|||||||
}
|
}
|
||||||
(** A simple weekly REM command *)
|
(** A simple weekly REM command *)
|
||||||
|
|
||||||
|
type simple_daily = {
|
||||||
|
count_or_until : Icalendar.count_or_until option;
|
||||||
|
interval : int option; (** Optional interval for daily recurrence, default is 1 *)
|
||||||
|
week_start : week_first_day option; (** First day of the week for weekly recurrence *)
|
||||||
|
}
|
||||||
|
(** A simple daily REM command *)
|
||||||
|
|
||||||
type rem = {
|
type rem = {
|
||||||
source : string; (** Source file or identifier for the reminder *)
|
source : string; (** Source file or identifier for the reminder *)
|
||||||
original_uuid : string; (** Original UID from the iCalendar event *)
|
original_uuid : string; (** Original UID from the iCalendar event *)
|
||||||
@@ -20,6 +27,7 @@ type rem = {
|
|||||||
duration : Timedesc.Span.t option; (** Optional duration for timed events *)
|
duration : Timedesc.Span.t option; (** Optional duration for timed events *)
|
||||||
yearly : (int * int) option; (** Optional simple yearly recurrence (month, day) *)
|
yearly : (int * int) option; (** Optional simple yearly recurrence (month, day) *)
|
||||||
weekly : simple_weekly option; (** Optional simple weekly recurrence *)
|
weekly : simple_weekly option; (** Optional simple weekly recurrence *)
|
||||||
|
daily : simple_daily option; (** Optional simple daily recurrence *)
|
||||||
recurring : Icalendar.event list;
|
recurring : Icalendar.event list;
|
||||||
(** List of events that are part of the same recurring series: these are only the overrides, not the master event
|
(** List of events that are part of the same recurring series: these are only the overrides, not the master event
|
||||||
*)
|
*)
|
||||||
@@ -38,6 +46,7 @@ let empty =
|
|||||||
duration = None;
|
duration = None;
|
||||||
yearly = None;
|
yearly = None;
|
||||||
weekly = None;
|
weekly = None;
|
||||||
|
daily = None;
|
||||||
recurring = [];
|
recurring = [];
|
||||||
exdate = [];
|
exdate = [];
|
||||||
}
|
}
|
||||||
@@ -49,11 +58,15 @@ let add_info b uuid = Buffer.add_string b (spf "INFO \"UID: %s\" " uuid)
|
|||||||
let add_date b date = Buffer.add_string b (Timedesc.Date.to_rfc3339 date)
|
let add_date b date = Buffer.add_string b (Timedesc.Date.to_rfc3339 date)
|
||||||
let add_weekday b wd = Buffer.add_string b (spf "%s " (string_of_weekday wd))
|
let add_weekday b wd = Buffer.add_string b (spf "%s " (string_of_weekday wd))
|
||||||
|
|
||||||
let add_interval b w =
|
let add_interval b (w : simple_weekly) =
|
||||||
let n = Option.value ~default:1 w.interval in
|
let n = Option.value ~default:1 w.interval in
|
||||||
Buffer.add_string b (spf "*%d " (n * 7))
|
Buffer.add_string b (spf "*%d " (n * 7))
|
||||||
|
|
||||||
let add_until b rem w =
|
let add_interval_daily b (d : simple_daily) =
|
||||||
|
let n = Option.value ~default:1 d.interval in
|
||||||
|
Buffer.add_string b (spf "*%d " n)
|
||||||
|
|
||||||
|
let add_until b rem (w : simple_weekly) =
|
||||||
match w.count_or_until with
|
match w.count_or_until with
|
||||||
| None -> ()
|
| None -> ()
|
||||||
| Some (`Until d) ->
|
| Some (`Until d) ->
|
||||||
@@ -71,6 +84,17 @@ let add_until b rem w =
|
|||||||
let until = Timedesc.Date.add ~days:((count * 7 * iv) - sub) rem.date in
|
let until = Timedesc.Date.add ~days:((count * 7 * iv) - sub) rem.date in
|
||||||
Buffer.add_string b (spf "UNTIL %s " (Timedesc.Date.to_rfc3339 until))
|
Buffer.add_string b (spf "UNTIL %s " (Timedesc.Date.to_rfc3339 until))
|
||||||
|
|
||||||
|
let add_until_daily b rem (d : simple_daily) =
|
||||||
|
match d.count_or_until with
|
||||||
|
| None -> ()
|
||||||
|
| Some (`Until dt) ->
|
||||||
|
let ts = timedesc_of_utc_or_timestamp_local dt in
|
||||||
|
Buffer.add_string b (spf "UNTIL %s " (Timedesc.Date.to_rfc3339 (Timedesc.date ts)))
|
||||||
|
| Some (`Count count) ->
|
||||||
|
let iv = Option.value ~default:1 d.interval in
|
||||||
|
let until = Timedesc.Date.add ~days:((count - 1) * iv) rem.date in
|
||||||
|
Buffer.add_string b (spf "UNTIL %s " (Timedesc.Date.to_rfc3339 until))
|
||||||
|
|
||||||
let add_at b = function
|
let add_at b = function
|
||||||
| Some t -> Buffer.add_string b (spf " AT %s" (string_of_time t))
|
| Some t -> Buffer.add_string b (spf " AT %s" (string_of_time t))
|
||||||
| None -> ()
|
| None -> ()
|
||||||
@@ -87,7 +111,20 @@ let add_msg b summary = Buffer.add_string b (spf " MSG %s\n" summary)
|
|||||||
|
|
||||||
(* ── rendering ────────────────────────────────────────────────── *)
|
(* ── rendering ────────────────────────────────────────────────── *)
|
||||||
|
|
||||||
let render_weekly rem w =
|
let render_daily rem (d : simple_daily) =
|
||||||
|
let b = Buffer.create 256 in
|
||||||
|
add_rem b;
|
||||||
|
add_info b rem.original_uuid;
|
||||||
|
add_date b rem.date;
|
||||||
|
Buffer.add_char b ' ';
|
||||||
|
add_interval_daily b d;
|
||||||
|
add_until_daily b rem d;
|
||||||
|
add_at b rem.time;
|
||||||
|
add_duration b rem.duration;
|
||||||
|
add_msg b rem.summary;
|
||||||
|
Buffer.contents b
|
||||||
|
|
||||||
|
let render_weekly rem (w : simple_weekly) =
|
||||||
let b = Buffer.create 256 in
|
let b = Buffer.create 256 in
|
||||||
List.iter
|
List.iter
|
||||||
(fun wd ->
|
(fun wd ->
|
||||||
@@ -125,9 +162,12 @@ let render_yearly month day summary =
|
|||||||
(* ── dispatcher ───────────────────────────────────────────────── *)
|
(* ── dispatcher ───────────────────────────────────────────────── *)
|
||||||
|
|
||||||
let string_of_rem rem =
|
let string_of_rem rem =
|
||||||
|
match rem.daily with
|
||||||
|
| Some d -> render_daily rem d
|
||||||
|
| None -> (
|
||||||
match rem.weekly with
|
match rem.weekly with
|
||||||
| Some w -> render_weekly rem w
|
| Some w -> render_weekly rem w
|
||||||
| None -> (
|
| None -> (
|
||||||
match rem.yearly with
|
match rem.yearly with
|
||||||
| Some (month, day) -> render_yearly month day rem.summary
|
| Some (month, day) -> render_yearly month day rem.summary
|
||||||
| None -> render_single rem)
|
| None -> render_single rem))
|
||||||
|
|||||||
Reference in New Issue
Block a user