summaryrefslogtreecommitdiff
path: root/test/test_temporal_ef.ml
diff options
context:
space:
mode:
Diffstat (limited to 'test/test_temporal_ef.ml')
-rw-r--r--test/test_temporal_ef.ml116
1 files changed, 116 insertions, 0 deletions
diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml
index 751abd2..7837fb5 100644
--- a/test/test_temporal_ef.ml
+++ b/test/test_temporal_ef.ml
@@ -167,13 +167,95 @@ let test_resumed_sundays () =
module Cel = Colitur_kernel.Celebration
module Sl = Colitur_kernel.Slug
module Colr = Colitur_kernel.Colour
+module Sub = Colitur_kernel.Subject
let office dt = (T.temporal dt).Colitur_kernel.Temporal.office
let slug_of dt = Sl.to_string (office dt).Cel.slug
let rank_of dt = V.rank_to_string (office dt).Cel.rank
let colour_of dt = Colr.to_string (office dt).Cel.colour
+let subject_of dt = (office dt).Cel.subject
let temporal_week dt = (T.temporal dt).Colitur_kernel.Temporal.week
+(* RG 17(b) (Caput III, "De Dominicis"), primary text (scan-verified,
+ docs/research/rules-register.md §4): "festum S. Familiae Iesu, Mariae,
+ Ioseph, celebrandum dominica prima post Epiphaniam" -- the Holy Family is
+ celebrated on the first Sunday after Epiphany, EVERY year (unlike RG
+ 17(a)'s Holy Name, whose narrower 2-5 January window can be empty and
+ carries an explicit "vel ea deficiente, die 2 ianuarii" fallback in the
+ calendarium itself -- 7-13 January can never be empty of a Sunday, so
+ Holy Family needs, and the calendarium carries, no such fallback).
+ [T.holy_family_sunday] is [named]'s own [week_origin Time_after_epiphany]
+ formula, restated for its own citation -- not re-derived a second time.
+
+ Deliberately NOT part of [T.named]: [named] is checked before
+ [T.sunday_slug]'s own Christmastide branch in [T.temporal]'s dispatch, but
+ this feast's slug, rank and colour are EXACTLY what that branch already
+ computes for the 7-13 January Sunday (register's own pre-existing note on
+ [sunday_slug]: "the key stays lectio's") -- the only field that actually
+ needed to change is [subject] (silently always [Temporal] before this),
+ so [T.temporal] applies it as a targeted override inside that branch
+ rather than duplicating slug/rank/colour a second time through [named]'s
+ 4-tuple. *)
+let test_holy_family () =
+ (* 2026: Epiphany (6 Jan) is a Tuesday, so Holy Family falls 11 January --
+ an ordinary instance, no collision with the Baptism (13 Jan). *)
+ Alcotest.(check string) "2026: slug is the ordinary I-post-Epiphany Sunday key"
+ "ef-time-after-epiphany-sunday-1" (slug_of (d 2026 1 11));
+ Alcotest.(check string) "2026: II class" "class-2" (rank_of (d 2026 1 11));
+ Alcotest.(check string) "2026: white" "white" (colour_of (d 2026 1 11));
+ Alcotest.(check bool) "2026: subject is Lord (RG 91 entry 14, \"Festa Domini II \
+ classis\" -- the mystery of the Holy Family touches Christ)"
+ true
+ (subject_of (d 2026 1 11) = Sub.Lord);
+ (* 2019: Epiphany itself is a Sunday, so the first Sunday AFTER it is the
+ LATEST possible date, 13 January -- the one date Holy Family collides
+ with the fixed Commemoration of the Baptism of the Lord (data/ef/
+ sanctoral.sexp: "commemoration-of-the-baptism-of-the-lord", also 13
+ Jan, II class, subject Lord). [T.temporal]'s own resolution of WHO wins
+ that day is {!Rite_ef.Precedence_ef}'s business (band/disposition, see
+ test_precedence_ef.ml), not this function's -- this only proves
+ [T.temporal] itself still identifies 13 January 2019 as Holy Family's
+ own date, unconditionally, regardless of what else might compete for
+ the day once precedence is applied. *)
+ Alcotest.(check bool) "2019: 6 January is a Sunday" true (D.weekday (d 2019 1 6) = D.Sun);
+ Alcotest.(check string) "2019: Holy Family falls on the latest possible date, 13 Jan"
+ "2019-01-13" (D.to_iso8601 (T.holy_family_sunday 2019));
+ Alcotest.(check string) "2019: still the same slug" "ef-time-after-epiphany-sunday-1"
+ (slug_of (T.holy_family_sunday 2019));
+ Alcotest.(check bool) "2019: still subject Lord" true (subject_of (T.holy_family_sunday 2019) = Sub.Lord);
+ (* Every possible weekday for 6 January exercised, proving the formula
+ never leaves the 7-13 window empty (register's own citation above) --
+ not merely the two years already checked above. *)
+ List.iter
+ (fun y ->
+ let hf = T.holy_family_sunday y in
+ Alcotest.(check bool) (Printf.sprintf "%d: Holy Family is a Sunday" y) true
+ (D.weekday hf = D.Sun);
+ Alcotest.(check bool) (Printf.sprintf "%d: Holy Family falls 7-13 January" y) true
+ (D.month hf = 1 && D.day hf >= 7 && D.day hf <= 13))
+ [ 2019; 2020; 2021; 2022; 2023; 2024; 2025; 2026; 2027 ];
+ (* Negative control: an ORDINARY Sunday after Epiphany (the 2nd) stays
+ [subject Temporal] -- the override is scoped to exactly one Sunday a
+ year, not every Time-after-Epiphany-shaped Sunday. *)
+ Alcotest.(check bool) "2026: the following Sunday (18 Jan, II after Epiphany) is NOT Lord"
+ true
+ (subject_of (d 2026 1 18) = Sub.Temporal)
+
+(* Three of [test_holy_family]'s own exhaustive-weekday sample, pinned to
+ their literal ISO dates and independently cross-checked against
+ `date -d <iso> +%A` (the same discipline test_golden.ml's own header
+ describes for its own weekday assertions) -- the earliest possible date
+ (Epiphany a Saturday), the latest (Epiphany a Sunday), and one ordinary
+ middle case, rather than trusting the structural "always 7-13" check
+ above on its own without ever anchoring it to real calendar dates. *)
+let test_holy_family_no_gap_year () =
+ Alcotest.(check string) "2024 (6 Jan a Saturday): earliest possible date, 7 Jan" "2024-01-07"
+ (D.to_iso8601 (T.holy_family_sunday 2024));
+ Alcotest.(check string) "2019 (6 Jan a Sunday): latest possible date, 13 Jan" "2019-01-13"
+ (D.to_iso8601 (T.holy_family_sunday 2019));
+ Alcotest.(check string) "2025 (6 Jan a Monday): 12 Jan" "2025-01-12"
+ (D.to_iso8601 (T.holy_family_sunday 2025))
+
(* Regression for a Task 14 Validate finding, reworked (register finding 4):
[named] no longer carries its own week at all -- [temporal] computes it
uniformly via [week] for every day, named or not -- so "a named day inside
@@ -432,6 +514,36 @@ let test_anchors_erosion_is_caught () =
[ "ef-ascension"; "ef-nativity" ]
(missing_from_anchors ~named_slugs ~anchors:eroded_anchors)
+(* Holy Family's own anchor coverage, kept SEPARATE from the two tests above:
+ it is genuinely NOT one of [T.named]'s outputs (test_holy_family's own
+ comment explains why -- [T.temporal] applies it as a targeted override
+ inside [T.sunday_slug]'s branch instead), so [named_slugs_for_year]'s scan
+ cannot see it, and folding it into [test_anchors_cover_all_named_days]/
+ [test_anchors_erosion_is_caught] would misrepresent what those two are
+ actually proving ("everything [named] produces"). This proves the same
+ two properties directly against [T.holy_family_sunday] instead: the entry
+ is present, AND deleting it is caught -- {!Validate}'s own production
+ anchor check (validate.ml, register/spec §5.7) calls [temporal], not
+ [named], so it is unaffected by this distinction and already covers this
+ entry regardless; this is this file's own unit-level guard against the
+ same erosion. *)
+let test_holy_family_anchor_present_and_erosion_is_caught () =
+ let y = 2026 in
+ let expected_slug = "ef-time-after-epiphany-sunday-1" in
+ let expected_date = T.holy_family_sunday y in
+ Alcotest.(check bool) "the anchor entry exists, at the right date" true
+ (List.mem (expected_slug, expected_date) (T.anchors y));
+ (* Erosion: with the entry struck out, [T.temporal] itself still puts
+ [expected_slug] at [expected_date] (unaffected -- deleting an [anchors]
+ row never touches [temporal]'s own computation, only what is CHECKED
+ against it), so a reader who only trusted [anchors] would no longer be
+ told to look there at all. *)
+ let eroded = List.filter (fun (s, dt) -> not (String.equal s expected_slug && dt = expected_date)) (T.anchors y) in
+ Alcotest.(check bool) "the erosion is caught: the entry no longer appears" false
+ (List.mem (expected_slug, expected_date) eroded);
+ Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion"
+ expected_slug (slug_of expected_date)
+
let test_totality () =
(* Every day of 2026 yields an office without raising. Not a slug
re-validation -- Slug.t is a private string validated on construction,
@@ -453,6 +565,8 @@ let suite_extra =
Alcotest.test_case "nativity octave" `Quick test_nativity_octave;
Alcotest.test_case "week numbers" `Quick test_week_numbers;
Alcotest.test_case "sunday slugs" `Quick test_sunday_slugs;
+ Alcotest.test_case "holy family (RG 17(b))" `Quick test_holy_family;
+ Alcotest.test_case "holy family: no gap year (RG 17(b))" `Quick test_holy_family_no_gap_year;
Alcotest.test_case "week/sunday_slug agree" `Quick test_week_sunday_slug_agree;
Alcotest.test_case "resumed sundays" `Quick test_resumed_sundays;
Alcotest.test_case "ferial slugs and ranks" `Quick test_ferial_slugs_and_ranks;
@@ -465,6 +579,8 @@ let suite_extra =
Alcotest.test_case "totality" `Quick test_totality;
Alcotest.test_case "anchors cover all named days" `Quick
test_anchors_cover_all_named_days;
+ Alcotest.test_case "holy family anchor present, erosion caught" `Quick
+ test_holy_family_anchor_present_and_erosion_is_caught;
Alcotest.test_case "anchors erosion is caught" `Quick test_anchors_erosion_is_caught ]
let suite =