summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 10:45:09 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 10:45:09 +0200
commitd571091e25e87758c3c77fbc8b2d935bcc8304a4 (patch)
tree5a523c231760040d57ebb2e556d17656b0c0ba81
parent44cb30b7ffcd776b26952080d1ec3587282a3db4 (diff)
downloadlectio-d571091e25e87758c3c77fbc8b2d935bcc8304a4.tar.gz
lectio-d571091e25e87758c3c77fbc8b2d935bcc8304a4.zip
feat(of): Christmas-season weekday ferials from catholic-resources.org (date-based)
Completes the seasonal weekday lectionary. The Christmas-season weekdays sit on the AdventChristmas page in a SHIFTED 6-column layout (Day in c[1], not c[2] like Advent's 7-column) -- the generator now finds the Day column wherever it is. Adds date-based slugs to temporal.go: christmas-dec-<29..31> (octave), christmas-jan-<2..7> (before Epiphany), christmas-after-epiphany-<weekday> (before the Baptism). Fixes the christmas-octave-fri coverage gap and the by-date/by-week collision. 362/362 seasonal ferial-days render across 2026-2029 with 0 gaps. Extends TestOFSeasonalFerials. All of Advent/Lent/Easter/Christmas weekdays now CR-sourced; only the Commons remain (optional -- memorials default to the ferial).
-rw-r--r--internal/caldata/of-lectionary.ini150
-rw-r--r--internal/caldata/of_test.go3
-rw-r--r--internal/calendar/temporal.go12
-rw-r--r--scripts/genlect-of-season-ferials-cr.py22
4 files changed, 181 insertions, 6 deletions
diff --git a/internal/caldata/of-lectionary.ini b/internal/caldata/of-lectionary.ini
index 8d36425..41b68cb 100644
--- a/internal/caldata/of-lectionary.ini
+++ b/internal/caldata/of-lectionary.ini
@@ -5000,3 +5000,153 @@ gospel = John 16:16-20
first = The Acts 18:1-8
psalm = Psalms 98:1,2-3ab,3cd-4
gospel = John 16:16-20
+
+[christmas-dec-29-I]
+first = 1 John 2:3-11
+psalm = Psalms 96:1-2a,2b-3,5b-6
+gospel = Luke 2:22-35
+
+[christmas-dec-29-II]
+first = 1 John 2:3-11
+psalm = Psalms 96:1-2a,2b-3,5b-6
+gospel = Luke 2:22-35
+
+[christmas-dec-30-I]
+first = 1 John 2:12-17
+psalm = Psalms 96:7-8a,8b-9,10
+gospel = Luke 2:36-40
+
+[christmas-dec-30-II]
+first = 1 John 2:12-17
+psalm = Psalms 96:7-8a,8b-9,10
+gospel = Luke 2:36-40
+
+[christmas-dec-31-I]
+first = 1 John 2:18-21
+psalm = Psalms 96:1-2,11-12,13
+gospel = John 1:1-18
+
+[christmas-dec-31-II]
+first = 1 John 2:18-21
+psalm = Psalms 96:1-2,11-12,13
+gospel = John 1:1-18
+
+[christmas-jan-2-I]
+first = 1 John 2:22-28
+psalm = Psalms 98:1,2-3ab,3cd-4
+gospel = John 1:19-28
+
+[christmas-jan-2-II]
+first = 1 John 2:22-28
+psalm = Psalms 98:1,2-3ab,3cd-4
+gospel = John 1:19-28
+
+[christmas-jan-3-I]
+first = 1 John 2:29-3:6
+psalm = Psalms 98:1,3cd-4,5-6
+gospel = John 1:29-34
+
+[christmas-jan-3-II]
+first = 1 John 2:29-3:6
+psalm = Psalms 98:1,3cd-4,5-6
+gospel = John 1:29-34
+
+[christmas-jan-4-I]
+first = 1 John 3:7-10
+psalm = Psalms 98:1,7-8,9
+gospel = John 1:35-42
+
+[christmas-jan-4-II]
+first = 1 John 3:7-10
+psalm = Psalms 98:1,7-8,9
+gospel = John 1:35-42
+
+[christmas-jan-5-I]
+first = 1 John 3:11-21
+psalm = Psalms 100:1b-2,3,4,5
+gospel = John 1:43-51
+
+[christmas-jan-5-II]
+first = 1 John 3:11-21
+psalm = Psalms 100:1b-2,3,4,5
+gospel = John 1:43-51
+
+[christmas-jan-6-I]
+first = 1 John 5:5-13
+psalm = Psalms 147:12-13,14-15,19-20
+gospel = Mark 1:7-11
+
+[christmas-jan-6-II]
+first = 1 John 5:5-13
+psalm = Psalms 147:12-13,14-15,19-20
+gospel = Mark 1:7-11
+
+[christmas-jan-7-I]
+first = 1 John 5:14-21
+psalm = Psalms 149:1-2,3-4,5+6a+9b
+gospel = John 2:1-11
+
+[christmas-jan-7-II]
+first = 1 John 5:14-21
+psalm = Psalms 149:1-2,3-4,5+6a+9b
+gospel = John 2:1-11
+
+[christmas-after-epiphany-mon-I]
+first = 1 John 3:22-4:6
+psalm = Psalms 2:7bc-8,10-12a
+gospel = Matthew 4:12-17,23-25
+
+[christmas-after-epiphany-mon-II]
+first = 1 John 3:22-4:6
+psalm = Psalms 2:7bc-8,10-12a
+gospel = Matthew 4:12-17,23-25
+
+[christmas-after-epiphany-tue-I]
+first = 1 John 4:7-10
+psalm = Psalms 72:1-2,3-4,7-8
+gospel = Mark 6:34-44
+
+[christmas-after-epiphany-tue-II]
+first = 1 John 4:7-10
+psalm = Psalms 72:1-2,3-4,7-8
+gospel = Mark 6:34-44
+
+[christmas-after-epiphany-wed-I]
+first = 1 John 4:11-18
+psalm = Psalms 72:1-2,10,12-13
+gospel = Mark 6:45-52
+
+[christmas-after-epiphany-wed-II]
+first = 1 John 4:11-18
+psalm = Psalms 72:1-2,10,12-13
+gospel = Mark 6:45-52
+
+[christmas-after-epiphany-thu-I]
+first = 1 John 4:19-5:4
+psalm = Psalms 72:1-2,14+15bc,17
+gospel = Luke 4:14-22a
+
+[christmas-after-epiphany-thu-II]
+first = 1 John 4:19-5:4
+psalm = Psalms 72:1-2,14+15bc,17
+gospel = Luke 4:14-22a
+
+[christmas-after-epiphany-fri-I]
+first = 1 John 5:5-13
+psalm = Psalms 147:12-13,14-15,19-20
+gospel = Luke 5:12-16
+
+[christmas-after-epiphany-fri-II]
+first = 1 John 5:5-13
+psalm = Psalms 147:12-13,14-15,19-20
+gospel = Luke 5:12-16
+
+[christmas-after-epiphany-sat-I]
+first = 1 John 5:14-21
+psalm = Psalms 149:1-2,3-4,5+6a+9b
+gospel = John 3:22-30
+
+[christmas-after-epiphany-sat-II]
+first = 1 John 5:14-21
+psalm = Psalms 149:1-2,3-4,5+6a+9b
+gospel = John 3:22-30
diff --git a/internal/caldata/of_test.go b/internal/caldata/of_test.go
index 086883a..ca5cc24 100644
--- a/internal/caldata/of_test.go
+++ b/internal/caldata/of_test.go
@@ -95,6 +95,9 @@ func TestOFSeasonalFerials(t *testing.T) {
{"easter-octave-mon-I", "Acts 2:14"}, // was absent
{"advent-dec-17-I", "Genesis 49"},
{"advent-dec-24-I", "2 Samuel 7"},
+ {"christmas-dec-29-I", "1 John 2:3-11"}, // Christmas octave, by date
+ {"christmas-jan-2-I", "1 John 2:22-28"}, // before Epiphany, by date
+ {"christmas-after-epiphany-mon-I", "1 John 3:22"}, // after Epiphany, by weekday
} {
var first string
for _, r := range TemporalReadings("new", tc.key) {
diff --git a/internal/calendar/temporal.go b/internal/calendar/temporal.go
index dadae49..b0dc653 100644
--- a/internal/calendar/temporal.go
+++ b/internal/calendar/temporal.go
@@ -223,13 +223,23 @@ func temporal(date time.Time, sel Selection) temporalDay {
return ferialDay(Advent, "advent-"+itoa(week)+"-"+weekdayAbbr(wd), Violet, week, privFeria)
case !date.Before(christmasThis): // Dec 25..Dec 31 — Christmas octave
+ // The octave-day ferials (Dec 29-31) have readings proper to the date;
+ // Dec 25-28 are the Nativity and its feasts (Stephen/John/Innocents).
+ if date.Day() >= 29 {
+ return ferialDay(Christmas, "christmas-dec-"+itoa(date.Day()), White, 0, false)
+ }
return ferialDay(Christmas, "christmas-octave-"+weekdayAbbr(wd), White, 0, false)
case !date.After(baptismThis): // Jan 1..Baptism — Christmas season
if sun {
return sundayDay(Christmas, "christmas-sunday-"+weekdayAbbr(wd), White, 0)
}
- return ferialDay(Christmas, "christmas-"+weekdayAbbr(wd), White, 0, false)
+ // Before Epiphany the weekdays are proper to the date (Jan 2-7); after
+ // Epiphany they are keyed by weekday (the days before the Baptism).
+ if date.Before(epiphany(y, sel)) {
+ return ferialDay(Christmas, "christmas-jan-"+itoa(date.Day()), White, 0, false)
+ }
+ return ferialDay(Christmas, "christmas-after-epiphany-"+weekdayAbbr(wd), White, 0, false)
default: // Ordinary Time (span 1: after Baptism..before Lent; span 2: after Pentecost..before Advent)
var week int
diff --git a/scripts/genlect-of-season-ferials-cr.py b/scripts/genlect-of-season-ferials-cr.py
index 13c56a3..4cca802 100644
--- a/scripts/genlect-of-season-ferials-cr.py
+++ b/scripts/genlect-of-season-ferials-cr.py
@@ -66,6 +66,12 @@ def slug_of(day):
d = re.sub(r"\s+", " ", st(day).lower())
m = re.match(r"december (\d+)", d) # late Advent Dec 17-24: proper to the date
if m and 17 <= int(m.group(1)) <= 24: return "advent-dec-" + m.group(1)
+ m = re.match(r"dec\.? (\d+)", d) # Christmas octave ferials Dec 29-31
+ if m and 29 <= int(m.group(1)) <= 31: return "christmas-dec-" + m.group(1)
+ m = re.match(r"\[?\s*jan\.? (\d+)", d) # Christmas weekdays before Epiphany, Jan 2-7
+ if m and 2 <= int(m.group(1)) <= 7: return "christmas-jan-" + m.group(1)
+ m = re.match(r"(monday|tuesday|wednesday|thursday|friday|saturday) after epiphany", d)
+ if m: return "christmas-after-epiphany-" + WD[m.group(1)]
if "ash wednesday" in d: return "lent-after-ashes-wed"
m = re.match(r"(thursday|friday|saturday) after ash", d)
if m: return "lent-after-ashes-" + WD[m.group(1)]
@@ -87,12 +93,18 @@ def main():
n = 0
for tr in re.split(r"<tr\b", h):
c = [x for x in re.findall(r"<td\b[^>]*>(.*?)</td>", tr, re.S)]
- if len(c) < 6: continue
- slug = slug_of(c[2])
- if not slug: continue
- cr[slug] = {"first": clean_cite(c[3]), "psalm": clean_cite(c[4]), "gospel": clean_cite(c[-1])}
+ if len(c) < 5: continue
+ # the "Day" column sits at c[2] (Advent's 7-col table) or c[1] (the
+ # Christmas 6-col table) — find it by which cell yields a slug, then the
+ # readings follow it (first, psalm, ..., gospel last).
+ di = next((i for i, cell in enumerate(c) if slug_of(cell)), None)
+ if di is None or di + 1 >= len(c): continue
+ slug = slug_of(c[di])
+ cr[slug] = {"first": clean_cite(c[di + 1]),
+ "psalm": clean_cite(c[di + 2]) if di + 2 < len(c) - 1 else "",
+ "gospel": clean_cite(c[-1])}
n += 1
- sys.stderr.write(f"{p}: {n} week-based seasonal ferials\n")
+ sys.stderr.write(f"{p}: {n} seasonal ferials mapped\n")
text = open(INI).read()
head = text[:text.index("\n[")]