summaryrefslogtreecommitdiff
path: root/lib/kernel/layer.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/layer.ml')
-rw-r--r--lib/kernel/layer.ml65
1 files changed, 51 insertions, 14 deletions
diff --git a/lib/kernel/layer.ml b/lib/kernel/layer.ml
index 515b052..ec074a7 100644
--- a/lib/kernel/layer.ml
+++ b/lib/kernel/layer.ml
@@ -20,26 +20,63 @@ let set t entry =
let t = remove t entry.cel.Celebration.slug in
{ t with entries = canonical (entry :: t.entries) }
-(* Dates are year-independent, so the index is built once per layer rather than
- once per year -- a full 1583..9999 sweep would otherwise rescan the entry
- list for every day. *)
-type 'r by_date = (int * int, 'r entry list) Hashtbl.t
+(* FIXED entries are year-independent, so their table is built once and keyed
+ (month, day) -- a full 1583..9999 sweep would otherwise rescan the entry
+ list for every day.
-let key = function Date_spec.Fixed { month; day } -> (month, day)
+ MOVABLE entries have no such key BY CONSTRUCTION: the same spec lands on a
+ different (month, day) in different years, so one year-independent table
+ cannot serve a multi-year span. They are resolved once per civil year in
+ the span and keyed by rata die instead.
-let index_by_date t =
- let tbl : 'r by_date = Hashtbl.create 512 in
+ Keeping the two separate, rather than resolving everything per year,
+ preserves the fixed path exactly as it was -- including the "30 November
+ counted twice in a 371-day liturgical span" behaviour validate.mli
+ documents for St Andrew, which falls straight out of querying by (month,
+ day) and would have to be re-established by hand under a uniform rata-die
+ index. *)
+type 'r index = {
+ fixed : (int * int, 'r entry list) Hashtbl.t;
+ movable : (int, 'r entry list) Hashtbl.t; (** keyed by rata die *)
+}
+
+let index t ~easter ~years =
+ (* Filter to the kernel domain BEFORE calling [easter] on anything. A
+ liturgical year is Advent-anchored, so a caller resolving civil year [y]
+ legitimately names [y - 1] or [y + 1] -- and at the two edges those are
+ 1582 and 10000, which {!Computus} correctly refuses by raising. Both
+ edges bit during development (the ceiling via the domain-ceiling test,
+ the floor via `colitur day 1583`), which is why the guard lives HERE, at
+ the single point that calls [easter], rather than as a clamp repeated in
+ every caller. A spec cannot resolve outside 1583..9999 anyway, so
+ dropping those years loses nothing. *)
+ let years = List.filter (fun y -> y >= 1583 && y <= 9999) years in
+ let fixed : (int * int, 'r entry list) Hashtbl.t = Hashtbl.create 512 in
+ let movable : (int, 'r entry list) Hashtbl.t = Hashtbl.create 32 in
+ let add tbl k e = Hashtbl.replace tbl k (e :: (try Hashtbl.find tbl k with Not_found -> [])) in
List.iter
(fun e ->
- let k = key e.date in
- Hashtbl.replace tbl k (e :: (try Hashtbl.find tbl k with Not_found -> [])))
+ match e.date with
+ | Date_spec.Fixed { month; day } -> add fixed (month, day) e
+ | _ ->
+ (* A spec resolving to nothing in a given year is not an error:
+ [Date_spec.resolve]'s own [None] means "does not occur this
+ year", the same contract 29 February has always had. *)
+ List.iter
+ (fun year ->
+ match Date_spec.resolve e.date ~year ~easter:(easter year) with
+ | Some d -> add movable (Date.to_rata d) e
+ | None -> ())
+ years)
t.entries;
- (* restore canonical order within each date bucket *)
- Hashtbl.iter (fun k v -> Hashtbl.replace tbl k (canonical v)) tbl;
- tbl
+ Hashtbl.iter (fun k v -> Hashtbl.replace fixed k (canonical v)) fixed;
+ Hashtbl.iter (fun k v -> Hashtbl.replace movable k (canonical v)) movable;
+ { fixed; movable }
-let on_date tbl ~month ~day =
- try Hashtbl.find tbl (month, day) with Not_found -> []
+let on_date idx date =
+ let f = try Hashtbl.find idx.fixed (Date.month date, Date.day date) with Not_found -> [] in
+ let m = try Hashtbl.find idx.movable (Date.to_rata date) with Not_found -> [] in
+ match m with [] -> f | _ -> canonical (f @ m)
let load rank_of_sexp path =
match Sexplib.Sexp.load_sexp path with