diff options
Diffstat (limited to 'lib/rites/rite_of/temporal_of.ml')
| -rw-r--r-- | lib/rites/rite_of/temporal_of.ml | 531 |
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 |
