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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
package calendar
import (
"regexp"
"strconv"
"strings"
)
var weekdayName = map[string]string{
"mon": "Monday", "tue": "Tuesday", "wed": "Wednesday", "thu": "Thursday", "fri": "Friday", "sat": "Saturday", "sun": "Sunday",
"monday": "Monday", "tuesday": "Tuesday", "wednesday": "Wednesday", "thursday": "Thursday", "friday": "Friday", "saturday": "Saturday", "sunday": "Sunday",
}
// namedDay maps a computed temporal slug (with any "ef-" prefix removed) to its
// proper English name, for days that have one.
var namedDay = map[string]string{
"triduum-thu": "Holy Thursday", "triduum-fri": "Good Friday", "triduum-sat": "Holy Saturday",
"maundy-thursday": "Holy Thursday", "good-friday": "Good Friday", "holy-saturday": "Holy Saturday",
"palm-sunday": "Palm Sunday", "passion-sunday": "Passion Sunday", "low-sunday": "Second Sunday of Easter",
"trinity-sunday": "The Most Holy Trinity", "trinity": "Trinity Sunday",
"corpus-christi": "The Body and Blood of Christ", "sacred-heart": "The Most Sacred Heart of Jesus",
"christ-the-king": "Our Lord Jesus Christ, King of the Universe",
"easter-sunday": "Easter Sunday", "pentecost": "Pentecost Sunday", "ascension": "The Ascension of the Lord",
"holy-family": "The Holy Family of Jesus, Mary and Joseph", "baptism-of-the-lord": "The Baptism of the Lord",
"epiphany": "The Epiphany of the Lord", "christmas": "The Nativity of the Lord", "nativity": "The Nativity of the Lord",
"circumcision": "The Circumcision of the Lord", "ash-wednesday": "Ash Wednesday",
"ascension-vigil": "Vigil of the Ascension", "pentecost-vigil": "Vigil of Pentecost",
"christmas-sunday-sun": "Second Sunday after the Nativity",
"mary-mother-of-god-octave-of-christmas": "Mary, the Holy Mother of God",
}
var (
reSundayNum = regexp.MustCompile(`^(ordinary|advent|lent|easter)-sunday-(\d+)$`)
reWeekday = regexp.MustCompile(`^(.+)-(\d+)-([a-z]+)$`) // <season>-<week>-<weekday>
reOctave = regexp.MustCompile(`^easter-octave-([a-z]+)$`)
reAfterAsh = regexp.MustCompile(`^lent-after-ashes-([a-z]+)$`)
reHolyWeek = regexp.MustCompile(`^holy-week-([a-z]+)$`)
reAdventDec = regexp.MustCompile(`^advent-dec-(\d+)$`)
reXmasDate = regexp.MustCompile(`^christmas-(dec|jan)-(\d+)$`)
reAftEpiph = regexp.MustCompile(`^christmas-after-epiphany-([a-z]+)$`)
reEmber = regexp.MustCompile(`^(september|advent)-ember-([a-z]+)$`)
reSeasonSun = regexp.MustCompile(`^(.+)-sunday-(\d+)$`)
)
func ordinal(n int) string {
s := strconv.Itoa(n)
if n%100 >= 11 && n%100 <= 13 {
return s + "th"
}
switch n % 10 {
case 1:
return s + "st"
case 2:
return s + "nd"
case 3:
return s + "rd"
}
return s + "th"
}
func titleCase(s string) string {
small := map[string]bool{"of": true, "the": true, "in": true, "after": true, "before": true}
words := strings.Fields(s)
for i, w := range words {
if i > 0 && small[w] {
continue
}
words[i] = strings.ToUpper(w[:1]) + w[1:]
}
return strings.Join(words, " ")
}
// seasonPhrase renders a season slug ("ordinary", "time-after-pentecost") and the
// preposition that introduces its numbered weeks.
func seasonPhrase(season string) (name, prep string) {
switch season {
case "ordinary":
return "Ordinary Time", "in"
case "advent", "lent", "easter", "septuagesima", "passiontide":
return titleCase(season), "of"
}
if rest := strings.TrimPrefix(season, "time-after-"); rest != season {
return titleCase(rest), "after" // e.g. "Week after Pentecost"
}
return titleCase(strings.ReplaceAll(season, "-", " ")), "of"
}
// HumanizeSlug turns a computed temporal-day slug into a proper English name (used
// when a temporal celebration carries no explicit name).
func HumanizeSlug(slug string) string {
s := strings.TrimPrefix(slug, "ef-")
if n, ok := namedDay[s]; ok {
return n
}
if m := reSundayNum.FindStringSubmatch(s); m != nil {
n, _ := strconv.Atoi(m[2])
name, _ := seasonPhrase(m[1])
if m[1] == "ordinary" {
return ordinal(n) + " Sunday in " + name
}
return ordinal(n) + " Sunday of " + name
}
if m := reOctave.FindStringSubmatch(s); m != nil {
return weekdayName[m[1]] + " in the Octave of Easter"
}
if m := reAfterAsh.FindStringSubmatch(s); m != nil {
if m[1] == "wed" {
return "Ash Wednesday"
}
return weekdayName[m[1]] + " after Ash Wednesday"
}
if m := reHolyWeek.FindStringSubmatch(s); m != nil {
return weekdayName[m[1]] + " of Holy Week"
}
if m := reAdventDec.FindStringSubmatch(s); m != nil {
n, _ := strconv.Atoi(m[1])
return "December " + strconv.Itoa(n)
}
if m := reXmasDate.FindStringSubmatch(s); m != nil {
n, _ := strconv.Atoi(m[2])
if m[1] == "dec" {
return "December " + strconv.Itoa(n)
}
return "January " + strconv.Itoa(n)
}
if m := reAftEpiph.FindStringSubmatch(s); m != nil {
return weekdayName[m[1]] + " after the Epiphany"
}
if m := reEmber.FindStringSubmatch(s); m != nil {
return "Ember " + weekdayName[m[2]] + " of " + titleCase(m[1])
}
if m := reSeasonSun.FindStringSubmatch(s); m != nil { // <multiword-season>-sunday-N (EF)
n, _ := strconv.Atoi(m[2])
name, prep := seasonPhrase(m[1])
return ordinal(n) + " Sunday " + prep + " " + name
}
if m := reWeekday.FindStringSubmatch(s); m != nil {
if wd, ok := weekdayName[m[3]]; ok {
n, _ := strconv.Atoi(m[2])
name, prep := seasonPhrase(m[1])
if n == 0 { // the pre-Palm-Sunday week of Passiontide has no week number
if m[1] == "passiontide" {
return wd + " of Passion Week"
}
return wd + " " + prep + " " + name
}
return wd + " of the " + ordinal(n) + " Week " + prep + " " + name
}
}
return titleCase(strings.ReplaceAll(s, "-", " "))
}
|