From 53e0639612a348ddc66e33952d913c5257fe2ba8 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Tue, 11 Aug 2026 14:02:35 +0200 Subject: rite(ef): pin down floor_div and week/sunday_slug agreement Task 12 review: add a permanent regression test for floor_div's negative branch (Ash Wednesday, 4 days before the Lent I origin -- the only date in the system where it fires), and stop sunday_slug from independently recomputing the Pentecost-relative week number that week already computes. A cross-check test pins the two together before the refactor and continues to guard against future duplication. --- lib/rites/rite_ef/temporal_ef.ml | 32 ++++++++++++++++++-------------- test/test_temporal_ef.ml | 26 +++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/lib/rites/rite_ef/temporal_ef.ml b/lib/rites/rite_ef/temporal_ef.ml index 9e8bcc4..6786cd2 100644 --- a/lib/rites/rite_ef/temporal_ef.ml +++ b/lib/rites/rite_ef/temporal_ef.ml @@ -165,20 +165,24 @@ let sunday_slug d = Some "ef-holy-name-sunday" else None | Time_after_pentecost -> ( - let easter = Computus.gregorian_easter y in - let pentecost = Date.add_days easter 49 in - let last_sunday = Date.add_days (advent_start y) (-7) in - let n = days_between pentecost d / 7 in - if same d last_sunday then - (* The last Sunday before Advent always keeps the 24th (Last) Mass. *) - Some "ef-time-after-pentecost-sunday-24" - else if n > 23 then - (* Surplus Sundays resume the Sundays after Epiphany that Septuagesima - cut short -- the highest-numbered ones, so the 6th sits just before - the Last. *) - let total = days_between pentecost last_sunday / 7 in - Some (Printf.sprintf "ef-time-after-epiphany-sunday-%d" (n - total + 7)) - else Some (Printf.sprintf "ef-time-after-pentecost-sunday-%d" n)) + (* Reuse [week] rather than recomputing the Pentecost-relative week + number locally, so the two can never drift apart (see + test_week_sunday_slug_agree). Only the last Sunday and the resumed + tail are genuinely special. *) + match week d with + | None -> None + | Some n -> + let last_sunday = Date.add_days (advent_start y) (-7) in + if same d last_sunday then + (* The last Sunday before Advent always keeps the 24th (Last) Mass. *) + Some "ef-time-after-pentecost-sunday-24" + else if n > 23 then + (* Surplus Sundays resume the Sundays after Epiphany that Septuagesima + cut short -- the highest-numbered ones, so the 6th sits just before + the Last. *) + let total = match week last_sunday with Some t -> t | None -> n in + Some (Printf.sprintf "ef-time-after-epiphany-sunday-%d" (n - total + 7)) + else Some (Printf.sprintf "ef-time-after-pentecost-sunday-%d" n)) | _ -> ( match week d with | Some n -> Some (Printf.sprintf "ef-%s-sunday-%d" (season_slug_word s) n) diff --git a/test/test_temporal_ef.ml b/test/test_temporal_ef.ml index 84ee031..96c7761 100644 --- a/test/test_temporal_ef.ml +++ b/test/test_temporal_ef.ml @@ -100,7 +100,12 @@ let test_week_numbers () = (* Time after Pentecost counts from Pentecost, so Trinity is week 1. *) Alcotest.(check (option int)) "Trinity 2026 is week 1" (Some 1) (T.week (d 2026 5 31)); (* Christmastide has no numbered weeks. *) - Alcotest.(check (option int)) "Christmastide has no week" None (T.week (d 2026 12 30)) + Alcotest.(check (option int)) "Christmastide has no week" None (T.week (d 2026 12 30)); + (* Ash Wednesday is 4 days before the Lent I origin (22 Feb 2026): it belongs + to no numbered week. This is the only place in the system [floor_div]'s + negative branch fires -- guard against a regression to plain [/], which + would wrongly round this up to week 1. *) + Alcotest.(check (option int)) "Ash Wednesday has no week" None (T.week (d 2026 2 18)) let test_sunday_slugs () = Alcotest.(check string) "Advent I" "ef-advent-sunday-1" (sunday_slug_of (T.advent_start 2026)); @@ -115,6 +120,24 @@ let test_sunday_slugs () = Alcotest.(check string) "11 Jan 2026" "ef-time-after-epiphany-sunday-1" (sunday_slug_of (d 2026 1 11)) +(* Cross-check: on an ordinary (non-Last, non-resumed) Sunday after Pentecost, + the number embedded in [sunday_slug] must equal [week]. The two functions + independently compute Pentecost-relative week arithmetic; nothing else pins + them together, so a future rubric fix to one that is not mirrored in the + other would otherwise diverge silently. *) +let test_week_sunday_slug_agree () = + let easter = Colitur_kernel.Computus.gregorian_easter 2026 in + let pentecost = D.add_days easter 49 in + List.iter + (fun n -> + let dt = D.add_days pentecost (7 * n) in + let slug = sunday_slug_of dt in + let embedded = Scanf.sscanf slug "ef-time-after-pentecost-sunday-%d" (fun k -> k) in + Alcotest.(check (option int)) + (Printf.sprintf "week %d after Pentecost matches slug" n) + (Some embedded) (T.week dt)) + [ 1; 5; 10; 15; 20 ] + (* The resumed-Sunday tail: when Easter is early there are more than 23 Sundays after Pentecost, and the surplus resume the Sundays after Epiphany that Septuagesima cut short. 2038 has Easter on 25 April (the latest possible) and @@ -147,6 +170,7 @@ 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 "week/sunday_slug agree" `Quick test_week_sunday_slug_agree; Alcotest.test_case "resumed sundays" `Quick test_resumed_sundays ] let suite = -- cgit v1.3