aboutsummaryrefslogtreecommitdiff
path: root/test/test_temporal_ef.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 12:44:15 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-13 12:44:15 +0200
commitdc00d1f730b23fa99cd15ff142ecba515a9fe0b7 (patch)
treee40f7f003008b33104723a6d8856168bbdea7633 /test/test_temporal_ef.ml
parent59fbda87d565ad9b98c2493e9703390cd988609d (diff)
parente45ec1ccce68d9edb9655004c21861d6bfb33a9d (diff)
downloadcolitur-dc00d1f730b23fa99cd15ff142ecba515a9fe0b7.tar.gz
colitur-dc00d1f730b23fa99cd15ff142ecba515a9fe0b7.zip
Merge branch 'ef-holyname-rg110': the Holy Name of Jesus, RG 110
Builds the Most Holy Name of Jesus with the fallback RG 17(a) and the calendarium both specify -- 'secus die 2 ianuarii', 'vel, ea deficiente, die 2 ianuarii' -- which was unimplemented, so in 3619 of the domain's 8417 years a II-class feast did not exist at all. Also retags it as a feast of the Lord per RG 91 entry 14. Builds RG 110's inseparable Peter/Paul commemoration ('in numero orationum computando, pro unica habeantur') across the three calendarium pairs, and adds the 30 June companion, which was missing from lectio's source and from missalemeum's output alike -- corroborated in both photographic scans plus RG 124(c), and added through adjustments.sexp so the bootstrap's provenance stands. 14627 days change, in four shapes, none unclassified. The register's earlier 852-day estimate for RG 110 reconciles exactly as the Lent Ember subset of one shape: 288 + 284 + 280. RG 110(c)'s ordering was implemented backwards at first and corrected: 'huic orationi additur altera' attaches the companion to the trigger's own oration, so it follows it, where (a) and (b) attach to the day's and so precede everything. The 'ante omnes alias commemorationes' half remains unimplemented and is unreachable by construction -- shape (c) needs the trigger to lose while still being admitted, which only a I-class day or a II-class Sunday allows, and both RG 111 caps are one. Records a fifth way the validation layers can be green while wrong: nothing anywhere compares commemoration order.
Diffstat (limited to 'test/test_temporal_ef.ml')
-rw-r--r--test/test_temporal_ef.ml156
1 files changed, 156 insertions, 0 deletions
diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml
index 23a817d..f341533 100644
--- a/test/test_temporal_ef.ml
+++ b/test/test_temporal_ef.ml
@@ -272,6 +272,105 @@ let test_holy_family_no_gap_year () =
Alcotest.(check string) "2025 (6 Jan a Monday): 12 Jan" "2025-01-12"
(D.to_iso8601 (T.holy_family_sunday 2025))
+(* RG 17(a) (Caput III, "De Dominicis"; full text on [T.holy_name_sunday]'s
+ own citation), Sunday shape: "festum Ss.mi Nominis Iesu, celebrandum
+ dominica quae occurrit a die 2 ad 5 ianuarii". Deliberately NOT part of
+ [T.named], same reason as Holy Family (that test's own comment): the
+ slug/rank/colour this branch produces for a plain Sunday 2-5 January
+ already match what [T.sunday_slug]'s Christmastide branch always
+ computed; only [subject]/[names] change. *)
+let test_holy_name_sunday_shape () =
+ (* 2026: 4 January is the Sunday (independently cross-checked against
+ `date -d 2026-01-04 +%u` = 7). Oracle-corroborated: missalemeum's own
+ 2026-01-04 row is rank 2, white, title "Holy Name of Jesus" (the task
+ brief's own example). *)
+ Alcotest.(check string) "2026: slug is the ordinary Holy-Name-window Sunday key" "ef-holy-name-sunday"
+ (slug_of (d 2026 1 4));
+ Alcotest.(check string) "2026: II class" "class-2" (rank_of (d 2026 1 4));
+ Alcotest.(check string) "2026: white" "white" (colour_of (d 2026 1 4));
+ Alcotest.(check bool) "2026: subject is Lord (RG 91 entry 14, \"Festa Domini II classis\")" true
+ (subject_of (d 2026 1 4) = Sub.Lord);
+ Alcotest.(check (option string)) "2026: Latin name from the calendarium/propers, verbatim"
+ (Some "Sanctissimi Nominis Iesu") (name_la_of (d 2026 1 4));
+ Alcotest.(check (option string)) "2026: no English name (deliberate -- would read the oracle it is \
+ compared against)"
+ None (Colitur_kernel.Names.find (office (d 2026 1 4)).Cel.names en);
+ (* Negative controls: every OTHER day in the 2-5 January window that year
+ stays [subject Temporal] -- the override is scoped to exactly the one
+ Sunday [T.holy_name_sunday] names, not the whole window. *)
+ List.iter
+ (fun dd ->
+ Alcotest.(check bool) (Printf.sprintf "2026-01-%02d: NOT Lord (not the Sunday)" dd) true
+ (subject_of (d 2026 1 dd) = Sub.Temporal))
+ [ 2; 3; 5 ];
+ (* 2027: 3 January is the Sunday (`date -d 2027-01-03 +%u` = 7) -- the
+ oracle window's own second instance of the Sunday shape. missalemeum's
+ 2027-01-03 row independently corroborates: rank 2, white, title "Holy
+ Name of Jesus". *)
+ Alcotest.(check bool) "2027: 3 January is a Sunday" true (D.weekday (d 2027 1 3) = D.Sun);
+ Alcotest.(check string) "2027: same slug" "ef-holy-name-sunday" (slug_of (d 2027 1 3));
+ Alcotest.(check bool) "2027: still subject Lord" true (subject_of (d 2027 1 3) = Sub.Lord);
+ (* Cross-checked against [T.holy_name_sunday] itself, independent of the
+ literal dates above. *)
+ Alcotest.(check (option string)) "2026: T.holy_name_sunday agrees" (Some "2026-01-04")
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2026));
+ Alcotest.(check (option string)) "2027: T.holy_name_sunday agrees" (Some "2027-01-03")
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2027))
+
+(* RG 17(a)'s own fallback clause, "(secus die 2 ianuarii)" -- the calendarium
+ confirms it in its own words: "vel, ea deficiente, die 2 ianuarii:
+ Sanctissimi Nominis Iesu, II classis". Unlike Holy Family's 7-13 January
+ window, this narrower 2-5 January window CAN be empty of a Sunday --
+ 3,619 of the 8,417 years in [1583, 9999] -- and colitur emitted NO Holy
+ Name office at all on any of them before this fix (register ยง6.2's own
+ account of the gap this closes). *)
+let test_holy_name_fallback_shape () =
+ (* 2029: no Sunday falls 2-5 January (independently cross-checked: `date -d
+ 2029-01-0{2,3,4,5} +%u` are 2,3,4,5 -- Tuesday through Friday, none a
+ 7). The task brief's own reported example: "2029-01-02
+ ef-christmas-1-tuesday class-4 white (no Sunday in window: feast
+ MISSING)" -- that is the pre-fix behaviour this test pins the fix
+ against. *)
+ Alcotest.(check (option string)) "2029: no Sunday in the window" None
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2029));
+ Alcotest.(check string) "2029: slug is the fallback key" "ef-holy-name" (slug_of (d 2029 1 2));
+ Alcotest.(check string) "2029: II class" "class-2" (rank_of (d 2029 1 2));
+ Alcotest.(check string) "2029: white" "white" (colour_of (d 2029 1 2));
+ Alcotest.(check bool) "2029: subject is Lord, same as the Sunday shape" true
+ (subject_of (d 2029 1 2) = Sub.Lord);
+ Alcotest.(check (option string)) "2029: SAME Latin name as the Sunday shape (one feast, two dates)"
+ (Some "Sanctissimi Nominis Iesu") (name_la_of (d 2029 1 2));
+ (* Negative controls: 3, 4 and 5 January stay ordinary Christmastide
+ ferias in a fallback year -- the fallback names 2 January specifically,
+ not "some day near the start of the window". *)
+ List.iter
+ (fun dd ->
+ Alcotest.(check bool) (Printf.sprintf "2029-01-%02d: NOT the feast (ordinary feria)" dd) true
+ (subject_of (d 2029 1 dd) = Sub.Temporal
+ && Sl.to_string (office (d 2029 1 dd)).Cel.slug <> "ef-holy-name"))
+ [ 3; 4; 5 ];
+ (* A second, independently cross-checked fallback year, at the OTHER end
+ of the week (2008: `date -d 2008-01-02 +%A` = Wednesday), so this is
+ not a single-year accident. *)
+ Alcotest.(check (option string)) "2008: no Sunday in the window either" None
+ (Option.map D.to_iso8601 (T.holy_name_sunday 2008));
+ Alcotest.(check string) "2008: same fallback slug" "ef-holy-name" (slug_of (d 2008 1 2))
+
+(* [T.holy_name_sunday]'s own [Some]/[None] split, cross-checked against
+ `date -d` independently for a sample spanning both shapes -- not merely
+ the two years the shape tests above already pin literally, so the
+ boundary itself (not only two hand-picked instances) is exercised. *)
+let test_holy_name_sunday_or_none () =
+ List.iter
+ (fun (y, expected) ->
+ Alcotest.(check (option string))
+ (Printf.sprintf "%d: holy_name_sunday" y)
+ expected
+ (Option.map D.to_iso8601 (T.holy_name_sunday y)))
+ [ (2026, Some "2026-01-04"); (2027, Some "2027-01-03"); (2009, Some "2009-01-04");
+ (2010, Some "2010-01-03"); (2020, Some "2020-01-05"); (2025, Some "2025-01-05");
+ (2006, None); (2007, None); (2008, None); (2018, None); (2029, None) ]
+
(* 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
@@ -583,6 +682,56 @@ let test_holy_family_anchor_present_and_erosion_is_caught () =
Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion"
expected_slug (slug_of expected_date)
+(* Holy Name of Jesus (RG 17(a)), SUNDAY shape -- same structure as Holy
+ Family's own test immediately above, same reasoning (also not one of
+ [T.named]'s outputs). 2026 is a Sunday-shape year (4 January). *)
+let test_holy_name_sunday_anchor_present_and_erosion_is_caught () =
+ let y = 2026 in
+ let expected_date =
+ match T.holy_name_sunday y with
+ | Some d -> d
+ | None -> Alcotest.fail "sanity: 2026 must have a Sunday in the Holy Name window"
+ in
+ let expected_slug =
+ match T.sunday_slug expected_date with
+ | Some s -> s
+ | None -> Alcotest.fail "sanity: holy_name_sunday must itself be a Sunday"
+ in
+ Alcotest.(check (list string))
+ "the anchor entry exists: T.sunday_slug's own independent computation is not missing from T.anchors" []
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:(anchor_slugs y));
+ let eroded_anchors = List.filter (fun s -> s <> expected_slug) (anchor_slugs y) in
+ Alcotest.(check (list string)) "the erosion is caught: missing_from_anchors reports it, and only it"
+ [ expected_slug ]
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:eroded_anchors);
+ Alcotest.(check string) "sanity: temporal itself is unaffected by the anchors-list erosion"
+ expected_slug (slug_of expected_date)
+
+(* Holy Name of Jesus, FALLBACK shape -- the SAME two properties (presence,
+ erosion caught), for a year with no Sunday in the window. Unlike the
+ Sunday shape above, there is no SECOND independent function (the way
+ [T.sunday_slug] is independent of [T.anchors]) that names "ef-holy-name"
+ for a non-Sunday date -- [expected_slug] is the literal this task chose,
+ cross-checked instead against [T.temporal]'s own output (the function
+ under test) via the final sanity line, same as every other branch of
+ this cross-check, honestly not a second independent source. 2029 has no
+ Sunday 2-5 January (test_holy_name_fallback_shape's own citation). *)
+let test_holy_name_fallback_anchor_present_and_erosion_is_caught () =
+ let y = 2029 in
+ (match T.holy_name_sunday y with
+ | None -> ()
+ | Some _ -> Alcotest.fail "sanity: 2029 must have no Sunday in the Holy Name window");
+ let expected_date = T.holy_name_fallback_date y in
+ let expected_slug = "ef-holy-name" in
+ Alcotest.(check (list string)) "the anchor entry exists for the fallback shape" []
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:(anchor_slugs y));
+ let eroded_anchors = List.filter (fun s -> s <> expected_slug) (anchor_slugs y) in
+ Alcotest.(check (list string)) "the erosion is caught: missing_from_anchors reports it, and only it"
+ [ expected_slug ]
+ (missing_from_anchors ~named_slugs:[ expected_slug ] ~anchors:eroded_anchors);
+ 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,
@@ -606,6 +755,9 @@ let suite_extra =
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 "holy name: Sunday shape (RG 17(a))" `Quick test_holy_name_sunday_shape;
+ Alcotest.test_case "holy name: fallback shape (RG 17(a))" `Quick test_holy_name_fallback_shape;
+ Alcotest.test_case "holy name: Sunday-or-none cross-check" `Quick test_holy_name_sunday_or_none;
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;
@@ -620,6 +772,10 @@ let suite_extra =
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 "holy name (Sunday shape) anchor present, erosion caught" `Quick
+ test_holy_name_sunday_anchor_present_and_erosion_is_caught;
+ Alcotest.test_case "holy name (fallback shape) anchor present, erosion caught" `Quick
+ test_holy_name_fallback_anchor_present_and_erosion_is_caught;
Alcotest.test_case "anchors erosion is caught" `Quick test_anchors_erosion_is_caught ]
let suite =