aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/temporal_ef_test.go
blob: db6e6aa89a110dd0ff3df5286ab584a23805f510 (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
package calendar

import "testing"

func TestTemporalEFSeasons(t *testing.T) {
	cases := []struct {
		date   string
		season Season
	}{
		{"2025-01-01", Christmas},          // Circumcision
		{"2025-01-20", TimeAfterEpiphany},  // after II Sunday after Epiphany
		{"2025-02-16", Septuagesima},       // Septuagesima Sunday (easter-63)
		{"2025-03-05", Lent},               // Ash Wednesday
		{"2025-04-06", Passiontide},        // Passion Sunday (easter-14)
		{"2025-04-13", Passiontide},        // Palm Sunday
		{"2025-04-20", Easter_},            // Easter
		{"2025-06-08", Easter_},            // Pentecost (still Paschaltide)
		{"2025-06-15", TimeAfterPentecost}, // Trinity
		{"2025-06-22", TimeAfterPentecost}, // II Sunday after Pentecost
		{"2025-10-26", TimeAfterPentecost}, // Christ the King
		{"2025-11-30", Advent},             // I Sunday of Advent
		{"2025-12-25", Christmas},          // Nativity
	}
	for _, c := range cases {
		got := temporalEF(d(c.date))
		if got.Season != c.season {
			t.Errorf("temporalEF(%s).Season = %s, want %s", c.date, got.Season, c.season)
		}
	}
}

func TestTemporalEFChristTheKing(t *testing.T) {
	// EF: last Sunday of October (2025-10-26), not the last before Advent.
	if got := temporalEF(d("2025-10-26")); got.Cel.Slug != "ef-christ-the-king" {
		t.Errorf("2025-10-26 slug = %q want ef-christ-the-king", got.Cel.Slug)
	}
	if got := efChristTheKing(2025).Format("2006-01-02"); got != "2025-10-26" {
		t.Errorf("efChristTheKing(2025) = %s want 2025-10-26", got)
	}
}

// TestTemporalEFSundayRanks: RG 11-12 / RG 91 entry 6 -- Sundays of Advent,
// Lent and Passiontide, and Low Sunday, are I class; all others (including
// Septuagesima's own three Sundays and the ordinary Sundays after Epiphany/
// Pentecost) stay II class. Before the fix, only Advent I was I class; every
// other Advent/Lent Sunday was left at the generic-Sunday II-class default.
func TestTemporalEFSundayRanks(t *testing.T) {
	cases := []struct {
		date string
		want Rank
	}{
		{"2025-11-30", RankClass1}, // Advent I
		{"2025-12-07", RankClass1}, // Advent II
		{"2025-12-14", RankClass1}, // Advent III (Gaudete)
		{"2025-12-21", RankClass1}, // Advent IV
		{"2025-03-09", RankClass1}, // Lent I
		{"2025-03-16", RankClass1}, // Lent II
		{"2025-03-23", RankClass1}, // Lent III
		{"2025-03-30", RankClass1}, // Lent IV (Laetare)
		{"2025-04-06", RankClass1}, // Passion Sunday
		{"2025-04-13", RankClass1}, // Palm Sunday
		{"2025-04-27", RankClass1}, // Low Sunday
		{"2025-02-16", RankClass2}, // Septuagesima Sunday -- NOT I class
		{"2025-02-23", RankClass2}, // Sexagesima Sunday -- NOT I class
		{"2025-03-02", RankClass2}, // Quinquagesima Sunday -- NOT I class
		{"2025-06-22", RankClass2}, // II Sunday after Pentecost -- ordinary II class
	}
	for _, c := range cases {
		got := temporalEF(d(c.date))
		if got.Cel.Rank != c.want {
			t.Errorf("temporalEF(%s).Cel.Rank = %s, want %s (%s)", c.date, got.Cel.Rank, c.want, got.Cel.Slug)
		}
	}
}

// TestTemporalEFRoseSundays: RG 131 -- rose is permitted on Gaudete (Advent
// III) and Laetare (Lent IV), for that Sunday's own Office and Mass only.
// Before the fix, efColour had no Rose case at all.
func TestTemporalEFRoseSundays(t *testing.T) {
	if got := temporalEF(d("2025-12-14")).Cel.Colour; got != Rose {
		t.Errorf("Gaudete (2025-12-14) colour = %s, want rose", got)
	}
	if got := temporalEF(d("2025-03-30")).Cel.Colour; got != Rose {
		t.Errorf("Laetare (2025-03-30) colour = %s, want rose", got)
	}
	// The Advent/Lent Sundays either side stay violet -- rose is an indult for
	// that one Sunday, not a season colour.
	if got := temporalEF(d("2025-12-07")).Cel.Colour; got != Violet {
		t.Errorf("Advent II (2025-12-07) colour = %s, want violet", got)
	}
	if got := temporalEF(d("2025-03-23")).Cel.Colour; got != Violet {
		t.Errorf("Lent III (2025-03-23) colour = %s, want violet", got)
	}
}

// TestTemporalEFHolyThursdayColour: RG 128(b)/RG 122 -- Holy Thursday's Mass
// of Chrism and Mass in Cena Domini are white, a whole-day exception to
// Passiontide's violet. Good Friday and Holy Saturday, either side, stay
// violet (their own black/no-colour exceptions are a separate, unmodelled
// gap -- see RG 132, precedence_ef.go's own doc comments).
// TestTemporalEFTriduumColours pins all three days of the Sacred Triduum,
// which take three different colours for three different reasons.
//
// Renamed from TestTemporalEFHolyThursdayColour, which asserted Good Friday
// was violet "(unchanged)" -- encoding a gap this file's own comment used to
// call "separate, unmodelled". It is modelled now, so the test that pinned
// the gap had to move with it. A test asserting the absence of a feature
// passes for exactly as long as the feature is absent, which is not the same
// as being correct.
func TestTemporalEFTriduumColours(t *testing.T) {
	easter := d("2025-04-20")
	holyThu := easter.AddDate(0, 0, -3)
	goodFri := easter.AddDate(0, 0, -2)
	holySat := easter.AddDate(0, 0, -1)
	// RG 128(b)'s named exception list, and RG 122 affirmatively.
	if got := temporalEF(holyThu).Cel.Colour; got != White {
		t.Errorf("Holy Thursday colour = %s, want white", got)
	}
	// RG 128(b) excepts the day from Passiontide's violet; RG 132 assigns
	// black. missalemeum's own colour set for the day is "bv", black first.
	if got := temporalEF(goodFri).Cel.Colour; got != Black {
		t.Errorf("Good Friday colour = %s, want black (RG 128(b) + RG 132)", got)
	}
	// Holy Saturday IS still violet: RG 128(b)'s exception for it covers only
	// the deacon at the Easter Preconium and the celebrant at the renewal of
	// baptismal promises, not the day, and this model carries one colour per
	// day. Asserted in the other direction on purpose, so a careless "the
	// whole Triduum is black" change fails here.
	if got := temporalEF(holySat).Cel.Colour; got != Violet {
		t.Errorf("Holy Saturday colour = %s, want violet (unchanged)", got)
	}
}

// TestTemporalEFEmberDayRanks: RG 91 entry 18 -- the Ember days of Lent (and,
// unchanged, September and Advent) are II-class privileged ferias, not the
// ordinary III-class Lenten ferias around them. Before the fix, efEmberSlug
// had no Lent case at all, so Lent's Ember Wed/Fri/Sat fell through to the
// ordinary III-class Lenten-feria rank.
func TestTemporalEFEmberDayRanks(t *testing.T) {
	// Lent I Sunday 2025 = 2025-03-09; Ember Wed/Fri/Sat = +3/+5/+6.
	lentI := d("2025-03-09")
	for _, off := range []int{3, 5, 6} {
		day := lentI.AddDate(0, 0, off)
		got := temporalEF(day)
		if got.Cel.Rank != RankClass2 {
			t.Errorf("Lent Ember day %s rank = %s, want class-2 (%s)", day.Format("2006-01-02"), got.Cel.Rank, got.Cel.Slug)
		}
	}
}