diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-18 16:27:05 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-18 16:27:05 +0200 |
| commit | 2dc44fbbc581430e2038e2ff6d67265fe716791c (patch) | |
| tree | 38348c085adf5937629414c1435e386ca5bc3fa3 /internal | |
| parent | c9bdca22b2c4492fcbb6cca696fe3a8c13713867 (diff) | |
| download | lectio-2dc44fbbc581430e2038e2ff6d67265fe716791c.tar.gz lectio-2dc44fbbc581430e2038e2ff6d67265fe716791c.zip | |
feat(ef): the Mass of Our Lady on Saturday (RG 309(a))
RG 78 gave the day Our Lady's office and its white colour; this gives it her
Mass. RG 309(a): "Missae de sancta Maria in sabbato ... iuxta temporum
diversitatem" -- the Missal prints five formularies and the season selects
among them.
Without it the day was kept as Our Lady's and then read the FERIA's Mass, so a
Saturday in Septuagesima announced Our Lady and read the parable of the
labourers. colitur hit and fixed the identical bug in its own v0.3.0.
The five, with the two seams that are easy to get wrong:
Advent Isa 7:10-15 / Luke 1:26-38
Christmas Time Titus 3:4-7 / Luke 2:15-20
...and on until the Purification (2 February), not merely to the end of
Christmas Time
thereafter Ecclus 24:14-16 / Luke 11:27-28
Paschaltide Ecclus 24:14-16 / John 19:25-27 -- same first reading,
different gospel, which is why the test asserts the gospel separately
Citations are colitur's, verified there against both photographic scans.
The Advent formulary is unreachable as the Saturday office -- Advent has no
IV-class ferias -- and is kept anyway, because RG 309's subject is the Masses
themselves and a missing branch would fall through silently if the rank rules
ever changed.
Guarded on the office rather than the day: a sanctoral feast that outranks the
Saturday keeps its own readings, so the observed slug must still be the
temporal one. All twelve BVM Saturdays of 2026 now agree with colitur on
readings, where none did before.
Diffstat (limited to 'internal')
| -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}, + } +} |
