aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-05 22:24:53 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-05 22:24:53 +0200
commit4855ef2a667b7ea71092e0582db5aff1838a0d63 (patch)
tree098d3a85c503f73d95c551f76e6f9f1d1ef1e208 /internal
parent8929ab96ad44b707bafd7dfe6f1f7773843c8e85 (diff)
downloadlectio-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.
Diffstat (limited to 'internal')
-rw-r--r--internal/calendar/types.go12
-rw-r--r--internal/i18n/golden_test.go4
-rw-r--r--internal/i18n/lang/en.ini1
-rw-r--r--internal/i18n/lang/pl.ini1
-rw-r--r--internal/i18n/vocab_test.go6
-rw-r--r--internal/readings/offline.go48
-rw-r--r--internal/readings/readings_test.go37
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) {