aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/temporal_ef.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-14 13:22:16 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-14 13:22:16 +0200
commit913974b10a993af251b25125d1a417c452ad785c (patch)
tree5dd82811c100de6daa995bebd115a12434296508 /internal/calendar/temporal_ef.go
parentd7da4b09f7775276231d0241cfe2700d247728ee (diff)
parent3b32c002d3eddda5ece9422442717657b9fee63b (diff)
downloadlectio-913974b10a993af251b25125d1a417c452ad785c.tar.gz
lectio-913974b10a993af251b25125d1a417c452ad785c.zip
Merge branch 'polish-ui-and-calendar': the gomobile facade and the EF calendar fixes
Two bodies of work that shared a branch. The gomobile facade (2026-08-03..05): mobile.PartLabels, Days, and the observed rank on DayInfo, so dlectio stops hardcoding part IDs and rank strings; the 1962 part labels become i18n data; the documented gomobile bind command is corrected so it reproduces the shipped .aar. The EF calendar fixes (2026-08-12): seven defects found by differencing this engine against colitur, a second 1962 implementation built from the Missal's General Rubrics rather than from this codebase. RG 96 transfers were not skipping II-class days; a II-class privileged feria was not yielding to a feast; Sunday ranks, the two Rose Sundays and Holy Thursday's colour were wrong; and scripts/gen-sanctoral-ef inferred ranks, deduped and tagged classes wrongly, which put 15 III-class feasts into the shipped tridentine-calendar.ini as bare commemorations and dropped four entries outright. Holy Thursday was violet in both engines, which is how a shared lineage hides a defect: this project's ini is generated from missalemeum and colitur's data was bootstrapped from here, so an error inherited by both is invisible to a differential. It took the Missal itself to see it. The EF oracle test now asserts rank and colour, not season alone. One known gap is recorded in the source rather than fixed, as out of scope: RG 95 chained transfers (calendar.go).
Diffstat (limited to 'internal/calendar/temporal_ef.go')
-rw-r--r--internal/calendar/temporal_ef.go81
1 files changed, 63 insertions, 18 deletions
diff --git a/internal/calendar/temporal_ef.go b/internal/calendar/temporal_ef.go
index 7420ebd..c649405 100644
--- a/internal/calendar/temporal_ef.go
+++ b/internal/calendar/temporal_ef.go
@@ -74,6 +74,16 @@ func efCel(season Season, slug string, col Colour, rank Rank, week int) temporal
}
}
+// efSunday is efCel plus the Sunday flag, for the temporal candidates that
+// are actual Sundays -- precedenceEF's tie-break needs to tell a Sunday (RG
+// 91 entry 15, wins its tie) apart from an equal-class privileged FERIA
+// (entry 18, the Ember/late-Advent days, which yields).
+func efSunday(season Season, slug string, col Colour, rank Rank, week int) temporalDay {
+ day := efCel(season, slug, col, rank, week)
+ day.Sunday = true
+ return day
+}
+
// temporalEF computes the 1962 temporal identity of date. Season is exact
// (oracle-validated); ranks/weeks are best-effort for display.
func temporalEF(date time.Time) temporalDay {
@@ -87,6 +97,15 @@ func temporalEF(date time.Time) temporalDay {
if !date.Before(easter.AddDate(0, 0, 50)) && !date.After(easter.AddDate(0, 0, 55)) {
col = Red
}
+ // Holy Thursday (the Mass of Chrism and the Mass in Cena Domini) is white,
+ // a whole-Mass exception to Passiontide's violet -- RG 128(b)'s own named
+ // exception list, and RG 122 stating the same fact affirmatively in the
+ // White section. Good Friday and Holy Saturday, either side, are NOT
+ // exceptions here (RG 132's black for Good Friday's liturgical action is a
+ // separate, unmodelled gap -- see precedence_ef.go's doc comments).
+ if sameDay(date, easter.AddDate(0, 0, -3)) {
+ col = White
+ }
sun := date.Weekday() == time.Sunday
// Major feasts of the Lord (I class): nice titles + colour.
@@ -129,8 +148,20 @@ func temporalEF(date time.Time) temporalDay {
if sun {
week := efWeek(date, season, y, easter)
rank := RankClass2
- if season == Advent && sameDay(date, adventStart(y)) {
- rank = RankClass1 // 1st Sunday of Advent
+ sundayColour := col
+ if season == Advent || season == Lent {
+ // RG 11-12 / RG 91 entry 6: every Sunday of Advent and every Sunday
+ // of Lent is I class -- not only Advent I, the sole case previously
+ // handled (Passiontide's own two Sundays, Passion and Palm, are
+ // already I class via the named-feast switch above; Low Sunday
+ // likewise).
+ rank = RankClass1
+ switch {
+ case season == Advent && week == 3:
+ sundayColour = Rose // Gaudete -- RG 131, that Sunday's Office/Mass only
+ case season == Lent && week == 4:
+ sundayColour = Rose // Laetare -- RG 131, that Sunday's Office/Mass only
+ }
}
// Resumed Sundays after Pentecost (1960 Rubrics): when Easter is early
// there are more than 24 Sundays after Pentecost. The LAST Sunday before
@@ -140,17 +171,17 @@ func temporalEF(date time.Time) temporalDay {
if season == TimeAfterPentecost {
lastSun := adventStart(y).AddDate(0, 0, -7)
if sameDay(date, lastSun) {
- return efCel(TimeAfterPentecost, "ef-time-after-pentecost-sunday-24", col, rank, 24)
+ return efSunday(TimeAfterPentecost, "ef-time-after-pentecost-sunday-24", col, rank, 24)
}
if week > 23 {
p := daysBetween(easter.AddDate(0, 0, 49), lastSun) / 7 // total Sundays after Pentecost
e := week - p + 7 // resumed Sunday after Epiphany
// Season stays time-after-pentecost (calendrical); the Epiphany
// slug only routes the readings to the resumed Mass.
- return efCel(TimeAfterPentecost, "ef-time-after-epiphany-sunday-"+strconv.Itoa(e), Green, rank, e)
+ return efSunday(TimeAfterPentecost, "ef-time-after-epiphany-sunday-"+strconv.Itoa(e), Green, rank, e)
}
}
- return efCel(season, "ef-"+string(season)+"-sunday-"+strconv.Itoa(week), col, rank, week)
+ return efSunday(season, "ef-"+string(season)+"-sunday-"+strconv.Itoa(week), sundayColour, rank, week)
}
// The days between Ash Wednesday and the 1st Sunday of Lent have their own
// proper Masses (not part of a numbered Lenten week).
@@ -166,6 +197,17 @@ func temporalEF(date time.Time) temporalDay {
// saint (e.g. the Conversion of St Paul, Jan 25) displaces them.
rank = RankClass3
}
+ if season == Advent && date.Month() == time.December && date.Day() >= 17 && date.Day() <= 23 {
+ // RG 91 entry 18: the late-Advent ferias (17-23 Dec) are II class
+ // privileged ferias, not the ordinary III class of the rest of Advent.
+ rank = RankClass2
+ }
+ if season == Christmas && date.Month() == time.December && date.Day() >= 26 {
+ // RG 67/68: "Dies infra octavam sunt II classis" -- days within the
+ // Octave of the Nativity (26-31 Dec) are II class; the octave day
+ // itself (1 Jan) is I class, already handled above as a named feast.
+ rank = RankClass2
+ }
if efPrivilegedFeria(date, easter) {
// The days of Holy Week and the privileged octaves of Easter and
// Pentecost are I class; no saint's feast is admitted (the feast is
@@ -173,12 +215,11 @@ func temporalEF(date time.Time) temporalDay {
// the temporal office win, matching the 1962 occurrence rules.
rank = RankClass1
}
- // The Ember Days of September and Advent (Wednesday, Friday, Saturday after
- // the third Sunday of the month/season) are II class privileged ferias in
- // violet; they displace a III class saint (only commemorated). The Lenten
- // Ember days are already III class Lenten ferias, and the Whitsun Ember days
- // fall inside the I class Pentecost octave handled above.
- if es, ok := efEmberSlug(date, y); ok {
+ // The Ember Days of September, Advent and Lent (Wednesday, Friday, Saturday
+ // after the anchor Sunday) are II class privileged ferias in violet; they
+ // displace a III class saint (only commemorated) -- RG 91 entry 18. The
+ // Whitsun Ember days fall inside the I class Pentecost octave handled above.
+ if es, ok := efEmberSlug(date, y, easter); ok {
return efCel(season, es, Violet, RankClass2, week)
}
// Unique ferial slug (season-week-weekday) so proper ferias (Lent, Advent,
@@ -197,22 +238,26 @@ func thirdSundayOfSeptember(y int) time.Time {
}
// efEmberSlug returns the Ember-day slug for date, if it is an Ember Wednesday,
-// Friday or Saturday of September or Advent (the day after the third Sunday +
-// 3/5/6). Returns ("", false) otherwise.
-func efEmberSlug(date time.Time, y int) (string, bool) {
+// Friday or Saturday of September, Advent or Lent (the day after the anchor
+// Sunday + 3/5/6: the third Sunday of September, Advent III, or Lent I --
+// MR1962, "De anno et eius partibus", Quatuor Tempora). Returns ("", false)
+// otherwise. The Whitsun Ember days are not listed here: they fall inside the
+// I class Pentecost octave, handled separately by efPrivilegedFeria.
+func efEmberSlug(date time.Time, y int, easter time.Time) (string, bool) {
for _, e := range []struct {
- third time.Time
+ anchor time.Time
season string
}{
{thirdSundayOfSeptember(y), "september"},
{adventStart(y).AddDate(0, 0, 14), "advent"}, // 3rd Sunday of Advent
+ {easter.AddDate(0, 0, -42), "lent"}, // 1st Sunday of Lent
} {
switch {
- case sameDay(date, e.third.AddDate(0, 0, 3)):
+ case sameDay(date, e.anchor.AddDate(0, 0, 3)):
return "ef-" + e.season + "-ember-wed", true
- case sameDay(date, e.third.AddDate(0, 0, 5)):
+ case sameDay(date, e.anchor.AddDate(0, 0, 5)):
return "ef-" + e.season + "-ember-fri", true
- case sameDay(date, e.third.AddDate(0, 0, 6)):
+ case sameDay(date, e.anchor.AddDate(0, 0, 6)):
return "ef-" + e.season + "-ember-sat", true
}
}