aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/precedence_ef_repro_test.go
blob: 11fd6caae53cc970dddaeac084aaab7ca4121a83 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package calendar_test

// Reproduction tests for three named EF precedence defects (colitur's
// differential/oracle audit against lectio's ~66-line EF precedence
// approximation, internal/calendar/precedence_ef.go). Each test fails before
// its fix and passes after -- see the report for the exact pre-fix failure
// message. These exercise the REAL tridentine sanctoral data
// (caldata.Tridentine()) through the public Compute entry point, since the
// bugs are about how real fixed-date feasts interact with the temporal
// calendar, not about precedenceEF's arithmetic in isolation (that is
// covered separately, package-internal, in precedence_ef_test.go).

import (
	"testing"
	"time"

	"github.com/lukaszkasprzak/lectio/internal/caldata"
	"github.com/lukaszkasprzak/lectio/internal/calendar"
)

func efCompute(date string) calendar.LiturgicalDay {
	sel := calendar.DefaultSelection()
	sel.Form = "old"
	d, _ := time.Parse("2006-01-02", date)
	return calendar.Compute(d.UTC(), sel, []calendar.Layer{caldata.Tridentine()})
}

// TestJosephYieldsToSundayOfLent: RG 91 entry 6 (Sundays of Lent, I class)
// vs entry 11 (I-class feasts of the universal Church not above) -- the
// Sunday holds; St Joseph (19 March) transfers to the next free day (RG 95,
// RG 96), landing on the 20th. Before the fix (defect 4: Sunday ranks), Lent
// Sundays were wrongly II class, so Joseph (I class) won outright every time
// this collision occurred.
func TestJosephYieldsToSundayOfLent(t *testing.T) {
	for _, year := range []string{"2006", "2017", "2023", "2028", "2034", "2045"} {
		sunday := efCompute(year + "-03-19")
		if sunday.Observed.Slug == "joseph-spouse-of-the-bl-virgin-mary" {
			t.Errorf("%s-03-19 observed = %q want the Lent Sunday (RG 91 entry 6 beats entry 11)", year, sunday.Observed.Slug)
		}
		next := efCompute(year + "-03-20")
		if next.Observed.Slug != "joseph-spouse-of-the-bl-virgin-mary" {
			t.Errorf("%s-03-20 observed = %q want joseph-spouse-of-the-bl-virgin-mary (transferred one day, RG 96)", year, next.Observed.Slug)
		}
	}
}

// TestTransferSkipsBothIAndIIClass: RG 96 -- an impeded I-class feast
// transfers to the next day that is NOT ITSELF I OR II CLASS (not merely "not
// I class"). 2011: the Sacred Heart (Friday after the Corpus Christi octave)
// falls on 1 July and impedes the Precious Blood (also 1 July, fixed). 2
// July is the Visitation (II class, fixed); 3 July is an ordinary II-class
// Sunday. Both must be skipped; the Precious Blood lands on 4 July, and the
// Visitation is observed, undisplaced, on its own day.
func TestTransferSkipsBothIAndIIClass(t *testing.T) {
	if got := efCompute("2011-07-01").Observed.Slug; got != "ef-sacred-heart" {
		t.Fatalf("2011-07-01 observed = %q want ef-sacred-heart", got)
	}
	if got := efCompute("2011-07-02").Observed.Slug; got != "visitation-of-the-blessed-virgin-mary" {
		t.Errorf("2011-07-02 observed = %q want visitation-of-the-blessed-virgin-mary (RG 96: the Precious Blood must skip past it, not displace it)", got)
	}
	if got := efCompute("2011-07-03").Observed.Slug; got != "ef-time-after-pentecost-sunday-3" {
		t.Errorf("2011-07-03 observed = %q want ef-time-after-pentecost-sunday-3 (an ordinary II-class Sunday also blocks a I-class transfer)", got)
	}
	if got := efCompute("2011-07-04").Observed.Slug; got != "precious-blood-of-our-lord-jesus-christ" {
		t.Errorf("2011-07-04 observed = %q want precious-blood-of-our-lord-jesus-christ (RG 96: first day that is neither I nor II class)", got)
	}
}

// TestMatthewBeatsSeptemberEmberWednesday: RG 91 -- St Matthew (21 September,
// II class) and the September Ember Wednesday are both II class; the table
// decides the tie. Entry 16 (II-class feasts of the universal Church) sits
// ABOVE entry 18 (II-class ferias, including the Ember days) -- the feast
// wins, the Ember feria is only commemorated.
func TestMatthewBeatsSeptemberEmberWednesday(t *testing.T) {
	// 21 September falls on the September Ember Wednesday whenever the third
	// Sunday of September is the 18th -- 2016 and 2022 both qualify.
	for _, date := range []string{"2016-09-21", "2022-09-21"} {
		day := efCompute(date)
		if day.Observed.Slug != "matthew" {
			t.Errorf("%s observed = %q want matthew (RG 91 entry 16 beats entry 18)", date, day.Observed.Slug)
		}
	}
}

// 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)
		}
	}
}

// TestPurificationBeatsFebruarySunday: the fixture behind classOf's own
// Purification decision (scripts/gen-sanctoral-ef.go), committed here rather
// than left as an assertion only, because the deciding years (2 February on
// a Sunday: 2014, 2020, 2025, 2031, 2042, 2048) fall entirely outside this
// repo's own missalemeum oracle snapshot (sources/snapshot.tar.gz,
// 2026-01-01 .. 2027-12-31), so nothing else in this repository can
// reproduce the evidence classOf's decision rests on. (2036, sometimes
// quoted alongside 2025/2031 as a third example: 2 February 2036 is in fact
// a SATURDAY, `date -d 2036-02-02 +%A` -- checked here rather than repeated;
// 2042 is the correct next instance after 2031.)
//
// RG 91 entry 14 ("Festa Domini II classis") sits above entry 15
// ("Dominicae II classis"), which sits above entry 16 ("Festa II classis
// Ecclesiae universae, quae non [sunt Domini]") -- entry 14 takes an
// occurring Sunday's place outright, entry 16 is merely commemorated on
// one. The Purification (2 February) matches entry 14's pattern, not entry
// 16's, independently fetched live from missalemeum for five different
// Sundays: 2014-02-02, 2020-02-02, 2025-02-02, 2031-02-02, 2042-02-02 (all
// "id":"sancti:02-02:2:w", "commemorations":[]). Control:
// nativity-of-the-blessed-virgin-mary (8 September, an undisputed ordinary
// Marian feast, entry 16's own pattern) on a Sunday -- 2019-09-08 -- shows
// the SUNDAY observed, the feast commemorated instead: the opposite shape,
// proving the Purification's own treatment is a deliberate pattern in the
// oracle, not a gap. This is a decision AGAINST the calendarium's own title
// ("IN PURIFICATIONE B. MARIAE VIRG.") and RG 120(b) (which files 2
// February under the white-colour rule's "B. Mariae Virg." heading, kept
// separate from 120(a)'s "Domini" heading) -- both point BVM; the
// occurrence-behaviour evidence here points Domini. See classOf's own doc
// comment for the fuller account; not re-argued here.
func TestPurificationBeatsFebruarySunday(t *testing.T) {
	for _, year := range []string{"2014", "2020", "2025", "2031", "2042"} {
		day := efCompute(year + "-02-02")
		if day.Observed.Slug != "purification-of-the-blessed-virgin-mary" {
			t.Errorf("%s-02-02 observed = %q want purification-of-the-blessed-virgin-mary (RG 91 entry 14 beats entry 15)", year, day.Observed.Slug)
		}
	}
}