aboutsummaryrefslogtreecommitdiff
path: root/internal/calendar/calendar.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/calendar/calendar.go')
-rw-r--r--internal/calendar/calendar.go62
1 files changed, 61 insertions, 1 deletions
diff --git a/internal/calendar/calendar.go b/internal/calendar/calendar.go
index e0f5217..99a24b3 100644
--- a/internal/calendar/calendar.go
+++ b/internal/calendar/calendar.go
@@ -29,7 +29,8 @@ func computeEF(date time.Time, sel Selection, layers []Layer) LiturgicalDay {
if !ok {
continue
}
- if sameDay(when, date) {
+ effective := transferIfImpededEF(cel, when)
+ if sameDay(effective, date) {
cands = append(cands, candidate{Cel: cel, Temporal: false, Season: td.Season})
}
}
@@ -75,6 +76,13 @@ func computeOF(date time.Time, sel Selection, layers []Layer) LiturgicalDay {
cands = append(cands, candidate{Cel: cel, Temporal: false, Season: td.Season})
}
}
+ // Movable Marian memorials that have no fixed date (so they cannot live in the
+ // sanctoral file): Mary, Mother of the Church, and the Immaculate Heart.
+ for _, mm := range movableMemorialsOF(date.Year()) {
+ if sameDay(mm.when, date) {
+ cands = append(cands, candidate{Cel: mm.cel, Temporal: false, Season: td.Season})
+ }
+ }
obs, others := pick(cands)
day := LiturgicalDay{
@@ -104,6 +112,31 @@ func celebrationDate(cel Celebration, year int, sel Selection) (time.Time, bool)
return resolveDate(cel.Date, year, Easter(year))
}
+// movableCel is a celebration whose date is computed (relative to Easter), not
+// read from a fixed-date layer.
+type movableCel struct {
+ when time.Time
+ cel Celebration
+}
+
+// movableMemorialsOF returns the Ordinary Form's two movable Marian memorials,
+// which have no fixed calendar date: Mary, Mother of the Church (the Monday after
+// Pentecost) and the Immaculate Heart of Mary (the Saturday after the Sacred
+// Heart, i.e. easter+69).
+func movableMemorialsOF(y int) []movableCel {
+ e := Easter(y)
+ mk := func(off int, slug, en, pl string) movableCel {
+ return movableCel{when: e.AddDate(0, 0, off), cel: Celebration{
+ Slug: slug, Rank: RankMemorial, Class: ClassBVM, Colour: White, Layer: "universal",
+ Name: map[string]string{"en": en, "pl": pl},
+ }}
+ }
+ return []movableCel{
+ mk(50, "mary-mother-of-the-church", "Mary, Mother of the Church", "Najświętszej Maryi Panny, Matki Kościoła"),
+ mk(69, "the-immaculate-heart-of-the-blessed-virgin-mary", "The Immaculate Heart of the Blessed Virgin Mary", "Niepokalanego Serca Najświętszej Maryi Panny"),
+ }
+}
+
// transferIfImpeded moves a solemnity off a band-1/2 temporal day to the next
// free day. Non-solemnities are never transferred (they yield instead).
func transferIfImpeded(cel Celebration, when time.Time, sel Selection) time.Time {
@@ -126,6 +159,33 @@ func transferIfImpeded(cel Celebration, when time.Time, sel Selection) time.Time
return day
}
+// transferIfImpededEF moves an EF sanctoral feast off a date it cannot be kept
+// on. Two rules cover the 1962 cases: All Souls, when Nov 2 is a Sunday, is
+// transferred to the Monday (Nov 3); and a I class feast impeded by a I class
+// temporal day (Holy Week, the Easter octave) is pushed forward to the next free
+// day — for the Annunciation in Holy Week this lands on the Monday after Low
+// Sunday, past the whole privileged octave. Lower-rank feasts are only
+// commemorated, never transferred.
+func transferIfImpededEF(cel Celebration, when time.Time) time.Time {
+ if when.Month() == time.November && when.Day() == 2 && when.Weekday() == time.Sunday &&
+ strings.Contains(cel.Slug, "all-souls") {
+ return when.AddDate(0, 0, 1)
+ }
+ if cel.Rank != RankClass1 {
+ return when
+ }
+ day := when
+ for i := 0; i < 30; i++ {
+ b := temporalEF(day)
+ if precedenceEF(candidate{Cel: b.Cel, Temporal: true, Season: b.Season}) <= 2 {
+ day = day.AddDate(0, 0, 1) // impeded by a I class temporal day; push forward
+ continue
+ }
+ return day
+ }
+ return day
+}
+
// buildCelebration types a RawCelebration.
func buildCelebration(slug string, rc RawCelebration) Celebration {
c := Celebration{Slug: strings.TrimSpace(slug), Name: map[string]string{}, Layer: rc.Fields["layer"]}