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:] }