aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/temporal_ef.go
blob: 725aac247c08fc28967911ac0622892ee5295c56 (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
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
package calendar

import "time"

// Extraordinary Form (1962) season constants. Advent/Christmas/Lent/Easter are
// shared with the OF; these are EF-specific.
const (
	Septuagesima       Season = "septuagesima"
	Passiontide        Season = "passiontide"
	TimeAfterEpiphany  Season = "time-after-epiphany"
	TimeAfterPentecost Season = "time-after-pentecost"
)

// efChristTheKing is the last Sunday of October (the 1962 date — unlike the OF's
// last Sunday before Advent).
func efChristTheKing(year int) time.Time {
	oct31 := time.Date(year, 10, 31, 0, 0, 0, 0, time.UTC)
	return oct31.AddDate(0, 0, -int(oct31.Weekday())) // Sunday on/before Oct 31
}

// efColour is the default (Sunday/ferial) colour of an EF season.
func efColour(s Season) Colour {
	switch s {
	case Advent, Septuagesima, Lent, Passiontide:
		return Violet
	case Christmas, Easter_:
		return White
	default: // TimeAfterEpiphany, TimeAfterPentecost
		return Green
	}
}

// efSeason returns the 1962 season for date, by chronological date boundaries.
func efSeason(date time.Time, y int, easter time.Time) Season {
	adventThis := adventStart(y)
	christmasThis := time.Date(y, 12, 25, 0, 0, 0, 0, time.UTC)
	jan6 := time.Date(y, 1, 6, 0, 0, 0, 0, time.UTC)
	septStart := easter.AddDate(0, 0, -63) // Septuagesima Sunday
	ashWed := easter.AddDate(0, 0, -46)
	passionSun := easter.AddDate(0, 0, -14)
	paschalEnd := easter.AddDate(0, 0, 55) // Saturday after Pentecost; Trinity = +56

	switch {
	case !date.Before(adventThis) && date.Before(christmasThis):
		return Advent
	case !date.Before(christmasThis): // Dec 25..31
		return Christmas
	case date.Before(jan6): // Jan 1..5
		return Christmas
	case date.Before(septStart): // Epiphany .. before Septuagesima
		return TimeAfterEpiphany
	case date.Before(ashWed):
		return Septuagesima
	case date.Before(passionSun):
		return Lent
	case date.Before(easter):
		return Passiontide
	case !date.After(paschalEnd):
		return Easter_
	default:
		return TimeAfterPentecost
	}
}

func efCel(season Season, slug string, col Colour, rank Rank, week int) temporalDay {
	return temporalDay{
		Season: season, Colour: col, Week: week, Rank: rank,
		Sunday: false,
		Cel:    Celebration{Slug: slug, Rank: rank, Colour: col, Layer: "temporal"},
	}
}

// temporalEF computes the 1962 temporal identity of date. Season is exact
// (oracle-validated); ranks/weeks are best-effort for display.
func temporalEF(date time.Time) temporalDay {
	date = date.UTC().Truncate(24 * time.Hour)
	y := date.Year()
	easter := Easter(y)
	season := efSeason(date, y, easter)
	col := efColour(season)
	sun := date.Weekday() == time.Sunday

	// Major feasts of the Lord (I class): nice titles + colour.
	switch {
	case date.Month() == time.December && date.Day() == 25:
		return efCel(Christmas, "ef-nativity", White, RankClass1, 0)
	case date.Month() == time.January && date.Day() == 1:
		return efCel(Christmas, "ef-circumcision", White, RankClass1, 0)
	case date.Month() == time.January && date.Day() == 6:
		return efCel(TimeAfterEpiphany, "ef-epiphany", White, RankClass1, 0)
	case sameDay(date, easter.AddDate(0, 0, -46)):
		return efCel(Lent, "ef-ash-wednesday", Violet, RankClass1, 0)
	case sameDay(date, easter.AddDate(0, 0, -14)):
		return efCel(Passiontide, "ef-passion-sunday", Violet, RankClass1, 1)
	case sameDay(date, easter.AddDate(0, 0, -7)):
		return efCel(Passiontide, "ef-palm-sunday", Violet, RankClass1, 2)
	case sameDay(date, easter):
		return efCel(Easter_, "ef-easter-sunday", White, RankClass1, 1)
	case sameDay(date, easter.AddDate(0, 0, 7)):
		return efCel(Easter_, "ef-low-sunday", White, RankClass1, 2)
	case sameDay(date, easter.AddDate(0, 0, 39)):
		return efCel(Easter_, "ef-ascension", White, RankClass1, 0)
	case sameDay(date, easter.AddDate(0, 0, 49)):
		return efCel(Easter_, "ef-pentecost", Red, RankClass1, 0)
	case sameDay(date, easter.AddDate(0, 0, 56)):
		return efCel(TimeAfterPentecost, "ef-trinity", White, RankClass1, 1)
	case sameDay(date, easter.AddDate(0, 0, 60)):
		return efCel(TimeAfterPentecost, "ef-corpus-christi", White, RankClass1, 0)
	case sameDay(date, easter.AddDate(0, 0, 68)):
		return efCel(TimeAfterPentecost, "ef-sacred-heart", White, RankClass1, 0)
	case sameDay(date, efChristTheKing(y)):
		return efCel(TimeAfterPentecost, "ef-christ-the-king", White, RankClass1, 0)
	}

	// Sundays vs ferias of the current season.
	if sun {
		rank := RankClass2
		if season == Advent && sameDay(date, adventStart(y)) {
			rank = RankClass1 // 1st Sunday of Advent
		}
		return efCel(season, "ef-"+string(season)+"-sunday", col, rank, efWeek(date, season, y, easter))
	}
	rank := RankClass4 // per-annum feria
	if season == Advent || season == Lent || season == Passiontide || season == Septuagesima {
		rank = RankClass3 // penitential ferias rank higher
	}
	return efCel(season, "ef-"+string(season)+"-feria", col, rank, efWeek(date, season, y, easter))
}

// efWeek is a best-effort week-within-season number for display (not
// oracle-checked). Time after Pentecost counts Sundays from Pentecost.
func efWeek(date time.Time, season Season, y int, easter time.Time) int {
	switch season {
	case Advent:
		return daysBetween(adventStart(y), date)/7 + 1
	case TimeAfterPentecost:
		return daysBetween(easter.AddDate(0, 0, 49), date) / 7 // Sundays after Pentecost
	case Lent:
		return daysBetween(easter.AddDate(0, 0, -42), date)/7 + 1 // from 1st Sunday of Lent
	case TimeAfterEpiphany:
		jan6 := time.Date(y, 1, 6, 0, 0, 0, 0, time.UTC)
		return daysBetween(jan6, date)/7 + 1
	default:
		return 0
	}
}