aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/calendar/calendar.go29
-rw-r--r--internal/calendar/precedence_ef.go37
-rw-r--r--internal/calendar/precedence_ef_repro_test.go39
3 files changed, 101 insertions, 4 deletions
diff --git a/internal/calendar/calendar.go b/internal/calendar/calendar.go
index f364321..19c0efb 100644
--- a/internal/calendar/calendar.go
+++ b/internal/calendar/calendar.go
@@ -258,16 +258,39 @@ func transferIfImpededEF(cel Celebration, when time.Time, occupiedByClass1, occu
if cel.Rank != RankClass1 {
return when
}
+ // Is cel actually impeded on `when` at all? Compare its OWN precedence
+ // (via precedenceEF, the same function pickEF uses to decide the day)
+ // against the temporal office's, rather than testing the temporal
+ // office's class in isolation -- a plain "is the temporal day I class"
+ // test wrongly impedes a candidate that would in fact WIN a tie against
+ // it: RG 91 entry 4 (the Immaculate Conception, the Assumption) sits
+ // ABOVE entry 6 (Sundays), so the Immaculate Conception is not impeded
+ // by the Advent Sunday it may fall on at all, unlike an ordinary I-class
+ // feast (entry 11, e.g. St Joseph), which is. occupiedByClass1 still
+ // covers the separate case of a competing FIXED I-class SANCTORAL
+ // feast, which this temporal-only comparison cannot see.
startTemporal := temporalEF(when)
- startImpeded := startTemporal.Cel.Rank == RankClass1 || occupiedByClass1(when)
+ tCand := candidate{Cel: startTemporal.Cel, Temporal: true, Season: startTemporal.Season, Sunday: startTemporal.Sunday}
+ sCand := candidate{Cel: cel, Temporal: false}
+ startImpeded := precedenceEF(tCand) < precedenceEF(sCand) || occupiedByClass1(when)
if !startImpeded {
return when
}
day := when.AddDate(0, 0, 1)
for i := 0; i < 30; i++ {
b := temporalEF(day)
- band := precedenceEF(candidate{Cel: b.Cel, Temporal: true, Season: b.Season, Sunday: b.Sunday})
- if band <= 3 || occupiedByClass1Or2(day) {
+ // A direct CLASS test, not precedenceEF's tie-break band: band
+ // encodes which of two EQUAL-class candidates wins a tie (e.g. a
+ // Sunday beats an equal-class feast, but a privileged Ember/late-
+ // Advent FERIA of the same class yields to one, defect 2) -- an
+ // unrelated question from RG 96's own "is this day itself I or II
+ // class" test. Reusing band here undercounted: a day within the
+ // Octave of the Nativity (26-31 Dec) is genuinely II class (RG 67),
+ // but as an ordinary, non-Sunday II-class temporal candidate its OWN
+ // band yields (5, not <=3) -- so band<=3 alone let an impeded I-class
+ // feast wrongly land there. isHighClass tests the class directly.
+ isHighClass := b.Cel.Rank == RankClass1 || b.Cel.Rank == RankClass2
+ if isHighClass || occupiedByClass1Or2(day) {
day = day.AddDate(0, 0, 1) // still I- or II-class; keep walking
continue
}
diff --git a/internal/calendar/precedence_ef.go b/internal/calendar/precedence_ef.go
index a1788be..9f5016f 100644
--- a/internal/calendar/precedence_ef.go
+++ b/internal/calendar/precedence_ef.go
@@ -1,6 +1,9 @@
package calendar
-import "sort"
+import (
+ "sort"
+ "strings"
+)
// efRankOrder orders EF (1960) ranks: class-1 highest, then class-2..4,
// commemoration, ferial lowest.
@@ -69,10 +72,42 @@ func precedenceEF(c candidate) int {
// falls on (unlike a saint's feast, which is only commemorated). Give it
// the edge over the Sunday's tie-break bonus.
p -= 2
+ } else if c.Cel.Rank == RankClass1 && beatsClass1Sunday(c.Cel.Slug) {
+ // RG 91 entries 4 (Immaculate Conception, Assumption BVM) and 5
+ // (Vigil & Octave day of the Nativity) both sit ABOVE entry 6
+ // (Sundays of Advent/Lent/Passiontide, Low Sunday) -- unlike an
+ // ORDINARY I-class feast (entry 11, e.g. St Joseph, the Precious
+ // Blood), which yields to a I-class Sunday, these win the tie.
+ // Found while verifying defect 4 (Sunday ranks): before that fix,
+ // Advent/Lent Sundays were wrongly II class, so these feasts beat
+ // them outright on base class alone, accidentally right; once
+ // Sundays became I class the tie-break mattered, and without this
+ // branch the Sunday would wrongly win. Two live cases in the fixed
+ // calendar: the Immaculate Conception (8 December) on an Advent
+ // Sunday, and the Vigil of Christmas (24 December) on Advent IV --
+ // the latter is not merely a wrong winner but a WORSE bug without
+ // this branch: transferIfImpededEF has no way to re-place a
+ // transfer that crosses the Dec31/Jan1 boundary (celebrationDate
+ // re-resolves a fixed date using the YEAR OF THE DAY BEING QUERIED,
+ // so a walk landing in January of the following year can never
+ // match the query that produced it), so the Vigil simply vanished
+ // for the year instead of landing on the wrong day. The Assumption
+ // (15 August) never falls in Advent or Lent, so its own share of
+ // this branch is citation-complete but not live.
+ p -= 2
}
return p
}
+// beatsClass1Sunday reports whether slug is one of RG 91's entries 4 or 5 --
+// the Immaculate Conception, the Assumption, or the Vigil of Christmas --
+// all of which sit above entry 6's Sundays in the Table of Precedence.
+func beatsClass1Sunday(slug string) bool {
+ return strings.Contains(slug, "immaculate-conception") ||
+ strings.Contains(slug, "assumption-of-the-blessed-virgin-mary") ||
+ slug == "vigil-of-christmas"
+}
+
// pickEF returns the observed EF celebration and the commemorations, ordered
// deterministically by precedence then slug.
func pickEF(cands []candidate) (candidate, []candidate) {
diff --git a/internal/calendar/precedence_ef_repro_test.go b/internal/calendar/precedence_ef_repro_test.go
index 77919d9..09cadbd 100644
--- a/internal/calendar/precedence_ef_repro_test.go
+++ b/internal/calendar/precedence_ef_repro_test.go
@@ -81,3 +81,42 @@ func TestMatthewBeatsSeptemberEmberWednesday(t *testing.T) {
}
}
}
+
+// TestImmaculateConceptionBeatsAdventSunday: found, not named among the
+// seven, while verifying defect 4 (Sunday ranks) against the real sanctoral
+// data. RG 91 entry 4 (Immaculate Conception, Assumption BVM) sits ABOVE
+// entry 6 (Sundays of Advent/Lent/Passiontide) -- unlike an ORDINARY
+// I-class feast (entry 11, e.g. St Joseph), the Immaculate Conception
+// (8 December) is not impeded by the Advent Sunday it falls on at all. Before
+// defect 4's own fix, Advent Sundays were wrongly II class, so this was
+// accidentally right (I class beats II class outright); once Sundays became
+// I class the tie mattered for the first time.
+func TestImmaculateConceptionBeatsAdventSunday(t *testing.T) {
+ for _, year := range []string{"2013", "2019", "2024"} {
+ day := efCompute(year + "-12-08")
+ if day.Observed.Slug != "immaculate-conception-of-the-blessed-virgin-mary" {
+ t.Errorf("%s-12-08 observed = %q want immaculate-conception-of-the-blessed-virgin-mary (RG 91 entry 4 beats entry 6)", year, day.Observed.Slug)
+ }
+ }
+}
+
+// TestVigilOfChristmasSurvivesAdventSunday: found, not named among the
+// seven, while verifying defect 4 against the real sanctoral data -- and
+// worse than a wrong winner. RG 91 entry 5 (Vigil & Octave day of the
+// Nativity) also sits above entry 6, so the Vigil of Christmas (24 December)
+// is not impeded by falling on Advent IV either. Without this, defect 4
+// alone would have made the Advent Sunday win a genuine tie, sending the
+// Vigil into transferIfImpededEF's forward walk -- which has no way to
+// re-place a transfer that crosses the Dec31/Jan1 boundary (celebrationDate
+// re-resolves a fixed date using the year of whatever day is being queried,
+// so a walk landing in the following January can never match the query that
+// produced it). The Vigil did not move to the wrong day; it vanished for the
+// whole year.
+func TestVigilOfChristmasSurvivesAdventSunday(t *testing.T) {
+ for _, year := range []string{"2006", "2017", "2023", "2028"} {
+ day := efCompute(year + "-12-24")
+ if day.Observed.Slug != "vigil-of-christmas" {
+ t.Errorf("%s-12-24 observed = %q want vigil-of-christmas (RG 91 entry 5 beats entry 6; must not vanish)", year, day.Observed.Slug)
+ }
+ }
+}