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
|
package calendar
import "sort"
// candidate is one celebration competing for a given day: the temporal day, or
// a sanctoral celebration landing on it. The flags come from the temporal
// builder (Task 4) or are empty for sanctoral entries.
type candidate struct {
Cel Celebration
Temporal bool
Privileged bool // band 1-2 temporal day
PrivFeria bool // band 9 privileged feria
Sunday bool // Ordinary/Christmas Sunday (band 6)
Season Season
}
// precedence maps a candidate to its band in the Table of Liturgical Days
// (Universal Norms, 1969) — lower = higher precedence. "Proper" celebrations
// (a non-universal, non-temporal layer) sit one band below their General
// Calendar counterpart.
func precedence(c candidate) int {
proper := c.Cel.Layer != "" && c.Cel.Layer != "universal" && c.Cel.Layer != "temporal"
if c.Temporal {
switch {
case c.Season == Triduum:
return 1
case c.Privileged:
return 2
case c.Sunday: // Ordinary/Christmas Sunday (checked before the solemnity-rank fallback)
return 6
case c.Cel.Rank == RankSolemnity: // Trinity, Corpus Christi, Sacred Heart, Christ the King
return 3
case c.Cel.Rank == RankFeast: // feasts of the Lord (Baptism, Holy Family)
return 5
case c.PrivFeria:
return 9
default:
return 13
}
}
switch c.Cel.Rank {
case RankSolemnity:
if proper {
return 4
}
return 3
case RankFeast:
switch {
case proper:
return 8
case c.Cel.Class == ClassLord: // feasts of the Lord in the General Calendar
return 5
default:
return 7
}
case RankMemorial:
if proper {
return 11
}
return 10
case RankOptional:
// An optional memorial does NOT displace the weekday as the day's
// DEFAULT observed celebration -- the ferial is celebrated unless the
// priest chooses the option. It sorts just below the ferial so it lands
// in Others (shown as an option), matching the General Roman Calendar's
// default (calapi's celebrations[0] on such a day is the ferial).
return 14
default:
return 13
}
}
// pick returns the highest-precedence candidate as observed and the rest as
// others (commemorations / optional memorials). Ordering is deterministic:
// by precedence band, then by slug, so equal-band collisions resolve the same
// way on every run.
func pick(cands []candidate) (candidate, []candidate) {
sorted := make([]candidate, len(cands))
copy(sorted, cands)
sort.SliceStable(sorted, func(i, j int) bool {
pi, pj := precedence(sorted[i]), precedence(sorted[j])
if pi != pj {
return pi < pj
}
return sorted[i].Cel.Slug < sorted[j].Cel.Slug
})
// General Norms 60: when two obligatory memorials fall on the same day, both
// become optional and the weekday (ferial) is celebrated instead -- UNLESS one
// is Mary, Mother of the Church, whose 2018 institution decree gives it
// precedence over a coinciding obligatory memorial (then it is observed).
if len(sorted) >= 2 && !sorted[0].Temporal && !sorted[1].Temporal &&
sorted[0].Cel.Rank == RankMemorial && sorted[1].Cel.Rank == RankMemorial {
if i := slugIndex(sorted, "mary-mother-of-the-church"); i >= 0 {
motc := sorted[i]
sorted = append(sorted[:i], sorted[i+1:]...)
sorted = append([]candidate{motc}, sorted...)
} else {
for i, c := range sorted {
if c.Temporal {
ferial := sorted[i]
sorted = append(sorted[:i], sorted[i+1:]...)
sorted = append([]candidate{ferial}, sorted...)
break
}
}
}
}
return sorted[0], sorted[1:]
}
// slugIndex returns the position of the candidate with the given slug, or -1.
func slugIndex(cands []candidate, slug string) int {
for i, c := range cands {
if c.Cel.Slug == slug {
return i
}
}
return -1
}
|