open Sexplib0.Sexp_conv (* How a sanctoral entry expresses its date. Plan 2 ships the only form the EF sanctoral needs -- a fixed calendar date -- because EF movable feasts come from the rite's temporal code, not from data. Sunday-relative and Easter-relative forms arrive with the OF sanctoral. *) type t = Fixed of { month : int; day : int } [@@deriving sexp] (* Leap-year maximum, so 29 February is constructible; it simply does not resolve in a common year. *) let max_day = function | 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31 | 4 | 6 | 9 | 11 -> 30 | 2 -> 29 | _ -> 0 let fixed ~month ~day = if month < 1 || month > 12 then Error (Printf.sprintf "date_spec: month %d out of range 1..12" month) else if day < 1 || day > max_day month then Error (Printf.sprintf "date_spec: day %d out of range for month %d" day month) else Ok (Fixed { month; day }) let resolve t ~year = match t with | Fixed { month; day } -> Result.to_option (Date.make ~year ~month ~day)