aboutsummaryrefslogtreecommitdiff
path: root/lib/rites/rite_of/temporal_of.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rites/rite_of/temporal_of.ml')
-rw-r--r--lib/rites/rite_of/temporal_of.ml531
1 files changed, 531 insertions, 0 deletions
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