summaryrefslogtreecommitdiff
path: root/internal/caldata/readings.go
blob: 1b87b7a7ec111c2bc54d50b55163a0931cdc439d (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
package caldata

import (
	"sort"
	"time"

	"github.com/lukaszkasprzak/lectio/internal/calendar"
)

var partOrder = map[string]int{"first": 0, "psalm": 1, "second": 2, "acclamation": 3, "gospel": 4}

// ofFerial returns the day's Ordinary-Form ferial (weekday) readings, or nil.
func ofFerial(sel calendar.Selection, date time.Time, day calendar.LiturgicalDay) []calendar.Reading {
	if day.WeekdayCycle == "" || date.Weekday() == time.Sunday {
		return nil
	}
	if temp := calendar.Compute(date, sel, nil); temp.Observed.Layer == "temporal" {
		return TemporalReadings("new", temp.Observed.Slug+"-"+day.WeekdayCycle)
	}
	return nil
}

// Readings resolves a day's Mass readings: the observed celebration's inline
// propers, else the temporal lectionary for its slug, else -- for an EF
// weekday with no proper Mass -- the preceding Sunday's Mass (the 1962 rule
// that green-season ferias repeat the Sunday). Shared by the CLI (cli.dayReadings
// delegates here) and calfeed.Build (injected as its readings func).
func Readings(sel calendar.Selection, layers []calendar.Layer, date time.Time, day calendar.LiturgicalDay) []calendar.Reading {
	var rs []calendar.Reading
	for _, m := range day.Observed.Masses {
		rs = append(rs, m.Readings...)
	}
	if len(rs) > 0 {
		// OF: a memorial with only a PROPER gospel (or only a proper first) takes
		// the missing reading from the day's ferial -- e.g. the Guardian Angels are
		// the ferial first reading + their proper gospel (Matt 18), and St Barnabas
		// is his proper first (Acts 11) + the ferial gospel. A fully-proper feast
		// (first AND gospel present) is left untouched.
		have := map[string]bool{}
		for _, r := range rs {
			have[r.Part] = true
		}
		if sel.Form != "old" && !(have["first"] && have["gospel"]) {
			for _, fr := range ofFerial(sel, date, day) {
				if p := fr.Part; !have[p] && (p == "first" || p == "psalm" || p == "gospel") {
					rs = append(rs, fr)
					have[p] = true
				}
			}
			sort.SliceStable(rs, func(i, j int) bool { return partOrder[rs[i].Part] < partOrder[rs[j].Part] })
		}
		return rs
	}
	// temporal table: EF by bare slug; OF by slug+cycle. Sundays & solemnities
	// use the 3-year Sunday cycle (A/B/C); ferial weekdays use the 2-year
	// weekday cycle (I/II). Sunday-slugs and weekday-slugs are disjoint, so
	// trying both cycle suffixes is unambiguous.
	if sel.Form == "old" {
		if r := TemporalReadings("old", day.Observed.Slug); len(r) > 0 {
			return r
		}
	} else {
		for _, cyc := range []string{day.SundayCycle, day.WeekdayCycle} {
			if cyc == "" {
				continue
			}
			if r := TemporalReadings("new", day.Observed.Slug+"-"+cyc); len(r) > 0 {
				return r
			}
		}
	}
	// OF: a sanctoral memorial / optional memorial with no proper readings of its
	// own reads the FERIAL (weekday) readings of the day -- the ordinary OF rule.
	if sel.Form != "old" && day.Observed.Layer != "temporal" {
		if r := ofFerial(sel, date, day); len(r) > 0 {
			return r
		}
	}
	// EF: a green-season weekday with no proper Mass repeats the preceding Sunday.
	if sel.Form == "old" && day.Observed.Layer == "temporal" && date.Weekday() != time.Sunday {
		sun := date.AddDate(0, 0, -int(date.Weekday())) // preceding Sunday (Sunday = 0)
		sd := calendar.Compute(sun, sel, layers)
		return TemporalReadings(sel.Form, sd.Observed.Slug)
	}
	return nil
}