diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-05 22:24:53 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-05 22:24:53 +0200 |
| commit | 4855ef2a667b7ea71092e0582db5aff1838a0d63 (patch) | |
| tree | 098d3a85c503f73d95c551f76e6f9f1d1ef1e208 | |
| parent | 8929ab96ad44b707bafd7dfe6f1f7773843c8e85 (diff) | |
| download | lectio-4855ef2a667b7ea71092e0582db5aff1838a0d63.tar.gz lectio-4855ef2a667b7ea71092e0582db5aff1838a0d63.zip | |
calendar/readings: report Sunday as its own display rank, not solemnity
The Ordinary Form's Table of Liturgical Days treats Sunday as its own
category, never a solemnity. temporal.go's sundayDay() already marks these
days distinctly from named solemnities (Class stays unset, unlike solemn()'s
ClassLord); internal/readings/offline.go now reads that existing signal to
relabel the REPORTED rank to "sunday" for ordinary and privileged-season
Sundays alike, leaving Celebration.Rank, ofRankOrder and all precedence
untouched. Named solemnities landing on a Sunday (Easter, Pentecost, ...)
and feasts of the Lord (Holy Family) keep reporting their own rank. The 1962
form is untouched.
Added calendar.RankSunday (display-only, deliberately excluded from
ofRankOrder), the i18n Sunday/niedziela words, and a test pinning the exact
dates from the original bug report.
| -rw-r--r-- | internal/calendar/types.go | 12 | ||||
| -rw-r--r-- | internal/i18n/golden_test.go | 4 | ||||
| -rw-r--r-- | internal/i18n/lang/en.ini | 1 | ||||
| -rw-r--r-- | internal/i18n/lang/pl.ini | 1 | ||||
| -rw-r--r-- | internal/i18n/vocab_test.go | 6 | ||||
| -rw-r--r-- | internal/readings/offline.go | 48 | ||||
| -rw-r--r-- | internal/readings/readings_test.go | 37 |
7 files changed, 103 insertions, 6 deletions
diff --git a/internal/calendar/types.go b/internal/calendar/types.go index 298e49d..6d828f1 100644 --- a/internal/calendar/types.go +++ b/internal/calendar/types.go @@ -17,6 +17,18 @@ const ( RankFeast Rank = "feast" // RankSolemnity Rank = "solemnity" // + // RankSunday is a DISPLAY-ONLY rank: in the 1969 Universal Norms' Table of + // Liturgical Days, Sunday is its own category, never a solemnity (Sundays + // of Christmas Time/Ordinary Time are band 6, below feasts of the Lord; + // Sundays of Advent/Lent/Easter are band 2, above solemnities -- a flat + // Rank cannot express both bands, and this constant does not try to). + // Nothing in this package ever assigns it to Celebration.Rank, and it MUST + // NOT be added to ofRankOrder below: ofRankOrder's default case (0, + // ferial-level) would make every Sunday lose every precedence contest. + // Callers that want it derive it themselves from the already-computed, + // unchanged Celebration (see internal/readings/offline.go's displayRank). + RankSunday Rank = "sunday" + // Extraordinary Form ranks (1960 Code of Rubrics): I-IV class + commemoration. RankClass1 Rank = "class-1" RankClass2 Rank = "class-2" diff --git a/internal/i18n/golden_test.go b/internal/i18n/golden_test.go index b32a259..d06d63b 100644 --- a/internal/i18n/golden_test.go +++ b/internal/i18n/golden_test.go @@ -27,7 +27,7 @@ var enUI = UI{ }, Rank: map[string]string{ "solemnity": "solemnity", "feast": "feast", "memorial": "memorial", - "optional": "optional memorial", "ferial": "feria", + "optional": "optional memorial", "ferial": "feria", "sunday": "Sunday", "class-1": "I class", "class-2": "II class", "class-3": "III class", "class-4": "IV class", "commemoration": "commemoration", @@ -125,7 +125,7 @@ var plUI = UI{ Rank: map[string]string{ "solemnity": "uroczystość", "feast": "święto", "memorial": "wspomnienie obowiązkowe", "optional": "wspomnienie dowolne", - "ferial": "dzień powszedni", + "ferial": "dzień powszedni", "sunday": "niedziela", "class-1": "I klasy", "class-2": "II klasy", "class-3": "III klasy", "class-4": "IV klasy", "commemoration": "komemoracja", diff --git a/internal/i18n/lang/en.ini b/internal/i18n/lang/en.ini index 60a5f32..4abd5d1 100644 --- a/internal/i18n/lang/en.ini +++ b/internal/i18n/lang/en.ini @@ -19,6 +19,7 @@ rank.ferial = feria rank.memorial = memorial rank.optional = optional memorial rank.solemnity = solemnity +rank.sunday = Sunday footer_keys = tab/⇧tab version ←/→ day d date j/k scroll space/b page g/G top/bottom q quit loading = loading… no_readings_for = "no readings for " diff --git a/internal/i18n/lang/pl.ini b/internal/i18n/lang/pl.ini index 5991938..4ae4ebf 100644 --- a/internal/i18n/lang/pl.ini +++ b/internal/i18n/lang/pl.ini @@ -19,6 +19,7 @@ rank.ferial = dzień powszedni rank.memorial = wspomnienie obowiązkowe rank.optional = wspomnienie dowolne rank.solemnity = uroczystość +rank.sunday = niedziela footer_keys = tab/⇧tab wersja ←/→ dzień d data j/k przewiń spacja/b strona g/G góra/dół q wyjście loading = ładowanie… no_readings_for = "brak czytań na " diff --git a/internal/i18n/vocab_test.go b/internal/i18n/vocab_test.go index 97c09d6..d0fd603 100644 --- a/internal/i18n/vocab_test.go +++ b/internal/i18n/vocab_test.go @@ -12,7 +12,7 @@ import ( func TestVocabularyCoversEveryConstant(t *testing.T) { ranks := []calendar.Rank{ calendar.RankFerial, calendar.RankOptional, calendar.RankMemorial, - calendar.RankFeast, calendar.RankSolemnity, + calendar.RankFeast, calendar.RankSolemnity, calendar.RankSunday, calendar.RankClass1, calendar.RankClass2, calendar.RankClass3, calendar.RankClass4, calendar.RankCommemoration, } @@ -31,7 +31,7 @@ func TestVocabularyCoversEveryConstant(t *testing.T) { func TestEnglishRankWordingUnchanged(t *testing.T) { want := map[string]string{ "solemnity": "solemnity", "feast": "feast", "memorial": "memorial", - "optional": "optional memorial", "ferial": "feria", + "optional": "optional memorial", "ferial": "feria", "sunday": "Sunday", "class-1": "I class", "class-2": "II class", "class-3": "III class", "class-4": "IV class", "commemoration": "commemoration", @@ -53,7 +53,7 @@ func TestPolishRankWordingUnchanged(t *testing.T) { want := map[string]string{ "solemnity": "uroczystość", "feast": "święto", "memorial": "wspomnienie obowiązkowe", "optional": "wspomnienie dowolne", - "ferial": "dzień powszedni", + "ferial": "dzień powszedni", "sunday": "niedziela", "class-1": "I klasy", "class-2": "II klasy", "class-3": "III klasy", "class-4": "IV klasy", "commemoration": "komemoracja", diff --git a/internal/readings/offline.go b/internal/readings/offline.go index be7c430..b2c16df 100644 --- a/internal/readings/offline.go +++ b/internal/readings/offline.go @@ -162,10 +162,56 @@ func dayInfo(cfg config.Config, day calendar.LiturgicalDay) liturgy.DayInfo { return liturgy.DayInfo{ Name: celebrationName(cfg, day.Observed), Colour: string(day.Colour), - Rank: string(day.Observed.Rank), + Rank: displayRank(cfg, day), } } +// displayRank is the Ordinary Form's REPORTED rank for the observed +// celebration -- it never changes calendar.Celebration.Rank (the engine's own +// field, which drives precedence via ofRankOrder); it only relabels what is +// handed to a caller here, after the engine has already finished computing. +// +// In the 1969 Universal Norms' Table of Liturgical Days, Sunday is its own +// category, not a solemnity. The calendar engine's temporal.go builds every +// "Nth Sunday of <season>" day (Ordinary Time, Advent, Lent, the Easter +// season, Christmas time, and Palm Sunday) with sundayDay(), which sets +// Rank=solemnity but leaves Class at its zero value (calendar.ClassNone) -- +// solemnity is a placeholder there, not a real classification. A genuinely +// NAMED solemnity of the Lord that happens to fall on a Sunday (Easter Sunday +// itself, Pentecost, Ascension/Corpus Christi when transferred, Trinity, +// Christ the King, Christmas, Epiphany) is built by solemn(), which does set +// Class=ClassLord, and must keep reporting "solemnity"; a feast of the Lord +// (Holy Family, Baptism of the Lord) is Rank=feast already and is untouched. +// +// Investigated first: temporal.go's own Sunday bool (sundayDay's +// `Sunday: !priv`) looked like the natural signal, but it does not reach +// calendar.LiturgicalDay at all (LiturgicalDay carries no such field, only +// the aggregate ObservedBand), and even if plumbed through it would be the +// wrong signal here -- by its own doc comment it marks only the band-6 +// Christmas-time/Ordinary-time Sundays, not the band-2 Advent/Lent/Easter +// Sundays this change must ALSO relabel (the 1st Sunday of Advent from the +// original bug report is band 2, Privileged=true, Sunday=false). Using +// Celebration.Class instead of that flag, or of temporalDay.Privileged, +// covers exactly the sundayDay()-built set in both bands without adding any +// new field: RankSolemnity + Layer=="temporal" + Class!=ClassLord occurs only +// from sundayDay(), and (checked against every call site in temporal.go) only +// ever on an actual Sunday, so the weekday check below is defensive, not +// load-bearing. +// +// The Extraordinary Form (1962) has no such category and is untouched: this +// only ever fires when the modern form is selected. +func displayRank(cfg config.Config, day calendar.LiturgicalDay) string { + obs := day.Observed + if cfg.Selection().Form != "old" && + day.Weekday == time.Sunday && + obs.Layer == "temporal" && + obs.Rank == calendar.RankSolemnity && + obs.Class != calendar.ClassLord { + return string(calendar.RankSunday) + } + return string(obs.Rank) +} + // celebrationName is the observed celebration's name in the UI language, // resolved by naming.CelebrationName (name.<lang> -> English -> Latin -> // humanized slug). An empty result (unnamed feria) omits the header line. diff --git a/internal/readings/readings_test.go b/internal/readings/readings_test.go index 143b43e..1674d85 100644 --- a/internal/readings/readings_test.go +++ b/internal/readings/readings_test.go @@ -99,6 +99,43 @@ func TestLoadModern(t *testing.T) { } } +// TestSundayRankIsDisplayOnly guards the Ordinary Form's Sunday display rank. +// Sunday is its own category in the 1969 Universal Norms' Table of Liturgical +// Days -- never a solemnity -- so an ordinary "Nth Sunday of <season>" temporal +// day (Ordinary Time, Advent, Lent, the Easter season, Christmas time; this +// also covers Palm Sunday) must report "sunday". A NAMED solemnity of the Lord +// that happens to fall on a Sunday (Easter Sunday itself, Pentecost) is a +// genuine solemnity and must keep reporting "solemnity"; a feast of the Lord +// (Holy Family) must keep reporting "feast". This is display-only: it must +// never touch the engine's internal precedence, so the 1962 form (which has no +// such Sunday category -- Sundays report their class exactly as before) is +// asserted unchanged too. Dates are the ones from the original bug report, +// plus the Easter/Pentecost/Holy Family dates they imply for the same +// liturgical year. +func TestSundayRankIsDisplayOnly(t *testing.T) { + cases := []struct { + date, lect, wantRank, why string + }{ + {"2026-08-09", "new", "sunday", "19th Sunday in Ordinary Time"}, + {"2026-11-29", "new", "sunday", "1st Sunday of Advent"}, + {"2027-03-28", "new", "solemnity", "Easter Sunday itself"}, + {"2027-05-16", "new", "solemnity", "Pentecost"}, + {"2026-12-27", "new", "feast", "Holy Family"}, + {"2026-08-09", "traditional", "class-2", "1962 Sunday after Pentecost"}, + {"2026-11-29", "traditional", "class-1", "1962 1st Sunday of Advent"}, + } + for _, c := range cases { + cfg := config.Config{Lectionary: c.lect} + _, info, err := Load(cfg, Options{Date: c.date, All: true}) + if err != nil { + t.Fatalf("%s (%s, %s): Load: %v", c.date, c.lect, c.why, err) + } + if info.Rank != c.wantRank { + t.Errorf("%s (%s, %s): Rank = %q, want %q (name=%q)", c.date, c.lect, c.why, info.Rank, c.wantRank, info.Name) + } + } +} + // TestLoadTraditional computes the Extraordinary Form day offline: it never // needs the network, and yields the EF epistle+gospel with a header name. func TestLoadTraditional(t *testing.T) { |
