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]+)$`) // -- 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 { // -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, "-", " ")) }