aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/coverage_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 08:49:57 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 08:49:57 +0200
commit70b805018f0ae0d8424c3c7de8f1169476385a35 (patch)
tree82de4bf52e588ab0c1f31ce0fc68d4b97b3d0de6 /internal/calendar/coverage_test.go
parentd3b7f41c7a823bda6b7bafae3a156348c4ce687f (diff)
downloadlectio-70b805018f0ae0d8424c3c7de8f1169476385a35.tar.gz
lectio-70b805018f0ae0d8424c3c7de8f1169476385a35.zip
feat: complete OF+EF calendar coverage (movable feasts, vigils, ember days, transfers)
Ordinary Form: - Add the movable Marian memorials that have no fixed date: Mary, Mother of the Church (Monday after Pentecost) and the Immaculate Heart (Saturday after the Sacred Heart), computed in computeOF. - General Norms 60: two coinciding obligatory memorials both become optional and the ferial is observed -- except Mary, Mother of the Church, which keeps precedence per its 2018 decree. Extraordinary Form: - Capture fixed-date vigils in the sanctoral harvest (Christmas, the Nativity of St John the Baptist, Sts Peter & Paul, St Lawrence, the Assumption, All Saints, the Immaculate Conception) -- previously filtered out. - temporalEF: the movable vigils of the Ascension (I class eve) and Pentecost (Whitsun Eve), and the September/Advent Ember days (II class violet ferias). - transferIfImpededEF: the Annunciation transfers past Holy Week/the Easter octave to the Monday after Low Sunday; All Souls moves to Nov 3 when Nov 2 is a Sunday. - genlect harvests proper Masses for the new Ember/vigil days. Adds TestEFCoverage + TestOFMovableMemorials. Bumps to 0.39.0. Verified per-day: OF 0 real errors vs calapi (2026-2028, 100% correct-observed); EF 0 real errors vs missalemeum (2025-2027), residual = deep-tail vigil-occurrence and St Joseph Passiontide-transfer edges (~1-2 days/yr, documented).
Diffstat (limited to 'internal/calendar/coverage_test.go')
-rw-r--r--internal/calendar/coverage_test.go75
1 files changed, 75 insertions, 0 deletions
diff --git a/internal/calendar/coverage_test.go b/internal/calendar/coverage_test.go
new file mode 100644
index 0000000..e6555b9
--- /dev/null
+++ b/internal/calendar/coverage_test.go
@@ -0,0 +1,75 @@
+package calendar_test
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/caldata"
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+func day(y int, m time.Month, d int) time.Time {
+ return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
+}
+
+// TestEFCoverage checks the EF special days added for full coverage: the movable
+// vigils, the September/Advent Ember days, and the transfer of an impeded feast.
+// (2027: Easter is March 28.)
+func TestEFCoverage(t *testing.T) {
+ sel := calendar.DefaultSelection()
+ sel.Form = "old"
+ layers := []calendar.Layer{caldata.Tridentine()}
+
+ cases := []struct {
+ date time.Time
+ slug string
+ rank calendar.Rank
+ }{
+ {day(2027, time.September, 22), "ef-september-ember-wed", calendar.RankClass2},
+ {day(2027, time.December, 15), "ef-advent-ember-wed", calendar.RankClass2},
+ {day(2027, time.May, 5), "ef-ascension-vigil", calendar.RankClass2},
+ {day(2027, time.May, 15), "ef-pentecost-vigil", calendar.RankClass1},
+ {day(2027, time.June, 23), "vigil-of-the-nativity-of-st-john-the-baptist", calendar.RankClass2},
+ // Annunciation (Mar 25) is in Holy Week in 2027, so it is transferred to
+ // the Monday after Low Sunday (Apr 5); Mar 25 itself keeps the Triduum.
+ {day(2027, time.April, 5), "annunciation-of-the-blessed-virgin-mary", calendar.RankClass1},
+ }
+ for _, c := range cases {
+ got := calendar.Compute(c.date, sel, layers)
+ if got.Observed.Slug != c.slug || got.Observed.Rank != c.rank {
+ t.Errorf("%s: observed %q/%v, want %q/%v", c.date.Format("2006-01-02"),
+ got.Observed.Slug, got.Observed.Rank, c.slug, c.rank)
+ }
+ }
+ // The Annunciation must NOT also appear on its impeded fixed date.
+ if got := calendar.Compute(day(2027, time.March, 25), sel, layers); strings.Contains(got.Observed.Slug, "annunciation") {
+ t.Errorf("2027-03-25 (Holy Thursday) wrongly observes %q", got.Observed.Slug)
+ }
+ // All Souls: Nov 2 2025 is a Sunday, so it is transferred to Nov 3.
+ if got := calendar.Compute(day(2025, time.November, 3), sel, layers); got.Observed.Slug != "commemoration-of-all-souls" {
+ t.Errorf("2025-11-03: observed %q, want the transferred All Souls", got.Observed.Slug)
+ }
+}
+
+// TestOFMovableMemorials checks Mary, Mother of the Church, the Immaculate Heart,
+// and the two-obligatory-memorials rule.
+func TestOFMovableMemorials(t *testing.T) {
+ sel := calendar.DefaultSelection() // Ordinary Form
+ layers := []calendar.Layer{caldata.Universal()}
+
+ // Mary, Mother of the Church: Monday after Pentecost (2027: May 17).
+ if got := calendar.Compute(day(2027, time.May, 17), sel, layers); got.Observed.Slug != "mary-mother-of-the-church" {
+ t.Errorf("2027-05-17: observed %q, want mary-mother-of-the-church", got.Observed.Slug)
+ }
+ // Two obligatory memorials coincide (St Anthony of Padua + the Immaculate
+ // Heart, 2026-06-13): both become optional and the ferial is observed.
+ if got := calendar.Compute(day(2026, time.June, 13), sel, layers); got.Observed.Rank != calendar.RankFerial {
+ t.Errorf("2026-06-13: observed %q/%v, want the ferial", got.Observed.Slug, got.Observed.Rank)
+ }
+ // But Mary, Mother of the Church keeps precedence over a coinciding memorial
+ // (2028-06-05, with St Boniface).
+ if got := calendar.Compute(day(2028, time.June, 5), sel, layers); got.Observed.Slug != "mary-mother-of-the-church" {
+ t.Errorf("2028-06-05: observed %q, want mary-mother-of-the-church", got.Observed.Slug)
+ }
+}