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 } // Within a band, higher liturgical dignity wins (Table of Liturgical Days: // of the Lord, then of the BVM, then of saints) -- so a Lord's solemnity // (Sacred Heart, Corpus Christi) outranks a coinciding saint's solemnity // rather than losing an alphabetical tie. if di, dj := dignity(sorted[i].Cel), dignity(sorted[j].Cel); di != dj { return di < dj } 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:] } // dignity ranks a celebration within its precedence band by the Table of // Liturgical Days' order of dignity: of the Lord (0), of the BVM (1), of saints // or unclassified (2). func dignity(c Celebration) int { switch c.Class { case ClassLord: return 0 case ClassBVM: return 1 default: return 2 } } // 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 }