aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/precedence.go
blob: 88cc0d87e51d76c6bc741ca1f872d61c81e9a5fe (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
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
	})
	return sorted[0], sorted[1:]
}