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
|
package calendar_test
import (
"encoding/json"
"os"
"sort"
"strings"
"testing"
"time"
"github.com/lukaszkasprzak/lectio/internal/caldata"
"github.com/lukaszkasprzak/lectio/internal/calendar"
)
type efOracleDay struct {
Tempora string `json:"tempora"`
Title string `json:"title"`
Rank int `json:"rank"`
Colour string `json:"colour"`
}
// efSeasonFromString maps a missalemeum tempora/title phrase (for a day in the
// given month) to lectio's EF season. Order matters. Two position-dependent
// subtleties: the Whit-octave weekdays ("Monday after Pentecost") are still
// Paschaltide, so only "Sunday after Pentecost" is Time after Pentecost; and the
// resumed Sundays after Epiphany (celebrated in autumn) sit within Time after
// Pentecost, so an "after Epiphany" phrase in the second half of the year maps
// there, not to Time after Epiphany.
func efSeasonFromString(s string, month int) calendar.Season {
s = strings.ToLower(s)
has := func(subs ...string) bool {
for _, sub := range subs {
if strings.Contains(s, sub) {
return true
}
}
return false
}
switch {
case has("vigil of christmas"): // last day of Advent
return calendar.Advent
case has("advent"):
return calendar.Advent
case has("sunday after pentecost"), has("trinity", "christ the king", "corpus christi"):
return calendar.TimeAfterPentecost
case has("after epiphany", "epiphany"):
if month >= 6 { // resumed Epiphany Sundays, celebrated within Time after Pentecost
return calendar.TimeAfterPentecost
}
return calendar.TimeAfterEpiphany
case has("passion", "palm", "holy week", "maundy", "good friday", "holy saturday"):
return calendar.Passiontide
case has("septuagesima", "sexagesima", "quinquagesima"):
return calendar.Septuagesima
case has("ash wednesday", "lent"):
return calendar.Lent
case has("pentecost", "ascension", "rogation", "after easter", "easter", "low sunday", "paschal", "whit"):
return calendar.Easter_ // incl. the Whit octave (Mon-Sat after Pentecost)
case has("christmas", "nativity", "circumcision", "holy name", "octave day of christ"):
return calendar.Christmas
default:
return "" // unknown phrase -> skip this day
}
}
// TestOracleEF diffs the EF engine against missalemeum (Divinum Officium data).
// Season is asserted strictly (validates temporalEF); rank/colour are reported
// informationally (partial 1962 sanctoral).
func TestOracleEF(t *testing.T) {
raw, err := os.ReadFile("testdata/oracle-ef.json")
if err != nil {
t.Skip("EF oracle snapshot missing; run scripts/build-oracle-ef.sh")
}
var oracle map[string]efOracleDay
if err := json.Unmarshal(raw, &oracle); err != nil {
t.Fatal(err)
}
sel := calendar.DefaultSelection()
sel.Form = "old"
layers := []calendar.Layer{caldata.Tridentine()}
dates := make([]string, 0, len(oracle))
for d := range oracle {
dates = append(dates, d)
}
sort.Strings(dates)
var seasonMiss, skipped, total int
shown := 0
for _, date := range dates {
od := oracle[date]
src := od.Tempora
if src == "" {
src = od.Title
}
day, _ := time.Parse("2006-01-02", date)
want := efSeasonFromString(src, int(day.Month()))
if want == "" {
skipped++
continue
}
total++
got := calendar.Compute(day.UTC(), sel, layers)
if got.Season != want {
seasonMiss++
if shown < 25 {
t.Errorf("%s: EF season got %q want %q (from %q)", date, got.Season, want, src)
shown++
}
}
}
t.Logf("EF oracle: %d checked, %d skipped(unmapped), %d season mismatches", total, skipped, seasonMiss)
if seasonMiss > 0 {
t.Fatalf("%d/%d EF season mismatches vs missalemeum — temporalEF is wrong", seasonMiss, total)
}
}
|