feat(monthly): add support for MONTHLY recurrence (P07, P08)
- Add `monthly_pattern`, `simple_monthly` types and `monthly` field to `rem` - Implement `render_monthly` and `add_until_monthly` in `remind.ml` - Handle `BYMONTHDAY` (P07) and nth-weekday `BYDAY` (P08) patterns in `eventPredicates.ml` - Add `add_months` utility for date arithmetic - Mark P07 and P08 as implemented in documentation
This commit is contained in:
@@ -17,6 +17,17 @@ type simple_daily = {
|
||||
}
|
||||
(** A simple daily REM command *)
|
||||
|
||||
type monthly_pattern =
|
||||
| By_month_day of int (** P07: BYMONTHDAY=n or implicit day from DTSTART *)
|
||||
| By_nth_weekday of int * Icalendar.weekday (** P08: BYDAY=nWD, n≠0, can be negative *)
|
||||
|
||||
type simple_monthly = {
|
||||
count_or_until : Icalendar.count_or_until option;
|
||||
interval : int option;
|
||||
pattern : monthly_pattern;
|
||||
}
|
||||
(** A simple monthly REM command *)
|
||||
|
||||
type rem = {
|
||||
source : string; (** Source file or identifier for the reminder *)
|
||||
original_uuid : string; (** Original UID from the iCalendar event *)
|
||||
@@ -26,6 +37,7 @@ type rem = {
|
||||
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) *)
|
||||
monthly : simple_monthly option; (** Optional simple monthly recurrence *)
|
||||
weekly : simple_weekly option; (** Optional simple weekly recurrence *)
|
||||
daily : simple_daily option; (** Optional simple daily recurrence *)
|
||||
recurring : Icalendar.event list;
|
||||
@@ -46,6 +58,7 @@ let empty =
|
||||
time = None;
|
||||
duration = None;
|
||||
yearly = None;
|
||||
monthly = None;
|
||||
weekly = None;
|
||||
daily = None;
|
||||
recurring = [];
|
||||
@@ -111,6 +124,22 @@ let add_until_daily b rem (d : simple_daily) =
|
||||
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_until_monthly b rem (m : simple_monthly) =
|
||||
match m.count_or_until with
|
||||
| None -> ()
|
||||
| Some (`Until dt) ->
|
||||
let ts = timedesc_of_utc_or_timestamp_local dt in
|
||||
let date = until_date_adjusted ts rem.time in
|
||||
Buffer.add_string b (spf "UNTIL %s " (Timedesc.Date.to_rfc3339 date))
|
||||
| Some (`Count count) ->
|
||||
let base = Utils.add_months rem.date (count - 1) in
|
||||
let until =
|
||||
match m.pattern with
|
||||
| By_month_day _ -> base
|
||||
| By_nth_weekday _ -> Timedesc.Date.add ~days:6 base (* weekday can shift up to 6 days *)
|
||||
in
|
||||
Buffer.add_string b (spf "UNTIL %s " (Timedesc.Date.to_rfc3339 until))
|
||||
|
||||
let add_at b = function
|
||||
| Some t -> Buffer.add_string b (spf " AT %s" (string_of_time t))
|
||||
| None -> ()
|
||||
@@ -199,6 +228,31 @@ let render_weekly rem (w : simple_weekly) =
|
||||
close_omit_context b rem.exdate;
|
||||
Buffer.contents b
|
||||
|
||||
let render_monthly rem (m : simple_monthly) =
|
||||
let b = Buffer.create 256 in
|
||||
add_omit_context b rem.exdate;
|
||||
add_rem b;
|
||||
add_info b rem.original_uuid;
|
||||
add_source b rem.source;
|
||||
(match m.pattern with
|
||||
| By_month_day day -> Buffer.add_string b (spf "%d " day)
|
||||
| By_nth_weekday (n, wd) when n > 0 ->
|
||||
let day = ((n - 1) * 7) + 1 in
|
||||
add_weekday b wd;
|
||||
Buffer.add_string b (spf "%d " day)
|
||||
| By_nth_weekday (n, wd) (* n < 0 *) ->
|
||||
let back = -n * 7 in
|
||||
add_weekday b wd;
|
||||
Buffer.add_string b (spf "1 --%d " back));
|
||||
Buffer.add_string b (spf "FROM %s " (Timedesc.Date.to_rfc3339 rem.date));
|
||||
add_until_monthly b rem m;
|
||||
add_skip b rem.exdate;
|
||||
add_at b rem.time;
|
||||
add_duration b rem.duration;
|
||||
add_msg b rem.summary;
|
||||
close_omit_context b rem.exdate;
|
||||
Buffer.contents b
|
||||
|
||||
let render_single rem =
|
||||
let b = Buffer.create 256 in
|
||||
add_rem b;
|
||||
@@ -230,9 +284,12 @@ let string_of_rem rem =
|
||||
match rem.weekly with
|
||||
| Some w -> render_weekly rem w
|
||||
| None -> (
|
||||
match rem.yearly with
|
||||
| Some (month, day) -> render_yearly rem month day
|
||||
| None -> render_single rem))
|
||||
match rem.monthly with
|
||||
| Some m -> render_monthly rem m
|
||||
| None -> (
|
||||
match rem.yearly with
|
||||
| Some (month, day) -> render_yearly rem month day
|
||||
| None -> render_single rem)))
|
||||
in
|
||||
let overrides = List.map render_single rem.overrides in
|
||||
String.concat "" (main :: overrides)
|
||||
|
||||
Reference in New Issue
Block a user