diff options
Diffstat (limited to 'internal/calendar')
| -rw-r--r-- | internal/calendar/temporal_ef.go | 30 | ||||
| -rw-r--r-- | internal/calendar/temporal_ef_test.go | 46 |
2 files changed, 76 insertions, 0 deletions
diff --git a/internal/calendar/temporal_ef.go b/internal/calendar/temporal_ef.go index 6828f35..0cd4b41 100644 --- a/internal/calendar/temporal_ef.go +++ b/internal/calendar/temporal_ef.go @@ -159,6 +159,23 @@ func temporalEF(date time.Time) temporalDay { return efCel(Christmas, "ef-nativity", White, RankClass1, 0) case date.Month() == time.January && date.Day() == 1: return efCel(Christmas, "ef-circumcision", White, RankClass1, 0) + // RG 17(a): "festum Ss.mi Nominis Iesu, celebrandum dominica quae occurrit + // a die 2 ad 5 ianuarii, secus die 2 ianuarii" -- the Most Holy Name of + // Jesus is kept on the Sunday falling 2-5 January, and when no Sunday falls + // in that window, on 2 January instead. + // + // The fallback is not decoration: the window is four days wide, so in + // roughly three years in seven no Sunday lands in it and 2 January carries + // the feast. Without the fallback lectio simply had no Holy Name office at + // all in those years. + // + // Placed before the Epiphany case below and after the Circumcision above, + // so 1 January keeps its own feast and 6 January keeps Epiphany. + case date.Month() == time.January && date.Day() >= 2 && date.Day() <= 5 && + date.Weekday() == time.Sunday: + return efCel(Christmas, "ef-holy-name-sunday", White, RankClass2, 0) + case date.Month() == time.January && date.Day() == 2 && !sundayFallsJan2to5(y): + return efCel(Christmas, "ef-holy-name", White, RankClass2, 0) case date.Month() == time.January && date.Day() == 6: // Christmas, not TimeAfterEpiphany: RG 72 puts Epiphany INSIDE Christmas // Time, which runs to 13 January. The slug is unchanged. @@ -330,6 +347,19 @@ func temporalEF(date time.Time) temporalDay { return efCel(season, "ef-"+string(slugSeason)+"-"+strconv.Itoa(week)+"-"+weekdayLower(date), col, rank, week) } +// sundayFallsJan2to5 reports whether any Sunday falls in RG 17(a)'s own window, +// 2 to 5 January inclusive. When one does the Holy Name is kept on it; when +// none does the feast falls back to 2 January, which is what "secus die 2 +// ianuarii" provides for. +func sundayFallsJan2to5(y int) bool { + for day := 2; day <= 5; day++ { + if time.Date(y, time.January, day, 0, 0, 0, 0, time.UTC).Weekday() == time.Sunday { + return true + } + } + return false +} + // firstSundayAfterEpiphany returns the first Sunday strictly after 6 January // of year y -- the anchor the weeks after Epiphany are numbered from. When // Epiphany itself falls on a Sunday the next one is meant, which is why the diff --git a/internal/calendar/temporal_ef_test.go b/internal/calendar/temporal_ef_test.go index d930cc7..c1571b0 100644 --- a/internal/calendar/temporal_ef_test.go +++ b/internal/calendar/temporal_ef_test.go @@ -360,3 +360,49 @@ func TestTemporalEFPassiontideWeeks(t *testing.T) { } } } + +// TestTemporalEFHolyName pins RG 17(a): "festum Ss.mi Nominis Iesu, +// celebrandum dominica quae occurrit a die 2 ad 5 ianuarii, secus die 2 +// ianuarii" -- the Most Holy Name of Jesus is kept on the Sunday falling 2-5 +// January, and when no Sunday falls in that window, on 2 January instead. +// +// The FALLBACK is the half worth testing hardest. The window is four days +// wide, so in about three years in seven no Sunday lands in it; without the +// fallback there is simply no Holy Name office in those years, which is a +// missing II-class feast rather than a misnamed one. +func TestTemporalEFHolyName(t *testing.T) { + // Years where a Sunday does fall in the window, at each end of it. + for _, c := range []struct{ date, slug string }{ + {"2026-01-04", "ef-holy-name-sunday"}, // Sunday the 4th + {"2027-01-03", "ef-holy-name-sunday"}, // Sunday the 3rd + {"2028-01-02", "ef-holy-name-sunday"}, // Sunday the 2nd -- the window's first day + } { + if got := temporalEF(d(c.date)).Cel.Slug; got != c.slug { + t.Errorf("%s: slug = %q, want %q", c.date, got, c.slug) + } + } + // 2029: no Sunday falls 2-5 January, so 2 January carries the feast. + if got := temporalEF(d("2029-01-02")).Cel.Slug; got != "ef-holy-name" { + t.Errorf("2029-01-02: slug = %q, want ef-holy-name -- no Sunday falls 2-5 January "+ + "that year, so RG 17(a)'s \"secus die 2 ianuarii\" applies", got) + } + // And in a year where a Sunday DOES fall in the window, 2 January must NOT + // also carry it -- the fallback is an alternative, not an addition. + if got := temporalEF(d("2026-01-02")).Cel.Slug; got == "ef-holy-name" { + t.Errorf("2026-01-02: slug = %q, but the Sunday the 4th carries the feast that "+ + "year -- the fallback must not fire as well", got) + } + // Sanity on the helper itself, across the whole fixture range: every year + // gets the feast exactly once, by one shape or the other. + for y := 2005; y <= 2050; y++ { + n := 0 + for day := 2; day <= 5; day++ { + if s := temporalEF(time.Date(y, time.January, day, 0, 0, 0, 0, time.UTC)).Cel.Slug; s == "ef-holy-name" || s == "ef-holy-name-sunday" { + n++ + } + } + if n != 1 { + t.Errorf("%d: the Holy Name appears %d times in 2-5 January, want exactly 1", y, n) + } + } +} |
