aboutsummaryrefslogtreecommitdiff
path: root/lib/rites/rite_of/temporal_of.mli
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 15:35:59 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 15:35:59 +0200
commit6559a9b19451ce82a7d71281e25899acbcee2d49 (patch)
tree50474427ffa3f0403ce8f42998ec3aaf018af303 /lib/rites/rite_of/temporal_of.mli
parent73cdea4403e02e356e6eb2abaa7a96845d3b050c (diff)
downloadcolitur-6559a9b19451ce82a7d71281e25899acbcee2d49.tar.gz
colitur-6559a9b19451ce82a7d71281e25899acbcee2d49.zip
feat(of): Phase 1 of the OF rite module — vocab_of, temporal_of
The first code for a second rite. lib/rites/rite_of/vocab_of implements the OF's five-way rank vocabulary (Sollemnitas/Festum/Memoria_obligatoria/ Memoria_ad_libitum/Feria) and five-season vocabulary (Advent/Christmas/ Lent/Easter/Ordinary_time), each constructor cited against the 2002 Missale Romanum's Normae universales and the Tabula dierum liturgicorum. temporal_of implements season boundaries, week numbering — including Ordinary Time's own two-block resumption, derived from the Missale's own "HEBDOMADA I/XXXIV 'per annum'" headings and empirically verified across the whole 1583-9999 domain — named temporal days, and Sunday/ferial slugs. Both satisfy Colitur_kernel.Temporal.RITE with no kernel change. No Rite.t is assembled and no CLI wiring is added (Phase 2's own scope); lib/rites/rite_ef and data/ef are untouched, verified byte-identical against the v1.0.0 tag across a domain-spanning sample. test/test_temporal_of.ml adds unit tests for every boundary and named day plus seven QCheck properties (seasons contiguous, Ordinary Time weeks in 1..34, Advent always four Sundays, slug uniqueness, weekday agreement, determinism, consecutive liturgical years partition civil time), with a committed exhaustive sweep mirroring test_validate.ml's own convention. Full account, including the Ordinary Time derivation's evidence and a list of EF concepts that do not carry over, in .superpowers/sdd/of-phase1-report.md (untracked, gitignored).
Diffstat (limited to 'lib/rites/rite_of/temporal_of.mli')
-rw-r--r--lib/rites/rite_of/temporal_of.mli141
1 files changed, 141 insertions, 0 deletions
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