diff options
| -rw-r--r-- | internal/caldata/caldata_test.go | 48 | ||||
| -rw-r--r-- | internal/caldata/readings.go | 60 |
2 files changed, 108 insertions, 0 deletions
diff --git a/internal/caldata/caldata_test.go b/internal/caldata/caldata_test.go index 95bb7dc..f993a12 100644 --- a/internal/caldata/caldata_test.go +++ b/internal/caldata/caldata_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/lukaszkasprzak/lectio/internal/calendar" + "time" ) func TestUniversalLoads(t *testing.T) { @@ -473,3 +474,50 @@ func TestTridentineMissalOverrides(t *testing.T) { } } } + +// TestEFBVMSaturdayReadings pins RG 309(a)'s seasonal selection among the five +// Missae de sancta Maria in sabbato. Without it the day was kept as Our Lady's +// office (RG 78) but read the FERIA's Mass -- so a Saturday in Septuagesima +// announced Our Lady and then read the parable of the labourers. +// +// Citations verified by colitur against both photographic scans of the 1962 +// Missal; see its lectionary_ef.ml for the per-formulary scan references. +func TestEFBVMSaturdayReadings(t *testing.T) { + sel := calendar.Selection{Form: "old"} + for _, c := range []struct{ date, first string }{ + {"2026-01-03", "Titus 3:4-7"}, // Christmas Time + {"2026-01-10", "Titus 3:4-7"}, // still Christmas Time + {"2026-02-14", "Ecclus 24:14-16"}, // after the Purification + {"2026-06-20", "Ecclus 24:14-16"}, // Time after Pentecost + {"2026-04-18", "Ecclus 24:14-16"}, // Paschaltide -- same first, DIFFERENT gospel + } { + day, _ := time.Parse("2006-01-02", c.date) + layers := []calendar.Layer{Tridentine()} + ld := calendar.Compute(day, sel, layers) + got := Readings(sel, layers, day, ld) + if len(got) == 0 || got[0].Citation != c.first { + first := "(none)" + if len(got) > 0 { + first = got[0].Citation + } + t.Errorf("%s: first reading = %q, want %q (RG 309(a), the Mass of Our Lady on Saturday)", + c.date, first, c.first) + } + } + // Paschaltide shares its first reading with the rest of the year but has + // its own gospel -- St John at the Cross rather than the woman in the + // crowd. Asserted separately because a first-reading-only test would pass + // with the wrong gospel. + day, _ := time.Parse("2006-01-02", "2026-04-18") + layers := []calendar.Layer{Tridentine()} + got := Readings(sel, layers, day, calendar.Compute(day, sel, layers)) + var gospel string + for _, r := range got { + if r.Part == "gospel" { + gospel = r.Citation + } + } + if gospel != "John 19:25-27" { + t.Errorf("2026-04-18: gospel = %q, want %q (the Paschaltide formulary)", gospel, "John 19:25-27") + } +} diff --git a/internal/caldata/readings.go b/internal/caldata/readings.go index 3e46ccd..62ef1fc 100644 --- a/internal/caldata/readings.go +++ b/internal/caldata/readings.go @@ -2,6 +2,7 @@ package caldata import ( "sort" + "strings" "time" "github.com/lukaszkasprzak/lectio/internal/calendar" @@ -56,6 +57,9 @@ func Readings(sel calendar.Selection, layers []calendar.Layer, date time.Time, d // weekday cycle (I/II). Sunday-slugs and weekday-slugs are disjoint, so // trying both cycle suffixes is unambiguous. if sel.Form == "old" { + if r := efBVMSaturdayReadings(day); len(r) > 0 { + return r + } if r := TemporalReadings("old", day.Observed.Slug); len(r) > 0 { return r } @@ -85,3 +89,59 @@ func Readings(sel calendar.Selection, layers []calendar.Layer, date time.Time, d } return nil } + +// efBVMSaturdayReadings returns the Mass of Our Lady on Saturday, when that is +// the day's office. +// +// RG 78 keeps the office on a Saturday whose feria is IV class (see +// temporal_ef.go, which gives the day its white colour). RG 309(a) then says +// which Mass is said: "Missae de sancta Maria in sabbato ... iuxta temporum +// diversitatem" -- the Missal prints five formularies and the season selects +// among them. Without this the day was kept as Our Lady's but read the feria's +// Mass, so a Saturday in Septuagesima announced Our Lady and then read the +// parable of the labourers. +// +// Citations are colitur's, which verified each against both photographic scans +// of the 1962 Missal; see its lib/rites/rite_ef/lectionary_ef.ml, +// bvm_saturday_citations, for the per-formulary scan references. +// +// The Advent formulary is included and is STRUCTURALLY UNREACHABLE as the +// Saturday office: Advent has no IV-class ferias, so the office never falls +// there. It is kept because RG 309's subject is the Masses themselves, not +// only the days they land on -- the Mass exists -- and because a missing +// branch would silently fall through if the rank rules ever changed. +func efBVMSaturdayReadings(day calendar.LiturgicalDay) []calendar.Reading { + // The office, not merely the day: a Saturday IV-class feria that nothing + // outranked. A sanctoral feast that won the day keeps its own readings, so + // the observed slug must still be the temporal one -- which the office + // deliberately reuses rather than replacing (see temporal_ef.go). + if day.Weekday != time.Saturday || day.Observed.Rank != calendar.RankClass4 { + return nil + } + if !strings.HasPrefix(day.Observed.Slug, "ef-") || !strings.HasSuffix(day.Observed.Slug, "-saturday") { + return nil + } + first, gospel := "", "" + switch day.Season { + case calendar.Advent: + first, gospel = "Isa 7:10-15", "Luke 1:26-38" + case calendar.Christmas: + first, gospel = "Titus 3:4-7", "Luke 2:15-20" + case calendar.TimeAfterEpiphany: + // The Christmastide formulary runs on until the Purification (2 + // February), not merely to the end of Christmas Time. + if m := day.Date.Month(); m == time.January || (m == time.February && day.Date.Day() <= 2) { + first, gospel = "Titus 3:4-7", "Luke 2:15-20" + } else { + first, gospel = "Ecclus 24:14-16", "Luke 11:27-28" + } + case calendar.Easter_: + first, gospel = "Ecclus 24:14-16", "John 19:25-27" + default: // Septuagesima, Lent, Passiontide, Time after Pentecost + first, gospel = "Ecclus 24:14-16", "Luke 11:27-28" + } + return []calendar.Reading{ + {Part: "first", Citation: first}, + {Part: "gospel", Citation: gospel}, + } +} |
