feat: implement simple weekly recurrence rendering
- Add `simple_weekly` type and `weekly` field to `rem` record
- Add `exdate` field to `rem` for excluded dates
- Add `collect_exdates` collector to pipeline
- implement weekly `RRULE` handling with `BYDAY`, `INTERVAL`,
`COUNT`/`UNTIL`
- Add `render_weekly` to emit one `REM` per weekday with `UNTIL`/`*N`
- Replace `timedesc_of_date_or_datetime` with
`timedesc_of_utc_or_timestamp_local` in utils
- Refactor `get_exdates`/`get_rdates` to separate dates, datetimes and
periods; add debug logging per UID
- Wrap reminder output in try/catch in main; drop trailing newline
duplication
- Mark implemented predicates (P00–P05, P09, P12, P14) with ✅;
remove P18–P20 (ignored/deferred)
This commit is contained in:
85
bin/utils.ml
85
bin/utils.ml
@@ -3,6 +3,15 @@ open Icalendar
|
||||
|
||||
type months = Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec
|
||||
|
||||
let string_of_weekday = function
|
||||
| `Monday -> "Mon"
|
||||
| `Tuesday -> "Tue"
|
||||
| `Wednesday -> "Wed"
|
||||
| `Thursday -> "Thu"
|
||||
| `Friday -> "Fri"
|
||||
| `Saturday -> "Sat"
|
||||
| `Sunday -> "Sun"
|
||||
|
||||
let month_of_int = function
|
||||
| 1 -> Jan
|
||||
| 2 -> Feb
|
||||
@@ -80,28 +89,15 @@ let timedesc_of_timestamp (ts : timestamp) : Timedesc.t =
|
||||
let hour, minute, second = (time.Timedesc.Time.hour, time.Timedesc.Time.minute, time.Timedesc.Time.second) in
|
||||
Timedesc.make_exn ~year ~month ~day ~hour ~minute ~second ~tz ()
|
||||
|
||||
let timedesc_of_date_or_datetime (t : date_or_datetime) : Timedesc.t =
|
||||
match t with
|
||||
| `Datetime (`Local _ptime_ts) ->
|
||||
(* this case is not present in my current dataset… *)
|
||||
failwith "Unhandled case: `Local datetime"
|
||||
| `Datetime (`Utc ts) ->
|
||||
Timedesc.Utils.timestamp_of_ptime ts
|
||||
|> Timedesc.of_timestamp_exn ~tz_of_date_time:(Timedesc.Time_zone.local_exn ())
|
||||
| `Datetime (`With_tzid (ts, (_b, tz_name))) ->
|
||||
(* Qui il timestamp è SCRITTO come se fosse UTC (+00:00) ma in realtà va interpretato con
|
||||
il fuso orario indicato da tz_name. *)
|
||||
let tz = Timedesc.Time_zone.make_exn tz_name in
|
||||
let wrong_ts = Timedesc.Utils.timestamp_of_ptime ts in
|
||||
let date = Timedesc.date (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in
|
||||
let year, month, day = (Timedesc.Date.year date, Timedesc.Date.month date, Timedesc.Date.day date) in
|
||||
let time = Timedesc.time_view (Timedesc.of_timestamp_exn ~tz_of_date_time:Timedesc.Time_zone.utc wrong_ts) in
|
||||
let hour, minute, second = (time.Timedesc.Time.hour, time.Timedesc.Time.minute, time.Timedesc.Time.second) in
|
||||
Timedesc.make_exn ~year ~month ~day ~hour ~minute ~second ~tz ()
|
||||
| `Date (year, month, day) ->
|
||||
Timedesc.make_exn ~year ~month ~day ~hour:0 ~minute:0 ~second:0 ~tz:(Timedesc.Time_zone.local_exn ()) ()
|
||||
let timedesc_of_utc_or_timestamp_local (ts : utc_or_timestamp_local) : Timedesc.t =
|
||||
let local_tz = Timedesc.Time_zone.local_exn () in
|
||||
match ts with
|
||||
| `Local t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz
|
||||
(* this case is not present in my current dataset… *)
|
||||
| `Utc t -> t |> Timedesc.Utils.timestamp_of_ptime |> Timedesc.of_timestamp_exn ~tz_of_date_time:local_tz
|
||||
|
||||
let get_exdates ev =
|
||||
let uid = get_uid ev in
|
||||
let event_props = ev.props in
|
||||
let dates_or_datetimes =
|
||||
List.filter_map
|
||||
@@ -111,36 +107,47 @@ let get_exdates ev =
|
||||
| _ -> None)
|
||||
event_props
|
||||
in
|
||||
ListLabels.fold_left ~init:[] dates_or_datetimes ~f:(fun acc dates ->
|
||||
let added =
|
||||
|
||||
let datetimes, dates =
|
||||
ListLabels.fold_left ~init:([], []) dates_or_datetimes ~f:(fun (acc_datetimes, acc_dates) dates ->
|
||||
match dates with
|
||||
| `Datetimes ts_list -> List.map (fun ts -> `Datetime ts) ts_list
|
||||
| `Dates date_list -> List.map (fun date -> `Date date) date_list
|
||||
in
|
||||
added @ acc)
|
||||
|> List.map timedesc_of_date_or_datetime
|
||||
| `Dates date_list -> (acc_datetimes, acc_dates @ date_list)
|
||||
| `Datetimes ts_list -> (acc_datetimes @ ts_list, acc_dates))
|
||||
in
|
||||
|
||||
if List.length dates > 0 then Printf.eprintf "Found EXDATE with dates: %d entries; UID: %s\n" (List.length dates) uid;
|
||||
if List.length datetimes > 0 then
|
||||
Printf.eprintf "Found EXDATE with datetimes: %d entries; UID: %s\n" (List.length datetimes) uid;
|
||||
|
||||
List.map (fun d -> `Date d) dates @ List.map (fun dt -> `Datetime dt) datetimes
|
||||
|
||||
let get_rdates ev =
|
||||
let uid = get_uid ev in
|
||||
let event_props = ev.props in
|
||||
let dates_or_datetimes =
|
||||
let dates_or_datetimes_or_periods =
|
||||
List.filter_map
|
||||
(fun prop ->
|
||||
match prop with
|
||||
| `Rdate (_, dates) -> Some dates
|
||||
| `Rdate (_, x) -> Some x
|
||||
| _ -> None)
|
||||
event_props
|
||||
in
|
||||
ListLabels.fold_left ~init:[] dates_or_datetimes ~f:(fun acc dates ->
|
||||
let added =
|
||||
|
||||
let datetimes, dates, periods =
|
||||
ListLabels.fold_left ~init:([], [], []) dates_or_datetimes_or_periods
|
||||
~f:(fun (acc_datetimes, acc_dates, acc_periods) dates ->
|
||||
match dates with
|
||||
| `Datetimes ts_list -> List.map (fun ts -> `Datetime ts) ts_list
|
||||
| `Dates date_list -> List.map (fun date -> `Date date) date_list
|
||||
| `Periods _ ->
|
||||
(* Ignored for now, does not appear in my current dataset *)
|
||||
failwith "Unhandled case: `Periods in RDATE"
|
||||
in
|
||||
added @ acc)
|
||||
|> List.map timedesc_of_date_or_datetime
|
||||
| `Dates date_list -> (acc_datetimes, acc_dates @ date_list, acc_periods)
|
||||
| `Datetimes ts_list -> (acc_datetimes @ ts_list, acc_dates, acc_periods)
|
||||
| `Periods period_list -> (acc_datetimes, acc_dates, acc_periods @ period_list))
|
||||
in
|
||||
|
||||
if List.length dates > 0 then Printf.eprintf "Found RDATE with dates: %d entries; UID: %s\n" (List.length dates) uid;
|
||||
if List.length datetimes > 0 then
|
||||
Printf.eprintf "Found RDATE with datetimes: %d entries; UID: %s\n" (List.length datetimes) uid;
|
||||
if List.length periods > 0 then
|
||||
Printf.eprintf "Found RDATE with periods: %d entries; UID: %s\n" (List.length periods) uid;
|
||||
[]
|
||||
|
||||
let get_recurrence_id ev =
|
||||
List.find_map
|
||||
|
||||
Reference in New Issue
Block a user