diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 13:04:53 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-12 13:04:53 +0200 |
| commit | f40eb498c612aeda2ff7f3d5b898e4d1ddcec352 (patch) | |
| tree | 968dcfc8fb263abadaf715833af9a8acda0a60a3 /internal/calendar/temporal_ef.go | |
| parent | ca3403893bfbebb325fe3ab6a98081640574bf2a (diff) | |
| download | lectio-f40eb498c612aeda2ff7f3d5b898e4d1ddcec352.tar.gz lectio-f40eb498c612aeda2ff7f3d5b898e4d1ddcec352.zip | |
temporal_ef: Sunday ranks, Rose Sundays, Holy Thursday white
Four fixes to internal/calendar/temporal_ef.go, all found by the
strengthened oracle test (previous commit):
1. Sunday ranks (defect 4). The generic Sunday branch assigned
class-2 to every Sunday except Advent I. RG 11-12 / RG 91 entry 6:
every Sunday of Advent and every Sunday of Lent is I class (as are
Passiontide's own two Sundays and Low Sunday, already correct via
their named-feast cases). Fixes St Joseph wrongly taking a Sunday
of Lent (defect 3, precedence_ef_repro_test.go's
TestJosephYieldsToSundayOfLent) as a direct consequence: once Lent
Sundays are I class, Joseph (also I class) no longer wins the tie
outright and correctly transfers to the 20th via the existing RG
96 walk -- no separate code change was needed for defect 3.
2. Rose (defect 5). efColour had no Rose case at all; Gaudete (Advent
III) and Laetare (Lent IV) now get Rose on that Sunday specifically
(RG 131: rose vestments may be used "in Officio et Missa diei
dominici tantum", for that Sunday's Office and Mass only), not the
surrounding Sundays.
3. Holy Thursday's colour (defect 6). RG 128(b) names the Missa in
Cena Domini as a whole-Mass exception to Passiontide's violet; RG
122 states the same fact affirmatively, in the White section
itself. Good Friday and Holy Saturday, either side, are unchanged
(still violet -- their own black/no-colour treatment is a separate,
unmodelled gap, noted in precedence_ef.go's own doc comments).
4. Beyond the seven, found by the same strengthened test and fixed for
the same RG 91 entry 18 reason defect 2 (next commit) relies on:
the Ember days of Lent had no case in efEmberSlug at all (only
September and Advent did), so they fell through to the ordinary
III-class Lenten-feria rank instead of the II class RG 91 entry 18
requires. The late-Advent ferias (17-23 Dec, RG 91 entry 18) and
the days within the Octave of the Nativity (26-31 Dec, RG 67-68)
had no elevation at all, defaulting to III/IV class. All three are
one-line, unambiguous, primary-cited additions to the same rank
logic already being touched here -- left unfixed, the strengthened
oracle test could only reach green by allow-listing them as if they
were defensible divergences, which they are not.
Witnesses (temporal_ef_test.go): TestTemporalEFSundayRanks,
TestTemporalEFRoseSundays, TestTemporalEFHolyThursdayColour,
TestTemporalEFEmberDayRanks. All fail before this commit; see the
report for the exact pre-fix failure messages.
Diffstat (limited to 'internal/calendar/temporal_ef.go')
| -rw-r--r-- | internal/calendar/temporal_ef.go | 81 |
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 } } |
