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

import "sort"

// efRankOrder orders EF (1960) ranks: class-1 highest, then class-2..4,
// commemoration, ferial lowest.
func efRankOrder(r Rank) int {
	switch r {
	case RankClass1:
		return 5
	case RankClass2:
		return 4
	case RankClass3:
		return 3
	case RankClass4:
		return 2
	case RankCommemoration:
		return 1
	default: // ferial / unset
		return 0
	}
}

// precedenceEF ranks an EF candidate for occurrence (lower = higher precedence):
// by class first, then a tie-break at equal class (1960 occurrence table).
//
// The tie-break is asymmetric by season. An ORDINARY feria (per annum, Advent,
// Septuagesima) yields to an equal-class feast — the feast is celebrated and the
// feria commemorated (e.g. St Francis Xavier, Dec 3, on an Advent feria). The
// ferias of Lent and Passiontide are privileged: they outrank an equal-class
// feast, which is only commemorated. Sundays and named temporal feasts likewise
// win their ties. The *2 class spacing means the ±1 tie-break never crosses a
// class boundary.
func precedenceEF(c candidate) int {
	p := (6 - efRankOrder(c.Cel.Rank)) * 2 // class-1 -> 2, ferial -> 12
	if c.Temporal {
		ordinaryFeria := (c.Cel.Rank == RankClass3 || c.Cel.Rank == RankClass4) &&
			c.Season != Lent && c.Season != Passiontide
		if ordinaryFeria {
			p++ // an ordinary feria yields to an equal-class feast
		} else {
			p-- // Sundays, named feasts, and penitential ferias win their ties
		}
	} else if c.Cel.Class == ClassLord && c.Cel.Rank == RankClass2 {
		// A II class feast of the Lord takes the place of a II class Sunday it
		// falls on (unlike a saint's feast, which is only commemorated). Give it
		// the edge over the Sunday's tie-break bonus.
		p -= 2
	}
	return p
}

// pickEF returns the observed EF celebration and the commemorations, ordered
// deterministically by precedence then slug.
func pickEF(cands []candidate) (candidate, []candidate) {
	sorted := make([]candidate, len(cands))
	copy(sorted, cands)
	sort.SliceStable(sorted, func(i, j int) bool {
		pi, pj := precedenceEF(sorted[i]), precedenceEF(sorted[j])
		if pi != pj {
			return pi < pj
		}
		return sorted[i].Cel.Slug < sorted[j].Cel.Slug
	})
	return sorted[0], sorted[1:]
}