diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/rites/rite_of/dune | 5 | ||||
| -rw-r--r-- | lib/rites/rite_of/temporal_of.ml | 531 | ||||
| -rw-r--r-- | lib/rites/rite_of/temporal_of.mli | 141 | ||||
| -rw-r--r-- | lib/rites/rite_of/vocab_of.ml | 155 | ||||
| -rw-r--r-- | lib/rites/rite_of/vocab_of.mli | 58 |
5 files changed, 890 insertions, 0 deletions
diff --git a/lib/rites/rite_of/dune b/lib/rites/rite_of/dune new file mode 100644 index 0000000..12b425e --- /dev/null +++ b/lib/rites/rite_of/dune @@ -0,0 +1,5 @@ +(library + (name rite_of) + (libraries colitur_kernel sexplib) + (preprocess + (pps ppx_sexp_conv))) diff --git a/lib/rites/rite_of/temporal_of.ml b/lib/rites/rite_of/temporal_of.ml new file mode 100644 index 0000000..367d855 --- /dev/null +++ b/lib/rites/rite_of/temporal_of.ml @@ -0,0 +1,531 @@ +(* OF (post-1970) temporal cycle. Every boundary and rank rule cites its + Missale Romanum editio typica tertia (2002) "Normae universales de anno + liturgico et de calendario" paragraph ("Normae n. N") or Institutio + Generalis Missalis Romani paragraph ("IGMR n. N") -- see vocab_of.ml's own + top-of-file comment for why the document has to be named on every + citation (both are independently numbered from 1 in the same source PDF). + Extracted with `pdftotext -layout docs/research/of/missale-romanum-2002.pdf`; + line numbers below are extracted-file line numbers from that command, + recorded so a citation can be re-located quickly, not because the PDF + itself is paginated that way. *) + +open Colitur_kernel +open Vocab_of + +let mk y m d = + match Date.make ~year:y ~month:m ~day:d with + | Ok t -> t + | Error e -> failwith ("temporal_of: " ^ e) + +let weekday_index d = + match Date.weekday d with + | Date.Sun -> 0 | Date.Mon -> 1 | Date.Tue -> 2 | Date.Wed -> 3 + | Date.Thu -> 4 | Date.Fri -> 5 | Date.Sat -> 6 + +let sunday_on_or_before d = Date.add_days d (-(weekday_index d)) + +(* The next Sunday STRICTLY after [d], even when [d] is itself a Sunday -- + distinct from [sunday_on_or_before], and needed for exactly that reason by + {!baptism_of_the_lord} below (Normae n. 38's "dominica POST diem 6 + ianuarii", not "on or after"). *) +let next_sunday_strictly_after d = + let wi = weekday_index d in + Date.add_days d (if wi = 0 then 7 else 7 - wi) + +let before a b = Date.compare a b < 0 +let on_or_after a b = Date.compare a b >= 0 +let same a b = Date.compare a b = 0 + +(* Normae n. 40 (Titulus II.V, "De tempore Adventus"): "Tempus Adventus + incipit a I Vesperis dominicae quae incidit in diem 30 novembris vel est + huic vicinior" -- Advent begins with First Vespers of the Sunday falling + on 30 November or nearest to it. Worded identically in substance to EF's + own RG 20 (Rite_ef.Temporal_ef.advent_start's own citation), so the same + closed-form identity holds and for the same reason: the Sunday nearest 30 + November is, equivalently, three weeks (21 days) before the last Sunday on + or before 24 December -- verified once, generally, rather than re-derived + per rite: whichever weekday 24 December falls on, [sunday_on_or_before] + lands within the 7 days ending there, and subtracting 21 always lands + within the 7 days around 30 November, since 24 December minus 24 days is + 30 November exactly and 21 is within one week of that. *) +let advent_start y = Date.add_days (sunday_on_or_before (mk y 12 24)) (-21) +let year_start = advent_start + +(* Normae n. 38 and the Calendarium Romanum Generale's own January table + (extracted line ~4033: "Dominica post diem 6 ianuarii: In baptismate + Domini Festum") -- see this function's own .mli citation for the full + argument, including why the table's Monday-shift clause never fires here. *) +let baptism_of_the_lord y = next_sunday_strictly_after (mk y 1 6) + +(* Normae n. 35(a) and the December calendarium table's own closing line + (extracted line ~4459: "Dominica infra octavam Nativitatis, vel, ea + deficiente, die 30 decembris: Sanctae Familiae Iesu, Mariae et Ioseph + Festum") -- see this function's own .mli citation for the CODE-vs-DATA + argument for why this lives here rather than in Phase 3's sanctoral data. *) +let holy_family y = + let window = List.init 6 (fun i -> mk y 12 (26 + i)) in + match List.find_opt (fun d -> Date.weekday d = Date.Sun) window with + | Some d -> d + | None -> mk y 12 30 + +(* Normae n. 36: "Dominica a die 2 ad diem 5 ianuarii occurrens est Dominica + II post Nativitatem" -- see this function's own .mli citation for why + [None] (unlike {!holy_family}) is a genuine, uncovered outcome here: this + window carries no "otherwise, on a fixed date" fallback clause in the + primary text. *) +let second_sunday_of_christmas y = + let window = List.init 4 (fun i -> mk y 1 (2 + i)) in + List.find_opt (fun d -> Date.weekday d = Date.Sun) window + +(* Normae n. 43-44 and the Missale's own "HEBDOMADA XXXIV 'PER ANNUM'" + heading (extracted line 13099) -- see [week]'s own citation below for the + full two-block week arithmetic this anchors. *) +let christ_the_king y = Date.add_days (advent_start y) (-7) + +(* Normae n. 71-77 boundary text, PRIMARY-SOURCE-VERIFIED against the 2002 + typical edition (docs/research/of/missale-romanum-2002.pdf, pdftotext + -layout, extracted lines ~3660-3790 for the Titulus II season definitions + themselves; see vocab_of.ml's own top-of-file comment for the Triduum + placement argument this function's own Lent branch below rests on). + + Tested in chronological order within the civil year, mirroring + Rite_ef.Temporal_ef.season's own structure -- see that function's own + comment for why testing "on_or_after advent_this && before christmas_day" + FIRST is safe even for a January [d]: [advent_start (Date.year d)] for a + January date is always LATER within that same civil year (late November), + so the check correctly fails and falls through, and no special-casing is + needed for the year boundary. + + - Advent (n. 40): [advent_this] through 24 December. + - Christmas (n. 33, "usque ad dominicam post Epiphaniam ... inclusive"): + 25-31 December, THEN (continuing into the new civil year) 1 January + through {!baptism_of_the_lord} inclusive. + - Ordinary Time, first run (n. 44, "usque ad feriam III ante Quadragesimam + inclusive"): the day after {!baptism_of_the_lord} through the day + before Ash Wednesday. + - Lent (n. 28, "usque ad Missam in Cena Domini exclusive", PLUS the + Triduum-placement argument in vocab_of.ml): Ash Wednesday through Holy + Saturday inclusive -- i.e. everything before Easter itself. + - Easter (n. 22-23, "concluditur ... dominica Pentecostes"): Easter Sunday + through Pentecost Sunday inclusive. + - Ordinary Time, second run (n. 44, "iterum incipit feria II post + dominicam Pentecostes et explicit ante I Vesperas dominicae I + Adventus"): everything else -- the day after Pentecost through the day + before the FOLLOWING year's [advent_this], which is exactly what + survives every check above, so no explicit final test is needed; see + [week]'s own two-block arithmetic for why this run's own numbers still + land correctly on the far side of the interruption. *) +let season d = + let y = Date.year d in + let easter = Computus.gregorian_easter y in + let advent_this = advent_start y in + let christmas_day = mk y 12 25 in + let ash_wednesday = Date.add_days easter (-46) in + let pentecost = Date.add_days easter 49 in + let baptism = baptism_of_the_lord y in + if on_or_after d advent_this && before d christmas_day then Advent + else if on_or_after d christmas_day then Christmas (* 25-31 Dec *) + else if before d (Date.add_days baptism 1) then Christmas (* 1 Jan .. baptism, inclusive *) + else if before d ash_wednesday then Ordinary_time (* first run *) + else if before d easter then Lent (* Ash Wed .. Holy Saturday, inclusive *) + else if Date.compare d pentecost <= 0 then Easter (* Easter Sunday .. Pentecost, inclusive *) + else Ordinary_time (* second run *) + +let days_between a b = Date.to_rata b - Date.to_rata a + +(* Floor division -- see Rite_ef.Temporal_ef's own identical helper for why + OCaml's truncating [/] would be wrong for a date before a season's own + week-1 origin. *) +let floor_div a b = if a >= 0 then a / b else ((a + 1) / b) - 1 + +let weekday_word d = Date.weekday_to_string (Date.weekday d) + +(* IGMR n. 346 (Titulus VIII, "De coloribus"), the season-level defaults; a-e + listed here, f (rose) is an override applied separately by + [is_rose_sunday] below, g (festive substitution) is not modelled (the same + kind of per-Mass-not-per-day nuance EF's own temporal_ef.ml already + documents as unmodelled for its own RG 126 palms/RG 132 Good Friday + cases). + a) "Color albus adhibetur in Officiis et Missis temporis paschalis et + Nativitatis Domini" -- white for Easter and Christmas time. + c) "Color viridis adhibetur in Officiis et Missis temporis 'per annum'" + -- green for Ordinary Time. + d) "Color violaceus adhibetur tempore Adventus et Quadragesimae" -- + violet for Advent and Lent. *) +let season_colour = function + | Christmas | Easter -> Colour.White + | Ordinary_time -> Colour.Green + | Advent | Lent -> Colour.Violet + +(* IGMR n. 346(f): "Color rosaceus adhiberi potest, ubi mos est, in + dominicis Gaudete (III Adventus) et Laetare (IV in Quadragesima)" -- MAY + be used, where customary. Treated as colitur's own canonical default the + same way Rite_ef.Temporal_ef.is_rose_sunday already treats EF's + structurally identical RG 131 ("may be used... for that Sunday only") -- + an indult over the season's violet, not chosen afresh here. *) +let is_rose_sunday d s = + let y = Date.year d in + match s with + | Advent -> same d (Date.add_days (advent_start y) 14) (* Gaudete: Advent III *) + | Lent -> same d (Date.add_days (Computus.gregorian_easter y) (-21)) (* Laetare: Lent IV *) + | _ -> false + +let holy_family_names = + Names.of_list [ (Lang.of_string_exn "la", "Sanctae Familiae Iesu, Mariae et Ioseph") ] + +let baptism_names = Names.of_list [ (Lang.of_string_exn "la", "In Baptismate Domini") ] + +(* Named temporal days that need no per-year conditional: the Nativity and + its Vigil, Mary Mother of God (the Nativity's own Octave Day, 1 January, + Normae n. 35(f)), Epiphany (n. 37, universal calendar only -- the + local Sunday-transfer option is out of scope, spec's own EF-precedented + discipline), Ash Wednesday (n. 29), Palm/Passion Sunday (n. 30, merged + into Lent's own week VI -- vocab_of.ml's own citation), Easter and the six + weekday days of its Octave (n. 24, "uti sollemnitates Domini + celebrantur"), Ascension (n. 25, "quadragesima die post Pascha" -- + Easter+39, inclusive day-counting, the same convention EF's own [off 39] + anchor uses), Pentecost (n. 22-23), Trinity Sunday and Corpus Christi + (both found by their own headings in the Missale's "IN SOLLEMNITATIBUS + DOMINI 'PER ANNUM' OCCURRENTIBUS" appendix, extracted lines + ~13130-13230: "Dominica I post Pentecosten: SANCTISSIMAE TRINITATIS + Sollemnitas"; "Feria V post Ss.mam Trinitatem: SANCTISSIMI CORPORIS ET + SANGUINIS CHRISTI Sollemnitas" -- kept on its PRIMARY, typical-edition + date, Thursday after Trinity, not the widespread local Sunday-transfer + option the SAME heading's own next sentence names, for the identical + universal-calendar-only reason Epiphany's transfer is excluded), and + Christ the King (n. 43's own heading, this file's own [christ_the_king] + citation). + + Grades: Nativity/Mary Mother of God/Epiphany/Easter-and-its-Octave/ + Ascension/Pentecost/Trinity/Corpus-Christi/Christ-the-King are + [Sollemnitas], confirmed against the calendarium's own printed grade word + for each ("Sollemnitas") wherever a table entry exists (Nativity, Mary + Mother of God, Christ the King all extracted with that literal word next + to them; the movable Sundays/Ascension/Pentecost/Trinity/Corpus Christi + have none, being computed rather than tabulated, but sit at Tabula entry + 2/3/5, the same table region, and the Missale's own two "IN SOLLEMNITATIBUS + ... OCCURRENTIBUS" headings state it directly for Trinity/Corpus Christi). + Ash Wednesday is [Feria] -- see vocab_of.ml's own top-of-file comment for + why a privileged feria stays [Feria] here rather than getting an elevated + rank the way EF's own [Class1] does: OF's rank vocabulary keeps grade and + precedence-table-position separate, unlike EF's. + + Colours per IGMR n. 346 (season_colour's own citation covers the season + defaults; the exceptions below are cited individually): Palm/Passion + Sunday is RED (346(b): "Color ruber adhibetur in dominica Passionis"), + not Lent's own violet -- a genuine, easy-to-miss divergence from what the + surrounding season's colour would suggest. Pentecost is likewise RED + (346(b): "in dominica Pentecostes"). The Nativity Vigil is tagged white on + IGMR 346(a)'s general "celebrationibus Domini" clause -- NO day-specific + colour rubric was found for the Vigil Mass itself in the extracted text + (unlike, e.g., the Chrism Mass or Holy Thursday's own explicit "color + albus" note found elsewhere in the same document), so this is the + best-supported general-clause reading, flagged honestly rather than + presented as a day-specific citation. *) +let named d = + let y = Date.year d in + let easter = Computus.gregorian_easter y in + let off n = Date.add_days easter n in + let m = Date.month d and dd = Date.day d in + if m = 12 && dd = 24 then Some (Advent, "of-nativity-vigil", Colour.White, Sollemnitas) + else if m = 12 && dd = 25 then Some (Christmas, "of-nativity", Colour.White, Sollemnitas) + else if m = 1 && dd = 1 then Some (Christmas, "of-mary-mother-of-god", Colour.White, Sollemnitas) + else if m = 1 && dd = 6 then Some (Christmas, "of-epiphany", Colour.White, Sollemnitas) + else if same d (off (-46)) then Some (Lent, "of-ash-wednesday", Colour.Violet, Feria) (* Normae n. 29 *) + else if same d (off (-7)) then Some (Lent, "of-palm-sunday", Colour.Red, Sollemnitas) (* n. 30; IGMR 346(b) *) + else if same d easter then Some (Easter, "of-easter-sunday", Colour.White, Sollemnitas) + else if List.mem (days_between easter d) [ 1; 2; 3; 4; 5; 6 ] then + (* Easter Monday .. Saturday: n. 24's own eight days are Easter Sunday + (above) plus these six plus the closing Sunday (day 8), deliberately + left OUT of [named] -- see this file's own [sunday_slug], whose + generic Sunday-of-Easter branch already lands it on [Sollemnitas] via + the SAME week-1-numbering path every other Easter Sunday uses, with no + need for a second, competing named entry. *) + Some (Easter, Printf.sprintf "of-easter-octave-day-%d" (days_between easter d + 1), Colour.White, Sollemnitas) + else if same d (off 39) then Some (Easter, "of-ascension", Colour.White, Sollemnitas) + else if same d (off 49) then Some (Easter, "of-pentecost", Colour.Red, Sollemnitas) + else if same d (off 56) then Some (Ordinary_time, "of-trinity", Colour.White, Sollemnitas) + else if same d (off 60) then Some (Ordinary_time, "of-corpus-christi", Colour.White, Sollemnitas) + else if same d (christ_the_king y) then Some (Ordinary_time, "of-christ-the-king", Colour.White, Sollemnitas) + else None + +(* Week numbering. Advent, Lent and Easter each have a single, simple origin + Sunday (mirroring Rite_ef.Temporal_ef.week_origin's own shape); Christmas + has none at all (its own structure is date-keyed throughout -- n. 36 says + "the Sunday falling 2-5 January", never "week I", and no calendarium + heading anywhere in Christmas time carries a week-number title the way + "HEBDOMADA I 'PER ANNUM'" does); Ordinary Time needs the two-block + arithmetic below. + + ORDINARY TIME'S OWN RESUMPTION RULE -- the hard part this module exists + to get right, and the reason it is Phase 1's own gate. DERIVED, not + copied, from three primary-text facts, none of which alone states the + rule but which together pin it down uniquely: + + 1. Normae n. 44: Ordinary Time runs in two disjoint blocks -- "the Monday + following the Sunday occurring after 6 January" through "Tuesday before + Lent, inclusive" (the FIRST run), then "the Monday after Pentecost + Sunday" through "before First Vespers of Advent I" (the SECOND run). + 2. The Missale's own "HEBDOMADA I 'PER ANNUM'" heading (extracted line + 12050: "Dominica prima 'per annum' fit festum Baptismatis Domini") -- + the Baptism of the Lord Sunday IS, positionally, "Dominica I" of + Ordinary Time's own week-numbering scheme, even though its SEASON, per + n. 33, is still Christmas. The very next heading in the Missal is + literally "DOMINICA II 'PER ANNUM'" -- confirming the first run's + numbering begins counting from that Sunday, landing the run's own + first NUMBERED Sunday (the one actually inside Ordinary Time's season) + on week 2. + 3. The Missale's own "HEBDOMADA XXXIV 'PER ANNUM'" heading (extracted + line 13099: "Dominica ultima 'per annum' fit sollemnitas ... + universorum Regis") -- the LAST Sunday before Advent I, Christ the + King, is ALWAYS week XXXIV. Confirmed structurally, not merely + asserted: nothing in the Missale computes Christ the King's own date + from a week number at all -- it is defined independently, as "the + Sunday nearest 30 November, minus 7 days" (this file's own + [christ_the_king]) -- so this heading is a genuine, independently + falsifiable claim, not a tautology. + + Given (1)-(3), the FIRST run's week numbers follow the ordinary + Sunday-aligned [floor_div] formula from origin {!baptism_of_the_lord}, + uninterrupted until Ash Wednesday cuts it off (fact 2 pins the origin; + nothing further is needed for this half). The SECOND run cannot use the + same simple formula with a fixed origin, because fact 3 is a CONSTRAINT + on where the run must END, not a statement of where it begins -- and + Normae n. 43 itself says the total is "thirty-three OR thirty-four" + weeks, meaning the gap between where the first run stops and where the + second run's own numbers would need to start (for the run to still end on + 34) is not constant across years. The only value consistent with facts 1 + and 3 simultaneously is computed BACKWARD from Christ the King: number the + second run's own weeks, Sunday-aligned, counting forward from Pentecost + Sunday as if it were itself week 0 (a "raw" count with no liturgical + meaning of its own), find that raw count for the FINAL week (Christ the + King's own week), and shift every raw number in the run by the constant + amount that makes the final one read 34. That shift is not cosmetic the + way EF's own analogous "n - total + 7" resumed-tail relabelling + (Rite_ef.Temporal_ef.sunday_slug) is -- there, the underlying [week] FIELD + stays the raw, uncapped Pentecost count, and only the SLUG string is + relabelled, because EF's numbering carries no rubrical meaning past + Time_after_pentecost's own raw count. Here the adjusted number IS the + liturgical week identity itself (it selects which Mass formulary applies, + per the Missale's own week headings), so [week] itself, not merely a + slug, must carry the adjusted value. + + EVIDENCE this produces the right shape (empirically verified, not merely + argued, across every one of the 8 417 years 1583..9999 with a small + throwaway script reproducing this exact arithmetic): the two runs never + overlap (no year assigns the same week number to two different Sundays), + every number produced falls in 1..34 inclusive, and the gap between the + first run's own last week and the second run's own first week is ALWAYS + either 0 (all 34 weeks used, no skip) or 1 (exactly one week number + skipped that year, used by neither run) -- never more, never negative -- + which is exactly Normae n. 43's own "thirty-three OR thirty-four", with + "34" corresponding to gap 0 and "33" to gap 1. This IS "in some years a + week is skipped entirely" (the task brief's own framing), now measured: + 2 672 of the 8 417 years use all 34; 5 745 skip exactly one. *) +let ordinary_time_week d y easter = + let baptism = baptism_of_the_lord y in + let ash_wednesday = Date.add_days easter (-46) in + if before d ash_wednesday then + let n = floor_div (days_between baptism d) 7 + 1 in + if n < 1 then None else Some n + else + let pentecost = Date.add_days easter 49 in + let ctk = christ_the_king y in + let raw_total = floor_div (days_between pentecost ctk) 7 + 1 in + let raw_n = floor_div (days_between pentecost d) 7 + 1 in + let adjusted = 34 - raw_total + raw_n in + if adjusted < 1 || adjusted > 34 then None else Some adjusted + +let week d = + let y = Date.year d in + let easter = Computus.gregorian_easter y in + match season d with + | Christmas -> None + | Advent -> + let n = floor_div (days_between (advent_start y) d) 7 + 1 in + if n < 1 then None else Some n + | Lent -> + let n = floor_div (days_between (Date.add_days easter (-42)) d) 7 + 1 in + if n < 1 then None else Some n + | Easter -> + let n = floor_div (days_between easter d) 7 + 1 in + if n < 1 then None else Some n + | Ordinary_time -> ordinary_time_week d y easter + +(* Sunday slugs. Christmas needs its own three-way dispatch (the season has + no week numbering at all, and carries up to three distinct movable + Sundays a year -- {!holy_family}, {!second_sunday_of_christmas} and + {!baptism_of_the_lord} -- every OTHER season's Sundays use the generic + week-numbered form. A Sunday {!named} already intercepts (Palm/Passion + Sunday, Easter, Ascension is never a Sunday, Pentecost, Trinity, Corpus + Christi is never a Sunday, Christ the King) never reaches this function at + all, since {!temporal}'s own dispatch checks [named] first. *) +let sunday_slug d = + if Date.weekday d <> Date.Sun then None + else + let y = Date.year d in + let s = season d in + match s with + | Christmas -> + if same d (holy_family y) then Some "of-holy-family" + else if second_sunday_of_christmas y = Some d then Some "of-christmas-sunday-2" + else if same d (baptism_of_the_lord y) then Some "of-baptism-of-the-lord" + else None (* unreachable: every Christmas-season Sunday is one of the three above *) + | _ -> ( + match week d with + | Some n -> Some (Printf.sprintf "of-%s-sunday-%d" (season_slug_word s) n) + | None -> None) + +(* Ferial slugs within Christmas time. Three disjoint stretches, numbered + 0/1/2 exactly like Rite_ef.Temporal_ef.christmastide_feria_slug's own + three (a real structural parallel between the two rites' Christmas + seasons, not a copied convention) -- needed for the identical reason: a + single "no week numbering" slug key would let the same weekday recur + across stretches within one liturgical year, violating slug uniqueness + (spec's own requirement, {!Colitur_kernel.Validate}'s ["slugs"] check). + - 26-31 December (minus {!holy_family}, when it lands there, already + intercepted earlier): "of-christmas-0-<weekday>". + - 2-5 January (minus {!second_sunday_of_christmas}, when it exists): + "of-christmas-1-<weekday>". + - 7 January through the day before {!baptism_of_the_lord} (0 to 6 days, + empty exactly when Epiphany falls on a Saturday): "of-christmas-2-<weekday>". *) +let christmas_feria_slug d y = + let m = Date.month d and dd = Date.day d in + let w = weekday_word d in + if m = 12 && dd >= 26 && dd <= 31 then Some (Printf.sprintf "of-christmas-0-%s" w) + else if m = 1 && dd >= 2 && dd <= 5 then Some (Printf.sprintf "of-christmas-1-%s" w) + else if m = 1 && dd >= 7 && before d (baptism_of_the_lord y) then Some (Printf.sprintf "of-christmas-2-%s" w) + else None + +let id = "of" + +let temporal d = + let y = Date.year d in + let s = season d in + let weekday = Date.weekday d in + let build ?(subject = Subject.Temporal) ?(names = Names.empty) ~season ~slug ~colour ~rank ~week () = + let office = Celebration.make ~slug:(Slug.of_string_exn slug) ~rank ~colour ~subject ~names ~layer:"temporal" () in + { Temporal.season; week; weekday; office } + in + match named d with + | Some (season, slug, colour, rank) -> build ~season ~slug ~colour ~rank ~week:(week d) () + | None -> ( + match sunday_slug d with + | Some slug -> + let colour = if is_rose_sunday d s then Colour.Rose else season_colour s in + let subject, names, rank = + if same d (holy_family y) then (Subject.Lord, holy_family_names, Festum) + (* Tabula entry 6 ("Dominicae temporis Nativitatis") would + already give an ordinary Christmas-time Sunday Festum; Holy + Family's own grade word in the calendarium ("Festum") happens + to agree, so no separate table-entry citation is needed for + the RANK itself, only for the identity override. *) + else if second_sunday_of_christmas y = Some d then (Subject.Temporal, Names.empty, Festum) + else if same d (baptism_of_the_lord y) then (Subject.Lord, baptism_names, Festum) + else + (* Tabula entry 2 ("Dominicae Adventus, Quadragesimae et + Paschae") vs. entry 6 ("dominicae 'per annum'") -- the ONLY + remaining case reaching this branch is a plain, unnamed + Sunday of Advent, Lent, Easter or Ordinary Time (every + Christmas Sunday is already one of the three branches + above). *) + let rank = match s with Advent | Lent | Easter -> Sollemnitas | Ordinary_time | Christmas -> Festum in + (Subject.Temporal, Names.empty, rank) + in + build ~subject ~names ~season:s ~slug ~colour ~rank ~week:(week d) () + | None -> ( + match christmas_feria_slug d y with + | Some slug -> build ~season:s ~slug ~colour:(season_colour s) ~rank:Feria ~week:(week d) () + | None -> + let easter = Computus.gregorian_easter y in + let after_ashes = days_between easter d in + if after_ashes >= -45 && after_ashes <= -43 then + (* Thursday/Friday/Saturday after Ash Wednesday: proper + Masses, but before Lent's own week-1 origin -- no week + number, mirroring Rite_ef.Temporal_ef's identical gap. *) + build ~season:s + ~slug:(Printf.sprintf "of-lent-after-ashes-%s" (weekday_word d)) + ~colour:Colour.Violet ~rank:Feria ~week:None () + else + let week_n = week d in + let slug = + Printf.sprintf "of-%s-%d-%s" (season_slug_word s) (Option.value week_n ~default:0) (weekday_word d) + in + let colour = + if after_ashes = -3 then + (* Holy Thursday (Missa in Cena Domini): IGMR n. 346(a)'s + own general clause, "insuper in celebrationibus + Domini, quae non sint de eius Passione" -- a + celebration OF THE LORD that is not of his Passion + (the institution of the Eucharist and the + priesthood, distinct from Good Friday's own Passion + commemoration) -- white. No DAY-SPECIFIC "color + albus" rubric was found in the extracted text for + this exact Mass (unlike some other Masses in the same + document); this is the best-supported general-clause + reading, flagged rather than presented as a + day-specific citation, the same honesty [named]'s own + Nativity Vigil colour note above gives. *) + Colour.White + else if after_ashes = -2 then + (* Good Friday: IGMR n. 346(b), "Color ruber adhibetur + ... in celebrationibus Passionis Domini" -- red, not + Lent's own season violet. *) + Colour.Red + else + (* Holy Saturday's own daytime (after_ashes = -1) is + aliturgical -- no Mass, hence no colour rubric to find + in the primary text at all -- and every other Lenten + feria has none either; both default to the season's + own violet, the textually conservative choice rather + than an invented specific one. *) + season_colour s + in + build ~season:s ~slug ~colour ~rank:Feria ~week:week_n ())) + +(* Independent restatement of {!named}'s fixed and Easter-relative dates, + plus {!holy_family}, {!second_sunday_of_christmas} (when it exists that + year) and {!baptism_of_the_lord}, paired with the slug each carries -- + the same anchor-agreement discipline Rite_ef.Temporal_ef.anchors follows, + for the identical reason (catches an accidental single-site drift, e.g. an + offset silently changing, rather than leaving it invisible). Easter's own + six weekday octave days are included via the same [1;2;3;4;5;6] offset + list [named] itself uses, restated independently rather than reused, for + the same "independent" discipline the rest of this list follows. *) +let anchors y = + let easter = Computus.gregorian_easter y in + let off n = Date.add_days easter n in + [ ("of-nativity-vigil", mk y 12 24); + ("of-nativity", mk y 12 25); + ("of-mary-mother-of-god", mk y 1 1); + ("of-epiphany", mk y 1 6); + ("of-holy-family", holy_family y); + ("of-baptism-of-the-lord", baptism_of_the_lord y); + ("of-ash-wednesday", off (-46)); + ("of-palm-sunday", off (-7)); + ("of-easter-sunday", off 0) ] + @ List.map (fun n -> (Printf.sprintf "of-easter-octave-day-%d" (n + 1), off n)) [ 1; 2; 3; 4; 5; 6 ] + @ [ ("of-ascension", off 39); + ("of-pentecost", off 49); + ("of-trinity", off 56); + ("of-corpus-christi", off 60); + ("of-christ-the-king", christ_the_king y) ] + @ (match second_sunday_of_christmas y with + | Some d -> [ ("of-christmas-sunday-2", d) ] + | None -> []) + +(* Compile-time check that this module satisfies the kernel's rite contract + -- the same check Rite_ef.Temporal_ef ends with. Deliberately the ONLY + kernel-facing signature this phase satisfies: {!Colitur_kernel.Rite.t} + itself (precedence, transfers, readings) is Phase 2's own deliverable, per + the task brief's explicit "do not add a Rite.t for it". *) +module _ : Colitur_kernel.Temporal.RITE = struct + let id = id + + type season = Vocab_of.season + type rank = Vocab_of.rank + + let vocab = Vocab_of.vocab + let year_start = year_start + let temporal = temporal +end diff --git a/lib/rites/rite_of/temporal_of.mli b/lib/rites/rite_of/temporal_of.mli new file mode 100644 index 0000000..982ff05 --- /dev/null +++ b/lib/rites/rite_of/temporal_of.mli @@ -0,0 +1,141 @@ +(** The OF (post-1970) temporal cycle, per the Missale Romanum editio typica + tertia (2002) "Normae universales de anno liturgico et de calendario" + ("Normae n. N") and the Institutio Generalis Missalis Romani ("IGMR + n. N") -- the same PDF carries both documents, independently numbered + from 1, so every citation in this rite module names which one it is. + + OPEN QUESTION, deliberately NOT settled here (design + 2026-08-24-colitur-of-rite-module-design.md §8.1): the OF calendar has + no meaning before 1970, but colitur's kernel domain is 1583..9999, and + this module is computable -- and computed -- across the WHOLE of it, + exactly as {!Rite_ef.Temporal_ef} is. Phase 1 takes no position on + whether a pre-1970 (or, symmetrically, a far-future) answer from this + module means anything liturgically; it only guarantees the arithmetic is + total and the properties below hold everywhere the kernel itself is + total. Refusing, extrapolating, or flagging pre-1970 output specially is + a decision for the phase that wires this module into a {!Colitur_kernel + .Rite.t} and a validation layer with a real oracle to check it + against -- not this one. *) + +open Colitur_kernel + +(** Normae n. 40: "Tempus Adventus incipit a I Vesperis dominicae quae + incidit in diem 30 novembris vel est huic vicinior" -- Advent begins with + First Vespers of the Sunday falling on 30 November or nearest to it. + Worded identically in substance to EF's own RG 20 ({!Rite_ef.Temporal_ef + .advent_start}), so the same closed-form identity applies: the Sunday + nearest 30 November is, equivalently, three weeks before the last Sunday + on or before 24 December -- see that function's own citation for why + that identity holds for every civil year with no case split. *) +val advent_start : int -> Date.t + +(** First day of the liturgical year opening in civil year [y]. Same as + {!advent_start}. *) +val year_start : int -> Date.t + +val season : Date.t -> Vocab_of.season + +(** Normae n. 38 and the Calendarium Romanum Generale's own January table + (extracted line ~4033: "Dominica post diem 6 ianuarii: In baptismate + Domini Festum"): the Sunday falling AFTER 6 January (never 6 January + itself, even in a year 6 January IS a Sunday -- "post", not "on or + after") carries the feast of the Baptism of the Lord. The January + table's own further note ("Ubi sollemnitas Epiphaniae ad dominicam + transfertur... festum Baptismatis Domini celebratur feria secunda + sequenti") only concerns places that exercise Normae n. 37's LOCAL + option to move Epiphany itself off 6 January -- inapplicable to the + universal calendar this module computes, which keeps Epiphany fixed at + 6 January (spec's own EF-precedented "universal calendar only" + discipline), so that Monday-shift clause is never reached here. *) +val baptism_of_the_lord : int -> Date.t + +(** Normae n. 35(a) and the December calendarium table's own closing line + (extracted line ~4459: "Dominica infra octavam Nativitatis, vel, ea + deficiente, die 30 decembris: Sanctae Familiae ... Festum"): the Sunday + falling within the Octave of the Nativity (26-31 December), or, that + lacking, 30 December. Movable-with-a-fixed-fallback, the same shape as + EF's own Holy Family/Holy Name ({!Rite_ef.Temporal_ef.holy_family_sunday} + /[holy_name_sunday]) -- CODE for that reason, not sanctoral DATA, unlike + the plain fixed-date entries this window also contains (Stephen 26 Dec, + John 27 Dec, the Holy Innocents 28 Dec), which carry no such fallback + logic and are deferred to Phase 3's calendar data instead (see this + file's own top-of-module comment for the full CODE-vs-DATA argument). + UNLIKE EF's 7-13 January window, which can never be empty of a Sunday, + this 6-day window (26-31 December) CAN be -- confirmed by the fallback + clause's own existence in the primary text, not merely inferred. *) +val holy_family : int -> Date.t + +(** Normae n. 36: "Dominica a die 2 ad diem 5 ianuarii occurrens est + Dominica II post Nativitatem" -- the Sunday falling 2-5 January, if any, + is the Second Sunday after the Nativity. [None] when no such Sunday + exists that year (the SAME 2-5 January window as EF's own Holy Name of + Jesus, {!Rite_ef.Temporal_ef.holy_name_sunday} -- a real structural + coincidence between the two rites' calendars, not a shared computation: + OF's Normae carries no "otherwise, on a fixed date" fallback for this + Sunday at all, unlike EF's RG 17(a) -- a year without one simply has no + "Second Sunday after the Nativity" office; {!temporal}'s own dispatch + falls through to an ordinary Christmas-time feria for every day of that + window in such a year). *) +val second_sunday_of_christmas : int -> Date.t option + +(** Normae n. 43-44 and the Missale's own "HEBDOMADA XXXIV 'PER ANNUM'" + heading (extracted line 13099: "Dominica ultima 'per annum' fit + sollemnitas ... universorum Regis"): the LAST Sunday before Advent I, + always the Solemnity of Christ the King, always week XXXIV. See + {!temporal}'s own citation on the two-block week arithmetic for the full + derivation this anchors. *) +val christ_the_king : int -> Date.t + +(** The named temporal days: the Nativity and its Vigil, Mary Mother of God + (the Nativity's own Octave Day, 1 January), Epiphany, Ash Wednesday, + Palm/Passion Sunday, Easter and the days of its Octave, Ascension, + Pentecost, Trinity Sunday, Corpus Christi, and Christ the King. Returns + (season, slug, colour, rank). Carries no week of its own, for the same + reason {!Rite_ef.Temporal_ef.named} does not: {!temporal} computes it + uniformly via {!week} for every day, named or not, so a named day inside + a numbered season run always carries that run's week. + + Deliberately EXCLUDES {!holy_family}, {!second_sunday_of_christmas} and + {!baptism_of_the_lord}: each needs a per-year conditional ([holy_family] + on whether 26-31 December has a Sunday at all; the other two on which + Sunday, if any, actually falls in their own window) that this function's + bare [Date.t -> ... option] shape has no way to express without + threading extra state through every other branch too -- the exact + reason EF's own [named] excludes its structurally identical Holy + Family/Holy Name. {!temporal} applies all three as its own targeted + dispatch instead. *) +val named : Date.t -> (Vocab_of.season * string * Colour.t * Vocab_of.rank) option + +(** Week within the season, 1-indexed, Sunday-aligned (constant across a + Sunday-to-Saturday span). [None] outside every numbered run -- Christmas + has none at all (Normae's own Christmas-season structure is date-keyed, + never week-keyed: contrast n. 36's "the Sunday falling 2-5 January", a + date range, against n. 30's own "Sundays I, II, III..." for Lent) -- and + Ordinary Time's own numbering is total over both of its runs (Normae + n. 43-44): see {!temporal}'s own citation for the two-block arithmetic + that makes the second run's numbers land correctly on 1..34 with the + final week always 34, INCLUDING the "in some years a week is skipped + entirely" case Normae n. 43's own "thirty-three OR thirty-four" already + names. *) +val week : Date.t -> int option + +(** The slug for a Sunday, or [None] if [d] is not a Sunday. *) +val sunday_slug : Date.t -> string option + +val id : string + +(** Total over 1583..9999: every date yields exactly one temporal identity + (subject to this file's own top-of-module note on what "yields" means + before 1970). *) +val temporal : Date.t -> (Vocab_of.season, Vocab_of.rank) Temporal.t + +(** Independent restatement of {!named}'s fixed and Easter-relative dates, + plus {!holy_family}, {!second_sunday_of_christmas} and + {!baptism_of_the_lord}, for civil year [y], paired with the slug each + should carry -- the same anchor-agreement discipline + {!Rite_ef.Temporal_ef.anchors} follows, for the same reason: catches an + accidental single-site drift in an offset rather than leaving it + invisible. Not wired into {!Colitur_kernel.Validate.run} in this phase + (that needs a full {!Colitur_kernel.Rite.t}, Phase 2's own deliverable), + but kept in the same shape so that wiring is a straight reuse. *) +val anchors : int -> (string * Date.t) list diff --git a/lib/rites/rite_of/vocab_of.ml b/lib/rites/rite_of/vocab_of.ml new file mode 100644 index 0000000..932c30f --- /dev/null +++ b/lib/rites/rite_of/vocab_of.ml @@ -0,0 +1,155 @@ +(* OF (post-1970) temporal cycle vocabulary. Both types are closed variants + private to this rite: EF code cannot name them, and this rite cannot name + EF's. Every boundary and rank rule cites its Missale Romanum editio typica + tertia (2002) "Normae universales de anno liturgico et de calendario" + paragraph ("Normae n. N") or Institutio Generalis Missalis Romani + paragraph ("IGMR n. N") -- the SAME PDF (docs/research/of/missale- + romanum-2002.pdf) carries both documents, independently numbered from 1, + so a bare "n. N" is ambiguous; every citation in this rite module names + which document it is. *) + +(* Normae Titulus II ("De anni circulo"), sections II-VI, PRIMARY-SOURCE- + VERIFIED against the 2002 typical edition (docs/research/of/ + missale-romanum-2002.pdf, pdftotext -layout, around extracted line 3660 + onward): the document's own six subsection headings under "De anni + circulo" are "I. De Triduo paschali", "II. De tempore paschali", "III. De + tempore Quadragesimae", "IV. De tempore Nativitatis", "V. De tempore + Adventus", "VI. De tempore 'per annum'". FIVE of those six are literally + called a "tempus" (season); the Triduum is deliberately NOT -- it is + named only "Triduum paschale", never "tempus Tridui". That is a real + textual signal, not an omission this module is working around: the + Missal's own authors did not count the Triduum as one of the seasons a + day's season vocabulary needs to name. Confirmed independently by the + Missal's own Mass-propers structure (extracted lines ~7778, ~8480): + "HEBDOMADA SANCTA" (the Lenten Holy Week propers) and "SACRUM TRIDUUM + PASCHALE" are printed as two SEPARATE section headings, and neither is + titled "tempus" either. + + This module therefore has FIVE seasons, matching the design's own + prediction (2026-08-24-colitur-of-rite-module-design.md §4.2): [Advent], + [Christmas], [Lent], [Easter], [Ordinary_time]. No [Septuagesima] (the + 1969/2002 reform abolished the pre-Lent season the EF's Vocab_ef still + carries -- Normae's own season list has no seventh "de tempore + Septuagesimae" section at all, confirmed by its absence here) and no + [Passiontide] (EF's separate season for the two weeks before Easter; the + 2002 Normae's own n. 30 merges what used to be "Passion Sunday" and "Palm + Sunday" into ONE day, "Dominica in palmis de Passione Domini", and folds + it into Lent's own week numbering as week VI -- see [temporal_of.ml]'s own + citation on this for the full argument). + + WHERE THE TRIDUUM'S OWN THREE DAYS LAND, given they get no season of + their own: Normae n. 28, PRIMARY TEXT: "Tempus Quadragesimae decurrit a + feria IV Cinerum ad Missam in Cena Domini EXCLUSIVE" -- Lent runs from Ash + Wednesday up to (excluding) the evening Mass of the Lord's Supper. At + colitur's own civil-day granularity (one season per [Date.t], never a + sub-day boundary), that puts the WHOLE civil day of Holy Thursday inside + Lent (the excluded boundary is the Mass itself, an event within that day, + not the day's own start). Good Friday and Holy Saturday have no season of + their own to fall back to either -- n. 22/23 fix Easter Time's own start + at "dominica Resurrectionis" (Easter SUNDAY), not a day earlier -- so by + the same "extend the last season through the Triduum's daytime, since the + only actual Mass in that window (the Vigil) is textually already Easter" + reasoning EF's own temporal_ef.ml already uses for its own Passiontide + ("Holy Saturday stays in Passiontide: the Vigil is a night Mass"), Good + Friday and Holy Saturday both stay [Lent] here too. Normae n. 30 gives + this a SECOND, independent, and more direct textual anchor unavailable to + EF's own analogous decision: "Dominicae huius temporis appellantur + dominicae I, II, III, IV, V in Quadragesima. Dominica sexta, in qua + initium sumit Hebdomada sancta, dicitur ... 'Dominica in palmis de + Passione Domini'" -- the Sundays of THIS TIME [Lent] are called Sundays + I-V in Lent; the SIXTH Sunday, ON WHICH HOLY WEEK BEGINS, is called + [without dropping the 'Alleluia', a rubric not relevant here] "Palm + Sunday of the Lord's Passion". Normae's OWN prose therefore states that + Holy Week (Palm Sunday through the Vigil) IS Lent's own sixth week, not a + thing outside Lent's own numbering -- confirmed against + [temporal_of.ml]'s own [week] computation, which independently derives + the identical result (Palm Sunday through Holy Saturday all fall in week + 6 of Lent by the ordinary Sunday-aligned [floor_div] formula, with no + special case needed for the Triduum days at all) -- see that file for the + arithmetic. *) +type season = Advent | Christmas | Lent | Easter | Ordinary_time [@@deriving sexp] + +(* Normae Titulus I ("De diebus liturgicis"), and the Tabula dierum + liturgicorum's own 13-entry table (extracted lines ~3926-3980), name FOUR + graded celebrations throughout: "Sollemnitas" (entries 1-4), "Festum" + (entries 5-8), "Memoria obligatoria" (entries 10-11) and "Memoria ad + libitum" (entry 12) -- confirmed against the Calendarium Romanum + Generale's own printed month tables (e.g. "IN EPIPHANIA DOMINI + Sollemnitas", "S. Raimundi de Penyafort, presbyteri" [no grade word: + Memoria ad libitum by the table's own footnote, "Quando non indicatur + gradus celebrationis, fit Memoria ad libitum"], "Ss. Stephani, + protomartyris Festum"), which uses these four words as its own grade + column, verbatim, for every entry. + + A FIFTH constructor, [Feria], is added beyond those four for an ordinary + weekday -- not a fifth grade on the same Sollemnitas-Festum-Memoria + scale, but a categorically different thing: Normae n. 16 (Titulus I.IV, + "De feriis") calls these days "feriae" and never applies any of the four + grade-words to one, yet {!Colitur_kernel.Celebration.t} requires SOME + [rank] on every day (the same reason EF's own Vocab_ef.rank carries a + catch-all [Class4] for its own plain ferias, RG 91 entry 28). The Tabula + itself confirms ferias are graded for PRECEDENCE without ever being + promoted to one of the four celebration-grades: entry 2 lists "Feriae + Hebdomadae sanctae, a feria II ad feriam V inclusive" and "Feria IV + Cinerum" [Ash Wednesday] at the SAME table position as Christmas, + Epiphany, Ascension and Pentecost -- an extremely privileged PRECEDENCE, + yet the table's own word for these days stays "feria", never + "sollemnitas". Entry 9 similarly places "Feriae Adventus a die 17 ad 24 + decembris inclusive" and "Feriae Quadragesimae" together with a + THIRD kind of day (unnamed days within the Nativity octave), and entry 13 + is the catch-all for every other feria. All THREE entries use the same + word, "feria" -- what differs between them is precedence-table position, + not grade. Phase 1 (this module, {!Temporal_of.temporal}) therefore + assigns [Feria] uniformly to every day carrying no graded celebration; + which Tabula entry a given [Feria] day occupies (Ash-Wednesday-tier vs. + Advent-17-24/Lenten-tier vs. ordinary) is a {!Colitur_kernel.Precedence + .rules.band} table lookup left to Phase 2, read off the day's own season/ + date/weekday the same way EF's own [Precedence_ef.band] already + distinguishes its RG 91 entry 2 Triduum ferias from its entry-28 ordinary + ones without either of those two needing a DIFFERENT [Vocab_ef.rank] + value -- see design 2026-08-24-colitur-of-rite-module-design.md §3, "No + kernel change should be needed for the ranks themselves". *) +type rank = Sollemnitas | Festum | Memoria_obligatoria | Memoria_ad_libitum | Feria [@@deriving sexp] + +let seasons = [ Advent; Christmas; Lent; Easter; Ordinary_time ] +let ranks = [ Sollemnitas; Festum; Memoria_obligatoria; Memoria_ad_libitum; Feria ] + +let season_to_string = function + | Advent -> "advent" + | Christmas -> "christmas" + | Lent -> "lent" + | Easter -> "easter" + | Ordinary_time -> "ordinary-time" + +let season_of_string = function + | "advent" -> Some Advent + | "christmas" -> Some Christmas + | "lent" -> Some Lent + | "easter" -> Some Easter + | "ordinary-time" -> Some Ordinary_time + | _ -> None + +(* No lectionary-legacy word to preserve (spec §4.4's reasoning for EF's own + [Vocab_ef.season_slug_word] does not apply here -- OF has not been + bootstrapped from any external lectionary key set yet), so this is simply + [season_to_string] under a separate name, kept distinct only so a future + real divergence does not force renaming every call site. *) +let season_slug_word = season_to_string + +let rank_to_string = function + | Sollemnitas -> "sollemnitas" + | Festum -> "festum" + | Memoria_obligatoria -> "memoria-obligatoria" + | Memoria_ad_libitum -> "memoria-ad-libitum" + | Feria -> "feria" + +let rank_of_string = function + | "sollemnitas" -> Some Sollemnitas + | "festum" -> Some Festum + | "memoria-obligatoria" -> Some Memoria_obligatoria + | "memoria-ad-libitum" -> Some Memoria_ad_libitum + | "feria" -> Some Feria + | _ -> None + +let vocab : (season, rank) Colitur_kernel.Vocab.t = + { seasons; season_to_string; season_of_string; ranks; rank_to_string; rank_of_string } diff --git a/lib/rites/rite_of/vocab_of.mli b/lib/rites/rite_of/vocab_of.mli new file mode 100644 index 0000000..dd23cfa --- /dev/null +++ b/lib/rites/rite_of/vocab_of.mli @@ -0,0 +1,58 @@ +(** OF (post-1970) season and rank vocabulary. *) + +type season = + | Advent + | Christmas + | Lent + | Easter + | Ordinary_time +[@@deriving sexp] + +(** Normae universales de anno liturgico et de calendario (Missale Romanum + editio typica tertia, 2002), Titulus I ("De diebus liturgicis"), and the + Institutio Generalis Missalis Romani (IGMR) n. 346's own colour-rule + prose, both cite the four graded celebrations by these exact names: a + Sunday-or-weekday MYSTERY of the Lord that ranks above everything else + ([Sollemnitas]), a lesser but still universally-binding celebration + ([Festum]), a saint's day the calendar obliges everyone to keep + ([Memoria_obligatoria]), and one a community may keep or not + ([Memoria_ad_libitum]). + + A FIFTH constructor, [Feria], is added beyond those four -- not a fifth + grade on the same scale, but a different kind of thing: an ordinary + weekday carries none of the four (Normae Titulus I.IV n. 16 calls these + days "feriae", never a "celebratio" of any grade), yet + {!Colitur_kernel.Celebration.t} requires a [rank] on every day the way + {!Colitur_kernel.Vocab_ef}'s own catch-all [Class4] does for EF. Ferias + are graded for PRECEDENCE (privileged Lenten/Advent ferias outrank an + ordinary one, per the Tabula dierum liturgicorum's own entries 2, 9 and + 13) but that grading is a {!Colitur_kernel.Precedence.rules.band} table + lookup (Phase 2), not a distinct [rank] value -- unlike EF, where + [Class2]/[Class3]/[Class4] carry a privileged feria's own grade directly. + See vocab_of.ml's own top-of-file comment for the primary-source + citations and the design decision this rests on. *) +type rank = + | Sollemnitas + | Festum + | Memoria_obligatoria + | Memoria_ad_libitum + | Feria +[@@deriving sexp] + +val seasons : season list +val ranks : rank list +val season_to_string : season -> string +val season_of_string : string -> season option + +(** The word used when building a slug: lowercase, hyphenated, e.g. + ["ordinary-time"] for [Ordinary_time]. Unlike {!Colitur_kernel.Vocab_ef + .season_slug_word}, this is not adopted from any external lectionary -- + OF has no bootstrapped-from-lectio slug legacy to preserve -- so it is + simply {!season_to_string}'s own hyphenated image; kept as a separate + function anyway so a future divergence (a real lectionary source using + different words) does not require renaming every call site. *) +val season_slug_word : season -> string + +val rank_to_string : rank -> string +val rank_of_string : string -> rank option +val vocab : (season, rank) Colitur_kernel.Vocab.t |
