1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
(* See lectionary_of.mli for the full citations and design argument. *)
open Colitur_kernel
type sunday_cycle = Year_a | Year_b | Year_c
type weekday_cycle = Year_i | Year_ii
let sunday_cycle_letter = function Year_a -> "a" | Year_b -> "b" | Year_c -> "c"
let weekday_cycle_letter = function Year_i -> "i" | Year_ii -> "ii"
(* The civil year in which the liturgical year CONTAINING [date] opened --
i.e. the year y such that [year_start y <= date < year_start (y + 1)].
This is the one piece of arithmetic both cycle rules share, and it is
exactly the "get the boundary right" the task brief names: a date in,
say, early December belongs to the liturgical year that opened THAT
same Advent only once Advent has actually started that year (compared
by real date, not by month) -- a month-based shortcut ("December always
means the new liturgical year") is wrong in the (real, if rare) case of
a December date still ahead of that year's own Advent I. *)
let liturgical_year_open_year ~year_start date =
let y = Date.year date in
if Date.compare date (year_start y) >= 0 then y else y - 1
(* OLM n.66 + footnote 102 (mli's own citation): the LABEL year is the one
most of the liturgical year falls in, i.e. one more than the year
Advent opened in ("a prima hebdomada Adventus, quae cadit in anno
civili praecedente" -- the first week of Advent falls in the PRECEDING
civil year relative to the label). Divisible by 3 -> C, then A, then B,
worked example 1980=C/1981=A/1982=B/1983=C verified against the real
page image. *)
let sunday_cycle ~year_start date =
let label = liturgical_year_open_year ~year_start date + 1 in
match label mod 3 with 0 -> Year_c | 1 -> Year_a | _ -> Year_b
(* OLM n.69 point 4 (mli's own citation): "Annus primus adhibetur annis
imparibus, annus secundus vero annis paribus" -- Year I in odd label
years, Year II in even. Same label-year computation as [sunday_cycle]
above, for the identical reason: this is a two-year cycle, not a
three-year one, but the label-year definition itself does not depend on
which modulus governs it. *)
let weekday_cycle ~year_start date =
let label = liturgical_year_open_year ~year_start date + 1 in
if label mod 2 = 1 then Year_i else Year_ii
(* Tries [base] flat, then [base]-<sunday letter>, then [base]-<weekday
letter>, in that order. Safe unconditionally: tools/
bootstrap_lectionary_of.ml emits a given base under AT MOST ONE of
these three forms (a flat entry when the two/three cycle years were
byte-identical, a cycle-lettered family otherwise -- never both), so at
most one of the three lookups below can ever hit, and trying the two
irrelevant forms for a family of the other kind or a flat family is
simply two wasted, harmless misses. This is what lets [readings] below
stay ignorant of which of the two cycle systems (or neither) a given
slug's own family actually uses. *)
let lookup_any lectionary ~year_start base date =
match Lectionary.find lectionary base with
| Some cs -> Some cs
| None -> (
let with_suffix suffix = Slug.of_string_exn (Slug.to_string base ^ "-" ^ suffix) in
match Lectionary.find lectionary (with_suffix (sunday_cycle_letter (sunday_cycle ~year_start date))) with
| Some cs -> Some cs
| None -> Lectionary.find lectionary (with_suffix (weekday_cycle_letter (weekday_cycle ~year_start date))))
let readings ~lectionary ~year_start ~(observed : Vocab_of.rank Celebration.t)
~(temporal : (Vocab_of.season, Vocab_of.rank) Temporal.t) ~date ~temporal_at:_ =
match observed.Celebration.citations with
| _ :: _ as cs ->
(Some { Mass_formulary.said = Some observed.Celebration.slug; via = Mass_formulary.Proper }, cs)
| [] -> (
(* Step 2: a sanctoral entity actually won the day -- i.e. the
observed celebration is not itself the day's own temporal office.
Same guard shape as Rite_ef.Lectionary_ef.readings' own step 4
(see that module's implementation comment for the full argument
for why this comparison, not a [subject]/[status] test, is the
right one): comparing SLUGS is exact whenever the temporal and
sanctoral streams cannot collide, which holds here for the
identical reason it holds for EF -- every Temporal_of slug this
module ever constructs carries the "of-" prefix (temporal_of.ml's
own [temporal], every branch), and 0 of the 222 shipped
data/of/calendar-2002.sexp + amendments sanctoral slugs do. *)
let sanctoral_office =
not (Slug.equal observed.Celebration.slug temporal.Temporal.office.Celebration.slug)
in
match
if sanctoral_office then lookup_any lectionary ~year_start observed.Celebration.slug date else None
with
| Some cs ->
(* Mass_formulary.Proper, not .Common -- see lectionary_of.mli's
own doc comment on this branch for the full argument. In
short: EF's [Common] constructor names a SPECIFIC mechanism
(several saints sharing one formulary via an explicit
assignment table, data/ef/commons.sexp) that OF's own data has
no counterpart for -- every sanctoral slug this step resolves
carries its OWN dedicated lectio entry, not a shared one, so
relabelling it [Common] would claim a mechanism that is not
actually present. [Proper] is the honest fit: "the observed
celebration's own proper Mass", true regardless of whether the
citations are embedded on Celebration.t (step 1) or held
externally in [lectionary] (this step) -- a storage detail
Mass_formulary.source was never built to carry. *)
(Some { Mass_formulary.said = Some observed.Celebration.slug; via = Mass_formulary.Proper }, cs)
| None -> (
match lookup_any lectionary ~year_start temporal.Temporal.office.Celebration.slug date with
| Some cs ->
( Some { Mass_formulary.said = Some temporal.Temporal.office.Celebration.slug;
via = Mass_formulary.Own_slug },
cs )
| None -> (None, [])))
|