aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--cmd/lectio-ef-dump/main.go136
-rw-r--r--go.mod10
-rw-r--r--go.sum30
-rw-r--r--internal/caldata/caldata_test.go307
-rw-r--r--internal/caldata/tridentine-calendar.ini66
-rw-r--r--internal/calendar/calendar.go124
-rw-r--r--internal/calendar/oracle_ef_test.go259
-rw-r--r--internal/calendar/precedence_ef.go86
-rw-r--r--internal/calendar/precedence_ef_repro_test.go160
-rw-r--r--internal/calendar/precedence_ef_test.go67
-rw-r--r--internal/calendar/temporal_ef.go81
-rw-r--r--internal/calendar/temporal_ef_test.go92
-rw-r--r--internal/calendar/testdata/oracle-ef.json7770
-rw-r--r--internal/calendar/types.go12
-rw-r--r--internal/i18n/golden_test.go19
-rw-r--r--internal/i18n/i18n.go14
-rw-r--r--internal/i18n/lang/en.ini13
-rw-r--r--internal/i18n/lang/pl.ini13
-rw-r--r--internal/i18n/vocab_test.go98
-rw-r--r--internal/liturgy/section.go9
-rw-r--r--internal/readings/offline.go125
-rw-r--r--internal/readings/partids_test.go75
-rw-r--r--internal/readings/readings_test.go37
-rw-r--r--internal/render/render.go8
-rw-r--r--mobile/mobile.go229
-rw-r--r--mobile/mobile_test.go211
-rwxr-xr-xscripts/build-oracle-ef.sh56
-rw-r--r--scripts/gen-sanctoral-ef.go472
29 files changed, 7616 insertions, 2969 deletions
diff --git a/.gitignore b/.gitignore
index 0e4cf7b..8868333 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,9 @@ sources/getbible/
*.swp
.idea/
.vscode/
+
+# built by cmd/dlectio-gen
+/dlectio-gen
+
+# subagent-driven-development scratch (ledger, briefs, review packages)
+.superpowers/
diff --git a/cmd/lectio-ef-dump/main.go b/cmd/lectio-ef-dump/main.go
new file mode 100644
index 0000000..e7d014a
--- /dev/null
+++ b/cmd/lectio-ef-dump/main.go
@@ -0,0 +1,136 @@
+// Command lectio-ef-dump prints lectio's Extraordinary Form (1962) calendar,
+// one line per day, for use as colitur's differential oracle.
+//
+// Output format is colitur's `colitur day <year>` line, PLUS two display-name
+// columns colitur's own line does not carry (so a straight `diff` against
+// colitur's output must ignore trailing fields, not compare them line for
+// line):
+//
+// YYYY-MM-DD weekday season week slug rank colour name_en name_pl [+other-slug]...
+//
+// name_en and name_pl are the observed celebration's own display name in
+// English and Polish (Celebration.Name["en"]/["pl"]), spaces replaced with
+// "_" so the line stays whitespace-delimited; "-" where the name is empty
+// (a bare feria with no proper name). Added after a regeneration of
+// internal/caldata/tridentine-calendar.ini once deleted all 322 Polish
+// names (name.pl 322 -> 0) with nothing in this repository's test suite OR
+// this dumper noticing: the season/rank/colour columns this tool already
+// printed were all still correct, since name is a wholly separate field
+// naming.CelebrationName reads independently. This dumper is what the
+// task's own before/after diff verification is run against, so it needed
+// to be structurally capable of seeing a name regression, not just told to
+// look harder next time.
+//
+// Every column carries lectio's OWN vocabulary (season names, slugs, rank and
+// colour spellings) -- this dumper does not translate lectio's values into
+// colitur's. The mapping between the two vocabularies is the differential
+// comparator's job, not this dumper's; translating here would hide real
+// divergences behind an already-reconciled view.
+//
+// The trailing "+other-slug" tokens are lectio's Others: the sanctoral
+// candidates that lost the day's precedence contest. lectio has no RG 111
+// commemoration-admission logic, so this is the set of losing candidates, not
+// the set of admitted commemorations -- the two are different things. See the
+// task report for detail; the comparator does not diff this column.
+//
+// Usage: lectio-ef-dump <from-year> <to-year>
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/caldata"
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+func main() {
+ os.Exit(run(os.Args[1:], os.Stdout, os.Stderr))
+}
+
+// run is lectio-ef-dump's testable entry point: parse args, compute, and
+// write. Returns a process exit code (0 ok, 1 runtime error, 2 usage error).
+func run(args []string, stdout, stderr io.Writer) int {
+ if len(args) != 2 {
+ fmt.Fprintln(stderr, "usage: lectio-ef-dump <from-year> <to-year>")
+ return 2
+ }
+ from, errFrom := strconv.Atoi(args[0])
+ to, errTo := strconv.Atoi(args[1])
+ if errFrom != nil || errTo != nil {
+ fmt.Fprintln(stderr, "lectio-ef-dump: from-year and to-year must be integers")
+ return 2
+ }
+ if from > to {
+ fmt.Fprintf(stderr, "lectio-ef-dump: from-year %d is after to-year %d\n", from, to)
+ return 2
+ }
+
+ sel := calendar.DefaultSelection()
+ sel.Form = "old"
+ layers := []calendar.Layer{caldata.Tridentine()}
+
+ w := bufio.NewWriter(stdout)
+ for y := from; y <= to; y++ {
+ start := time.Date(y, time.January, 1, 0, 0, 0, 0, time.UTC)
+ end := time.Date(y, time.December, 31, 0, 0, 0, 0, time.UTC)
+ for d := start; !d.After(end); d = d.AddDate(0, 0, 1) {
+ day := calendar.Compute(d, sel, layers)
+ if _, err := w.WriteString(dumpLine(day)); err != nil {
+ fmt.Fprintf(stderr, "lectio-ef-dump: %v\n", err)
+ return 1
+ }
+ }
+ }
+ if err := w.Flush(); err != nil {
+ fmt.Fprintf(stderr, "lectio-ef-dump: %v\n", err)
+ return 1
+ }
+ return 0
+}
+
+// dumpName renders a display-name field: spaces become "_" so the line stays
+// whitespace-delimited (names routinely contain spaces, e.g. "St. Thomas
+// Becket", "Wniebowzięcie N. M. P."); "-" for an empty name, matching the
+// week column's own convention for "absent".
+func dumpName(s string) string {
+ if s == "" {
+ return "-"
+ }
+ return strings.ReplaceAll(s, " ", "_")
+}
+
+// dumpLine renders one LiturgicalDay as a colitur-format line, plus the
+// name_en/name_pl columns colitur's own line does not carry (see this
+// package's doc comment). Week is lectio's own int (0 where no season week
+// applies, e.g. named I class feasts and per annum green-season ferias);
+// printed as "-" there so the field count stays fixed, matching colitur's
+// own convention for an absent week.
+func dumpLine(day calendar.LiturgicalDay) string {
+ week := "-"
+ if day.Week != 0 {
+ week = strconv.Itoa(day.Week)
+ }
+ var b strings.Builder
+ fmt.Fprintf(&b, "%s %s %s %s %s %s %s %s %s",
+ day.Date.Format("2006-01-02"),
+ strings.ToLower(day.Weekday.String()),
+ string(day.Season),
+ week,
+ day.Observed.Slug,
+ string(day.Observed.Rank),
+ string(day.Colour),
+ dumpName(day.Observed.Name["en"]),
+ dumpName(day.Observed.Name["pl"]),
+ )
+ for _, o := range day.Others {
+ fmt.Fprintf(&b, " +%s", o.Slug)
+ }
+ b.WriteByte('\n')
+ return b.String()
+}
diff --git a/go.mod b/go.mod
index e2ba908..6c82929 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/lukaszkasprzak/lectio
-go 1.24.0
+go 1.25.0
require (
github.com/charmbracelet/bubbletea v1.3.10
@@ -24,6 +24,12 @@ require (
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
- golang.org/x/sys v0.36.0 // indirect
+ golang.org/x/mobile v0.0.0-20260730202154-c700fe717e6e // indirect
+ golang.org/x/mod v0.38.0 // indirect
+ golang.org/x/sync v0.22.0 // indirect
+ golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.3.8 // indirect
+ golang.org/x/tools v0.48.0 // indirect
)
+
+tool golang.org/x/mobile/cmd/gobind
diff --git a/go.sum b/go.sum
index 8aa8bc9..e121589 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,8 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
+github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
@@ -10,12 +13,15 @@ github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
+github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -30,16 +36,36 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
+github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
+golang.org/x/exp/shiny v0.0.0-20260611194520-c48552f49976/go.mod h1:UXxgIHNj0uSRIk9ua62CRJcUpfHN9V+cRZRXHEo4GhE=
+golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
+golang.org/x/mobile v0.0.0-20260730202154-c700fe717e6e h1:v5PGTbI7uer7FhpDeGryGytfbI8t7CDWkKukozvIsNU=
+golang.org/x/mobile v0.0.0-20260730202154-c700fe717e6e/go.mod h1:YX+n47s+53POxN3dx9cIGxG3hGUm/lD64hvrRJFbcSA=
+golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
+golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
+golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
+golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
+golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
-golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
+golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/telemetry v0.0.0-20260708182218-49f421fb7959/go.mod h1:LV7u5Oco+Z/g6XI7PqN+EUUUGGkEcmB1uj2ceI0fOVg=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
+golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
+golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
+golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
+golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
+golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
diff --git a/internal/caldata/caldata_test.go b/internal/caldata/caldata_test.go
index 12aa765..7dbbf84 100644
--- a/internal/caldata/caldata_test.go
+++ b/internal/caldata/caldata_test.go
@@ -74,9 +74,13 @@ func TestTridentineLoads(t *testing.T) {
t.Fatalf("EF sanctoral has only %d entries; expected the full calendar", len(l.Cels))
}
// Landmark feasts must be present with the right rank/class: Sts Peter & Paul
- // (I class, 06-29), the Purification/Presentation (a II class feast of the
- // Lord, 02-02, which must displace a Sunday), the Immaculate Conception
- // (I class, 12-08).
+ // (I class, 06-29), the Purification/Presentation (II class, 02-02, class
+ // = lord -- see classOf's own doc comment in scripts/gen-sanctoral-ef.go
+ // for why: the calendarium's own title names the BLESSED VIRGIN, but the
+ // tag exists to drive OCCURRENCE behaviour, and missalemeum -- this
+ // data's own oracle -- shows the Purification taking a II-class Sunday's
+ // place outright, the "festum Domini" pattern, not the ordinary-BVM
+ // pattern), the Immaculate Conception (I class, 12-08).
want := map[string]struct{ date, rank, class string }{
"sts-peter-paul": {"06-29", "class-1", ""},
"purification-of-the-blessed-virgin-mary": {"02-02", "class-2", "lord"},
@@ -91,7 +95,7 @@ func TestTridentineLoads(t *testing.T) {
if rc.Fields["date"] != w.date || rc.Fields["rank"] != w.rank {
t.Errorf("%s: date/rank = %q/%q, want %q/%q", slug, rc.Fields["date"], rc.Fields["rank"], w.date, w.rank)
}
- if w.class != "" && rc.Fields["class"] != w.class {
+ if rc.Fields["class"] != w.class {
t.Errorf("%s: class = %q, want %q", slug, rc.Fields["class"], w.class)
}
}
@@ -103,3 +107,298 @@ func TestTridentineLoads(t *testing.T) {
}
}
}
+
+// TestTridentineClassOfLord: scripts/gen-sanctoral-ef.go's classOf tags
+// feasts of the Lord (class = lord). "Most Holy Name of Mary" was wrongly
+// tagged lord (a title-substring false positive: "holy name" alone matches
+// both "Holy Name of Jesus" and "Most Holy Name of MARY", calendarium
+// "Sanctissimi Nominis Mariae") and is fixed here, not contested. The
+// Baptism commemoration was missing the tag entirely (13 January's title
+// ends "of THE Lord", not "of OUR Lord", the only suffix classOf used to
+// check -- calendarium "IN COMMEMORATIONE BAPTISMATIS D. N. I. C."), also
+// fixed and not contested. The Purification is DELIBERATELY absent from
+// this table: it stays tagged lord, on occurrence-behaviour evidence, not
+// the calendarium's title -- see TestTridentineLoads and classOf's own doc
+// comment in scripts/gen-sanctoral-ef.go for the full account of why it is
+// the one contested case, not a clean-cut fix.
+func TestTridentineClassOfLord(t *testing.T) {
+ l := Tridentine()
+ want := map[string]string{
+ "most-holy-name-of-mary": "", // BVM, not the Lord
+ "commemoration-of-the-baptism-of-the-lord": "lord",
+ }
+ for slug, wantClass := range want {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing landmark feast %q", slug)
+ continue
+ }
+ if got := rc.Fields["class"]; got != wantClass {
+ t.Errorf("%s: class = %q, want %q", slug, got, wantClass)
+ }
+ }
+}
+
+// TestTridentineLentRankNotCommemoration: 15 III-class feasts, every one
+// falling 6 March - 5 April, were tagged rank = commemoration because all
+// six of the generator's reference years happen to place that fixed date
+// within Lent, where a privileged Lenten feria always outranks an
+// equal-class feast (RG 109(e)) -- the generator mistook "how the entry
+// presented on the sampled days" for the entry's true, intrinsic rank. A
+// sample of the 15 (full list in the report); the calendarium gives each one
+// III class outright, e.g. 5 April "S. Vincentii Ferrerii Conf., III
+// classis."
+func TestTridentineLentRankNotCommemoration(t *testing.T) {
+ l := Tridentine()
+ for _, slug := range []string{
+ "sts-felicitas-perpetua", "thomas-aquinas", "frances-rome",
+ "gregory-the-great", "patrick", "benedict", "francis-of-paola",
+ "isidore-of-seville", "vincent-ferrer",
+ } {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing %q", slug)
+ continue
+ }
+ if got := rc.Fields["rank"]; got != "class-3" {
+ t.Errorf("%s: rank = %q, want class-3 (not commemoration)", slug, got)
+ }
+ }
+}
+
+// TestTridentineGenuineCommemorationStaysCommemoration: the fix above must
+// NOT promote every commemoration-only entry indiscriminately -- some really
+// are without an independent Mass in the 1960-reformed books, demoted even
+// on an ORDINARY, unprivileged day (not explained by Lent/Passiontide or any
+// higher-class temporal day). St Blaise (3 Feb, an ordinary Septuagesima-
+// season feria) is live-confirmed by missalemeum as a mere commemoration
+// even then, so RankCommemoration is the honest, correct rank -- promoting
+// him to class-4 would let him wrongly win an ordinary green-season feria he
+// has no independent Mass to celebrate.
+func TestTridentineGenuineCommemorationStaysCommemoration(t *testing.T) {
+ l := Tridentine()
+ rc, ok := l.Cels["blaise"]
+ if !ok {
+ t.Fatal("missing blaise")
+ }
+ if got := rc.Fields["rank"]; got != "commemoration" {
+ t.Errorf("blaise: rank = %q, want commemoration (genuinely no independent Mass)", got)
+ }
+}
+
+// TestTridentineNoMissingEntries: four entries present in missalemeum (and
+// so in the calendarium) were silently dropped by the generator's own
+// dedup, which keyed a single global map by SLUG alone -- two by a literal
+// slug collision with an unrelated feast of the same English title on a
+// different date (28 January's "St. Agnes", the traditional SECOND
+// commemoration of 21 January's own Agnes; 14 May's "St. Boniface", a
+// different martyr from 5 June's Boniface of Mainz), and two (Evaristus,
+// Theodore) by the generator returning on the first reference year that
+// showed an observed office, before ever reaching the later year that
+// revealed their commemoration.
+func TestTridentineNoMissingEntries(t *testing.T) {
+ l := Tridentine()
+ want := map[string]string{
+ "agnes-secundo": "01-28",
+ "boniface-martyr": "05-14",
+ "evaristus": "10-26",
+ "theodore": "11-09",
+ }
+ for slug, date := range want {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing %q (%s)", slug, date)
+ continue
+ }
+ if got := rc.Fields["date"]; got != date {
+ t.Errorf("%s: date = %q, want %q", slug, got, date)
+ }
+ }
+}
+
+// TestTridentineRomanusAndEusebiusPresent: CORRECTED after review found the
+// opposite claim resting on an incomplete primary-source check. An earlier
+// version of this test (and of scripts/gen-sanctoral-ef.go's
+// knownSpuriousComm) asserted Romanus ABSENT, on the strength of ONE of the
+// three local Missal scans -- "1962-06-23,…LT.pdf", an ELECTRONIC
+// TRANSCRIPTION that silently drops vigil commemorations generally (also
+// missing: 7 August Donatus, 25 December Anastasia, both present elsewhere).
+// The other two, PHOTOGRAPHIC scans of the actual 1962 Missale Romanum, both
+// carry it: "missale-romanum-1962.pdf" calendarium, 9 August row: "XVI d V
+// 9 Vigilia, III classis, Commemoratio S. Romani Mart.", with the saint's
+// own proper text elsewhere in the same scan ("Et fit commemoratio S. Romani
+// Mar-"), and its own back-of-book index ("Romani Mart., 9 augusti ... 621").
+// Where the scans and the transcription disagree, the scans win -- see
+// gen-sanctoral-ef.go's own primary-source note, same rule recorded there so
+// it is not lost a second time.
+//
+// 14 August's "St. Eusebius" is the identical shape (calendarium: "XI b XIX
+// 14 Vigilia, II classis, Commemoratio S. Eusebii Conf.") and a DIFFERENT
+// person from 16 December's "St. Eusebius, Ep. et Mart." (calendarium: "V
+// XVII S. Eusebii Ep. et Mart., III classis.") -- a Confessor and a Bishop
+// and Martyr, not the same saint moved or duplicated. missalemeum gives both
+// the same bare English title, so they collide by slug; slugOverride
+// disambiguates rather than either being dropped.
+func TestTridentineRomanusAndEusebiusPresent(t *testing.T) {
+ l := Tridentine()
+ if rc, ok := l.Cels["romanus"]; !ok {
+ t.Error("romanus missing: the calendarium's photographic scans both carry \"Commemoratio S. Romani Mart.\" on 9 August")
+ } else if rc.Fields["date"] != "08-09" {
+ t.Errorf("romanus: date = %q, want 08-09", rc.Fields["date"])
+ }
+ want := map[string]struct{ date, rank string }{
+ // St Eusebius Confessor (14 Aug) and St Romanus (9 Aug, checked
+ // above) are both bare "Commemoratio" in the calendarium (no class
+ // of their own) -- pinned here, not just presence/date, per
+ // TestTridentineCommemorationRanksPinned's own reasoning below:
+ // romanus's own rank is pinned there instead, since that test
+ // groups every commemoration-rank entry this task's fixes touched
+ // in one place. eusebius-confessor is listed here because it is
+ // the OTHER half of this specific test's own slug-disambiguation
+ // story.
+ "eusebius-confessor": {"08-14", "commemoration"}, // "S. Eusebii Conf." -- a Confessor
+ "eusebius": {"12-16", "class-3"}, // "S. Eusebii Ep. et Mart." -- a Bishop and Martyr, a different person, unaffected by this round
+ }
+ for slug, w := range want {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing %q (%s)", slug, w.date)
+ continue
+ }
+ if rc.Fields["date"] != w.date {
+ t.Errorf("%s: date = %q, want %q", slug, rc.Fields["date"], w.date)
+ }
+ if rc.Fields["rank"] != w.rank {
+ t.Errorf("%s: rank = %q, want %q", slug, rc.Fields["rank"], w.rank)
+ }
+ }
+}
+
+// TestTridentineCommemorationRanksPinned: nothing in this test suite
+// asserted `rank` for thomas-becket, silvester, or romanus before this --
+// `TestTridentineRomanusAndEusebiusPresent` above pinned presence and date
+// for romanus but not rank, and Thomas Becket/Silvester (the I3 fix, RG
+// 68(d)/(e)) had no rank assertion anywhere at all. So a future
+// regeneration could silently rewrite any of the three back to class-4 --
+// the EXACT I3 failure mode -- with the suite green throughout. Pinned
+// together because they are the same failure shape (a bare "Commemoratio"
+// in the calendarium, no class of its own, silently promoted by
+// refYearExplainsAbsence's own coupling to temporal_ef.go, see that
+// function's doc comment) even though they were found in two different
+// review rounds (romanus in C2, Thomas Becket/Silvester in I3).
+func TestTridentineCommemorationRanksPinned(t *testing.T) {
+ l := Tridentine()
+ for _, slug := range []string{"romanus", "thomas-becket", "silvester"} {
+ rc, ok := l.Cels[slug]
+ if !ok {
+ t.Errorf("missing %q", slug)
+ continue
+ }
+ if rc.Fields["rank"] != "commemoration" {
+ t.Errorf("%s: rank = %q, want commemoration", slug, rc.Fields["rank"])
+ }
+ }
+}
+
+// TestTridentineNoTransferArtifacts: a movable-transfer feast displayed on
+// whatever civil date it actually landed on in a given reference year (St
+// Joseph pushed to 20 March by a Sunday of Lent; the Annunciation deferred
+// past Holy Week; All Souls moved to the Monday; St Matthias shown on the
+// 25th in a leap year) must not leak into the sanctoral as a phantom
+// fixed-date entry keyed to that transferred civil date.
+func TestTridentineNoTransferArtifacts(t *testing.T) {
+ l := Tridentine()
+ for _, slug := range []string{
+ "joseph-spouse-of-the-bl-virgin-mary-0320",
+ "annunciation-of-the-blessed-virgin-mary-0405",
+ "annunciation-of-the-blessed-virgin-mary-0408",
+ "annunciation-of-the-blessed-virgin-mary-0409",
+ "commemoration-of-all-souls-1103",
+ "matthias-0225",
+ } {
+ if _, ok := l.Cels[slug]; ok {
+ t.Errorf("phantom transfer-artifact entry %q present", slug)
+ }
+ }
+}
+
+// TestTridentineNamesPreservedAcrossRegeneration is the coverage guard a
+// regeneration silently destroying a whole language's names needed and did
+// not have: an earlier version of scripts/gen-sanctoral-ef.go's main()
+// preserved existing name.la values across a regeneration (missalemeum
+// supplies English only) but had no equivalent for name.pl -- and, because
+// the bootstrapped file has in fact never carried a name.la value, that
+// mechanism looked correct while doing nothing at all. A regeneration
+// deleted all 322 Polish names outright (name.pl count 322 -> 0), silently:
+// no test here asserted anything about a name.* field, and
+// `len(l.Cels) < 250` (TestTridentineLoads) does not notice a field going
+// missing within entries that still exist. `naming.CelebrationName`'s own
+// name[lang] -> name.en fallback (internal/naming/naming.go) then quietly
+// substituted English for Polish on every EF display in that language,
+// reaching mobile.Day(date, "ef", version, "pl") -- a shipped dlectio entry
+// point -- with no error anywhere in the chain.
+//
+// CORRECTED after review: the first version of this test counted name.pl
+// only (a hardcoded single language -- the exact "whitelist of two
+// languages" mistake C1's own fix was written to stop repeating, just
+// moved into the test), and its coverage floor (>= 315) had 7 entries of
+// slack -- the review proved dropping 6 entries' Polish names still passed
+// it. Fixed on both axes: the language set is DISCOVERED from the data
+// (every "name.<lang>" key actually present, not a hardcoded list, so a
+// regeneration dropping name.la or introducing a future name.de is
+// checked the same way as name.pl), and the floor for each discovered
+// language is its EXACT true count, not a loose approximation -- verified
+// directly against the branch point (`git show 2386a45:...`) once, by
+// hand, and hardcoded as the answer, not derived at test time from data
+// that could itself be wrong.
+//
+// True counts, independently verified: name.en on every entry (327, all
+// regenerated fresh from missalemeum, including the 5 entries this task's
+// own fixes added); name.pl on exactly 322 (preserved from the branch
+// point; the 5 new entries -- St Agnes secundo, St Boniface Martyr, St
+// Evaristus, St Theodore, St Eusebius Confessor -- never had a curated
+// Polish name to preserve in the first place, so 322, not 327, is the
+// correct target, not a shortfall); no OTHER name.<lang> exists in the
+// branch point at all (name.la is read by the generator but the
+// bootstrapped file has in fact never carried one), so the discovered
+// language set itself must be exactly {en, pl} -- a regeneration that
+// silently introduced or lost an entire language key, not just some
+// values within one, is caught by this assertion, not only by the count.
+func TestTridentineNamesPreservedAcrossRegeneration(t *testing.T) {
+ l := Tridentine()
+ counts := map[string]int{}
+ for _, rc := range l.Cels {
+ for k, v := range rc.Fields {
+ if v == "" || !strings.HasPrefix(k, "name.") {
+ continue
+ }
+ lang := strings.TrimPrefix(k, "name.")
+ counts[lang]++
+ }
+ }
+ wantLangs := map[string]bool{"en": true, "pl": true}
+ for lang := range counts {
+ if !wantLangs[lang] {
+ t.Errorf("unexpected name.%s present (%d entries) -- discovered language set must be exactly {en, pl}", lang, counts[lang])
+ }
+ }
+ for lang := range wantLangs {
+ if counts[lang] == 0 {
+ t.Errorf("name.%s entirely absent -- discovered language set must be exactly {en, pl}", lang)
+ }
+ }
+ if got := counts["en"]; got != len(l.Cels) {
+ t.Errorf("name.en coverage = %d entries, want %d (every entry, all freshly regenerated)", got, len(l.Cels))
+ }
+ if got := counts["pl"]; got != 322 {
+ t.Errorf("name.pl coverage = %d entries, want exactly 322 (the branch point's own true count; the 5 entries this task added have no curated Polish name to preserve, so more or fewer than 322 is a bug either way)", got)
+ }
+
+ rc, ok := l.Cels["assumption-of-the-blessed-virgin-mary"]
+ if !ok {
+ t.Fatal("missing assumption-of-the-blessed-virgin-mary")
+ }
+ if got := rc.Fields["name.pl"]; got != "Wniebowzięcie N. M. P." {
+ t.Errorf("assumption-of-the-blessed-virgin-mary: name.pl = %q, want the preserved Polish name", got)
+ }
+}
diff --git a/internal/caldata/tridentine-calendar.ini b/internal/caldata/tridentine-calendar.ini
index 3fb61d7..55564d7 100644
--- a/internal/caldata/tridentine-calendar.ini
+++ b/internal/caldata/tridentine-calendar.ini
@@ -4,7 +4,9 @@
; by internal/calendar (temporalEF) and is NOT listed here.
; Ranks use the 1960 Code of Rubrics: class-1..class-4.
; Generated by scripts/gen-sanctoral-ef.go from missalemeum (Divinum Officium 1962
-; data). Latin names are hand-curated where present. See NOTICE.
+; data). name.en is always regenerated fresh from missalemeum; every other
+; name.<lang> (missalemeum supplies English only) is preserved verbatim from
+; whatever this file already carried before regeneration. See NOTICE.
[layer]
id = tridentine
@@ -29,6 +31,7 @@ name.pl = św. Hygina, Papieża i Męczennika
date = 01-13
rank = class-2
colour = white
+class = lord
name.en = Commemoration of the Baptism of the Lord
name.pl = Wspomnienie Chrztu Pańskiego
reading.first = Isa 60:1-6
@@ -191,6 +194,12 @@ name.pl = św. Jana Chryzostoma, Wyznawcy, Biskupa i Doktora Kościoła
reading.first = 2 Tim 4:1-8
reading.gospel = Matt 5:13-19
+[agnes-secundo]
+date = 01-28
+rank = commemoration
+colour = red
+name.en = St. Agnes
+
[peter-nolasco]
date = 01-28
rank = class-3
@@ -430,56 +439,56 @@ name.pl = św. Lucjusza I, Papieża i Męczennika
[sts-felicitas-perpetua]
date = 03-06
-rank = commemoration
+rank = class-3
colour = red
name.en = Sts. Felicitas & Perpetua
name.pl = śś. Perpetui i Felicyty, Męczennic
[thomas-aquinas]
date = 03-07
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Thomas Aquinas
name.pl = św. Tomasza z Akwinu, Wyznawcy i Doktora Kościoła
[john-of-god]
date = 03-08
-rank = commemoration
+rank = class-3
colour = white
name.en = St. John of God
name.pl = św. Jana Bożego, Wyznawcy
[frances-rome]
date = 03-09
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Frances Rome
name.pl = św. Franciszki Rzymianki, Wdowy
[forty-holy-martyrs-of-sebaste]
date = 03-10
-rank = commemoration
+rank = class-3
colour = red
name.en = Forty Holy Martyrs of Sebaste
name.pl = śś. Czterdziestu Męczenników
[gregory-the-great]
date = 03-12
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Gregory the Great
name.pl = św. Grzegorza Wielkiego, Papieża, Wyznawcy i Doktora Kościoła
[patrick]
date = 03-17
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Patrick
name.pl = św. Patryka, Biskupa i Wyznawcy
[cyril-of-jerusalem]
date = 03-18
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Cyril of Jerusalem
name.pl = św. Cyryla Jerozolimskiego, Biskupa, Wyznawcy i Doktora Kościoła
@@ -495,14 +504,14 @@ reading.gospel = Matt 1:18-21
[benedict]
date = 03-21
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Benedict
name.pl = św. Benedykta, Opata
[gabriel-the-archangel]
date = 03-24
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Gabriel the Archangel
name.pl = św. Gabriela Archanioła
@@ -518,35 +527,35 @@ reading.gospel = Luke 1:26-38
[john-damascene]
date = 03-27
-rank = commemoration
+rank = class-3
colour = white
name.en = St. John Damascene
name.pl = św. Jana Damasceńskiego, Wyznawcy i Doktora Kościoła
[john-of-capistrano]
date = 03-28
-rank = commemoration
+rank = class-3
colour = white
name.en = St. John of Capistrano
name.pl = św. Jana Kapistrana, Wyznawcy
[francis-of-paola]
date = 04-02
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Francis of Paola
name.pl = św. Franciszka z Pauli, Wyznawcy
[isidore-of-seville]
date = 04-04
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Isidore of Seville
name.pl = św. Izydora, Biskupa, Wyznawcy i Doktora Kościoła
[vincent-ferrer]
date = 04-05
-rank = commemoration
+rank = class-3
colour = white
name.en = St. Vincent Ferrer
name.pl = św. Wincentego Fereriusza, Wyznawcy
@@ -784,6 +793,12 @@ name.pl = św. Roberta Bellarmina, Biskupa, Wyznawcy i Doktora Kościoła
reading.first = Wis 7:7-14.
reading.gospel = Matt 5:13-19
+[boniface-martyr]
+date = 05-14
+rank = commemoration
+colour = red
+name.en = St. Boniface
+
[john-baptist-de-la-salle]
date = 05-15
rank = class-3
@@ -1596,6 +1611,12 @@ colour = red
name.en = Sts. Hippolytus & Cassian
name.pl = śś. Hipolita i Kasjana, Męczenników
+[eusebius-confessor]
+date = 08-14
+rank = commemoration
+colour = red
+name.en = St. Eusebius
+
[vigil-of-the-assumption]
date = 08-14
rank = class-2
@@ -1866,7 +1887,6 @@ name.pl = św. Prota i Jacka, Męczenników
date = 09-12
rank = class-3
colour = white
-class = lord
name.en = Most Holy Name of Mary
name.pl = Najświętszego Imienia Maryi
reading.first = Sir 24:23-31
@@ -2264,6 +2284,12 @@ colour = red
name.en = Sts. Chrysanthus & Daria
name.pl = śś. Chryzanta i Darii, Męczenników
+[evaristus]
+date = 10-26
+rank = commemoration
+colour = red
+name.en = St. Evaristus
+
[sts-simon-jude]
date = 10-28
rank = class-2
@@ -2324,6 +2350,12 @@ name.pl = Rocznica Konsekracji Bazyliki Najświętszego Zbawiciela na Lateranie
reading.first = Rev 21:2-5
reading.gospel = Luke 19:1-10
+[theodore]
+date = 11-09
+rank = commemoration
+colour = red
+name.en = St. Theodore
+
[andrew-avellino]
date = 11-10
rank = class-3
diff --git a/internal/calendar/calendar.go b/internal/calendar/calendar.go
index 33706d8..2d8611a 100644
--- a/internal/calendar/calendar.go
+++ b/internal/calendar/calendar.go
@@ -35,14 +35,43 @@ func TemporalSlug(date time.Time, sel Selection) string {
func computeEF(date time.Time, sel Selection, layers []Layer) LiturgicalDay {
td := temporalEF(date)
merged := mergeLayers(layers)
- cands := []candidate{{Cel: td.Cel, Temporal: true, Season: td.Season}}
+ year := date.Year()
+ // occupiedByRank reports whether some OTHER fixed-date sanctoral
+ // celebration whose rank passes `allowed` resolves onto d this year.
+ // transferIfImpededEF uses this at two different thresholds: class 1
+ // only, to decide whether a candidate is impeded in the first place (a
+ // class-2 occupant never impedes a class-1 feast -- class 1 always beats
+ // class 2 outright, no tie exists); and class 1 OR 2, for RG 96's "next
+ // day that is not I or II class" once a transfer is already under way
+ // (e.g. the Visitation, 2 July, blocking the Precious Blood's transfer
+ // off 1 July in 2011).
+ occupiedByRank := func(d time.Time, exceptSlug string, allowed func(Rank) bool) bool {
+ for slug2, rc2 := range merged {
+ if slug2 == exceptSlug {
+ continue
+ }
+ cel2 := buildCelebration(slug2, rc2)
+ if !allowed(cel2.Rank) {
+ continue
+ }
+ if when2, ok := celebrationDate(cel2, year, sel); ok && sameDay(when2, d) {
+ return true
+ }
+ }
+ return false
+ }
+ isClass1 := func(r Rank) bool { return r == RankClass1 }
+ isClass1Or2 := func(r Rank) bool { return r == RankClass1 || r == RankClass2 }
+ cands := []candidate{{Cel: td.Cel, Temporal: true, Season: td.Season, Sunday: td.Sunday}}
for slug, rc := range merged {
cel := buildCelebration(slug, rc)
- when, ok := celebrationDate(cel, date.Year(), sel)
+ when, ok := celebrationDate(cel, year, sel)
if !ok {
continue
}
- effective := transferIfImpededEF(cel, when)
+ effective := transferIfImpededEF(cel, when,
+ func(d time.Time) bool { return occupiedByRank(d, cel.Slug, isClass1) },
+ func(d time.Time) bool { return occupiedByRank(d, cel.Slug, isClass1Or2) })
if sameDay(effective, date) {
cands = append(cands, candidate{Cel: cel, Temporal: false, Season: td.Season})
}
@@ -199,12 +228,54 @@ func transferIfImpeded(cel Celebration, when time.Time, sel Selection) time.Time
// 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 {
+// transferred to the Monday (Nov 3); and a I class feast genuinely impeded is
+// pushed forward to the next day that is itself neither I nor II class (RG
+// 96: "the next following day that is not I or II class"). Lower-rank feasts
+// are only commemorated, never transferred (RG 95).
+//
+// occupiedByClass1 and occupiedByClass1Or2 are deliberately different
+// thresholds, not the same check reused twice:
+// - a candidate is impeded IN THE FIRST PLACE only by a genuine class-1
+// collision -- the temporal office of `when` is itself I class (a Sunday
+// of Advent/Lent/Passiontide, Holy Week, a named I-class feast), or
+// another FIXED I-class sanctoral feast already sits on `when` (RG
+// 97/98). A II-class occupant, temporal or sanctoral, never impedes a
+// I-class feast at all -- I class always outranks II class outright, no
+// tie-break is even reached -- so using the wider I-OR-II threshold here
+// would (and, before this fix, did: e.g. All Saints, 1 Nov, was wrongly
+// bumped to 3 Nov merely for landing on an ordinary II-class Sunday it
+// would have won outright) send an unimpeded I-class feast on an
+// unnecessary walk.
+// - once a transfer is under way, the DESTINATION must avoid landing on
+// ANY I- or II-class day (RG 96's own wider "not I or II class" text) --
+// e.g. the Visitation, 2 July, blocks the Precious Blood's transfer off 1
+// July in 2011, and 3 July's ordinary Sunday blocks it a second time.
+//
+// KNOWN GAP, not fixed here (recorded per RG 95, so the next person does not
+// have to rediscover it from scratch): this function resolves ONE
+// candidate's own transfer walk in isolation. It has no way to notice that a
+// SECOND, separately-transferred I-class candidate has landed on the exact
+// same destination day. Concretely: St Joseph (19 March) and the
+// Annunciation (25 March) can both be walked, independently, to the Monday
+// after Low Sunday in the same year (2008, 2035, 2046 -- e.g. 2035-04-02:
+// `annunciation-of-the-blessed-virgin-mary … +joseph-spouse-of-the-bl-virgin-mary`).
+// RG 95 grants the right of translation "solummodo festis I classis" (to
+// I-class feasts ONLY) -- so BOTH have that right, and the day they land on
+// together is itself an ordinary occurrence collision RG 97/98 governs (the
+// higher table position is kept, the other transfers FURTHER): this
+// function does not re-walk the loser, it lets pickEF's plain alphabetical
+// slug tie-break settle it, so the loser is merely commemorated instead of
+// continuing its own walk one more day. RG 98 itself supplies the missing
+// determinism rule for the tie this collision even needs: "in paritate
+// autem Officium prius impeditum praecedit" -- at equal table position, the
+// office impeded FIRST takes precedence -- which is chronological (Joseph,
+// impeded on the 19th, before the Annunciation's own walk begins on the
+// 25th) and may favour Joseph over pickEF's alphabetical fallback. Neither
+// RG 98's tie rule nor the re-walk RG 97 implies is implemented; fixing this
+// properly means resolving occurrence between two ALREADY-TRANSFERRED
+// candidates, not a single one, which is out of this function's current
+// shape.
+func transferIfImpededEF(cel Celebration, when time.Time, occupiedByClass1, occupiedByClass1Or2 func(time.Time) bool) 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)
@@ -212,11 +283,40 @@ func transferIfImpededEF(cel Celebration, when time.Time) time.Time {
if cel.Rank != RankClass1 {
return when
}
- day := when
+ // Is cel actually impeded on `when` at all? Compare its OWN precedence
+ // (via precedenceEF, the same function pickEF uses to decide the day)
+ // against the temporal office's, rather than testing the temporal
+ // office's class in isolation -- a plain "is the temporal day I class"
+ // test wrongly impedes a candidate that would in fact WIN a tie against
+ // it: RG 91 entry 4 (the Immaculate Conception, the Assumption) sits
+ // ABOVE entry 6 (Sundays), so the Immaculate Conception is not impeded
+ // by the Advent Sunday it may fall on at all, unlike an ordinary I-class
+ // feast (entry 11, e.g. St Joseph), which is. occupiedByClass1 still
+ // covers the separate case of a competing FIXED I-class SANCTORAL
+ // feast, which this temporal-only comparison cannot see.
+ startTemporal := temporalEF(when)
+ tCand := candidate{Cel: startTemporal.Cel, Temporal: true, Season: startTemporal.Season, Sunday: startTemporal.Sunday}
+ sCand := candidate{Cel: cel, Temporal: false}
+ startImpeded := precedenceEF(tCand) < precedenceEF(sCand) || occupiedByClass1(when)
+ if !startImpeded {
+ return when
+ }
+ day := when.AddDate(0, 0, 1)
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
+ // A direct CLASS test, not precedenceEF's tie-break band: band
+ // encodes which of two EQUAL-class candidates wins a tie (e.g. a
+ // Sunday beats an equal-class feast, but a privileged Ember/late-
+ // Advent FERIA of the same class yields to one, defect 2) -- an
+ // unrelated question from RG 96's own "is this day itself I or II
+ // class" test. Reusing band here undercounted: a day within the
+ // Octave of the Nativity (26-31 Dec) is genuinely II class (RG 67),
+ // but as an ordinary, non-Sunday II-class temporal candidate its OWN
+ // band yields (5, not <=3) -- so band<=3 alone let an impeded I-class
+ // feast wrongly land there. isHighClass tests the class directly.
+ isHighClass := b.Cel.Rank == RankClass1 || b.Cel.Rank == RankClass2
+ if isHighClass || occupiedByClass1Or2(day) {
+ day = day.AddDate(0, 0, 1) // still I- or II-class; keep walking
continue
}
return day
diff --git a/internal/calendar/oracle_ef_test.go b/internal/calendar/oracle_ef_test.go
index ce4badd..6d63220 100644
--- a/internal/calendar/oracle_ef_test.go
+++ b/internal/calendar/oracle_ef_test.go
@@ -12,11 +12,51 @@ import (
"github.com/lukaszkasprzak/lectio/internal/calendar"
)
+// efOracleDay is one day of missalemeum's per-date proper API, as captured in
+// sources/snapshot.tar.gz (missalemeum/en/YYYY-MM-DD.json, 2026-01-01 ..
+// 2027-12-31, 730 days) and flattened by scripts/build-oracle-ef.sh.
+//
+// Three things about this data that will otherwise cost hours (see that
+// script's own header for the full account):
+//
+// 1. ID looks like "sancti:MM-DD:rank:colour", but its embedded rank is the
+// rank of the PROPERS USED that day, not the day's own rank -- e.g.
+// 2026-01-02 is a class-4 feria carrying id "sancti:01-01:1:w" because it
+// reuses the Circumcision's propers. Kept here for provenance/debugging
+// ONLY. Rank and Colour comparisons below use Rank/Colours, never ID.
+// 2. Colours is an array -- 14 of 730 days carry two values (Gaudete/Laetare
+// "pv", Palm Sunday "rv", Good Friday "bv", Holy Saturday "vw") -- so the
+// comparison is membership, not equality.
+// 3. A two-colour value on a weekday can be an artifact of proper reuse (a
+// feria inside Gaudete/Laetare week reusing the Sunday's own propers)
+// rather than a claim about that weekday's own colour. No special-casing
+// is needed for this: lectio's single ferial colour (violet) is already a
+// member of the reused set (e.g. ["p","v"]), so a plain membership check
+// absorbs it.
type efOracleDay struct {
- Tempora string `json:"tempora"`
- Title string `json:"title"`
- Rank int `json:"rank"`
- Colour string `json:"colour"`
+ ID string `json:"id"`
+ Tempora string `json:"tempora"`
+ Title string `json:"title"`
+ Rank int `json:"rank"`
+ Colours []string `json:"colours"`
+}
+
+// efColourLetters maps missalemeum's single-letter colour codes to lectio's
+// Colour vocabulary (scripts/gen-sanctoral-ef.go's colourWord table, same
+// mapping, kept in sync deliberately rather than imported across a
+// package/build-tag boundary).
+var efColourLetters = map[string]calendar.Colour{
+ "w": calendar.White, "r": calendar.Red, "g": calendar.Green,
+ "v": calendar.Violet, "b": calendar.Black, "p": calendar.Rose,
+}
+
+// efRankNumbers maps missalemeum's 1-4 rank number to lectio's EF Rank
+// vocabulary. missalemeum has no separate "commemoration" rank number --
+// RankCommemoration is a lectio-side concept for a losing sanctoral
+// candidate, never the oracle's claim about the OBSERVED day.
+var efRankNumbers = map[int]calendar.Rank{
+ 1: calendar.RankClass1, 2: calendar.RankClass2,
+ 3: calendar.RankClass3, 4: calendar.RankClass4,
}
// efSeasonFromString maps a missalemeum tempora/title phrase (for a day in the
@@ -28,6 +68,14 @@ type efOracleDay struct {
// there, not to Time after Epiphany.
func efSeasonFromString(s string, month int) calendar.Season {
s = strings.ToLower(s)
+ // missalemeum's own EN-locale text still carries the occasional Latin
+ // ligature (e.g. "Feria V after Sexagesimæ") -- confirmed in the
+ // committed snapshot: "Sexagesimæ" appears 10 times, "Sexagesima" (no
+ // ligature) only 4, and the ligatured form matched no case below,
+ // silently mapping those 10 days to season "" (skipped from coverage
+ // entirely, not merely a season miss). Normalising here fixes every
+ // current and future ligature in one place, not just this one word.
+ s = strings.ReplaceAll(s, "æ", "ae")
has := func(subs ...string) bool {
for _, sub := range subs {
if strings.Contains(s, sub) {
@@ -63,9 +111,116 @@ func efSeasonFromString(s string, month int) calendar.Season {
}
}
-// TestOracleEF diffs the EF engine against missalemeum (Divinum Officium data).
-// Season is asserted strictly (validates temporalEF); rank/colour are reported
-// informationally (partial 1962 sanctoral).
+// efAllow is one cited allow-list entry: a genuine, defensible divergence
+// between lectio's EF engine and the missalemeum oracle on Rank and/or
+// Colour, kept out of the pass/fail count deliberately rather than silently
+// skipped. Every entry names the date(s) it covers, which field(s) it
+// excuses, and why -- with an RG citation where the divergence is a rubric
+// question, or a plain "not built" note where it is a documented scope gap
+// (see internal/calendar/precedence_ef.go and temporal_ef.go's own doc
+// comments, and colitur's rules-register.md, for the fuller accounts).
+type efAllow struct {
+ date string // "" = pattern-matched by match(), see below
+ match func(date string, day time.Time, oracle efOracleDay) bool
+ field string // "rank", "colour", or "rank+colour"
+ reason string
+}
+
+var efAllowList = []efAllow{
+ {
+ // RG 91 entry 27, "Officium sanctae Mariae in sabbato": the votive
+ // BVM Office on an otherwise-unoccupied IV-class Saturday. Not built
+ // in lectio (temporal_ef.go constructs only a bare ferial slug on
+ // these Saturdays; the vocabulary comment in colitur's own register
+ // documents the same gap on its side). missalemeum's rank/colour for
+ // these Saturdays reflects the BVM Office (usually white); lectio's
+ // reflects the plain per-annum feria (green, class-4). A real,
+ // scoped, pre-existing gap -- not one of the seven defects, and not
+ // attempted here (a Plan-scale feature, per the sibling project's own
+ // parking of the identical item).
+ match: func(date string, day time.Time, o efOracleDay) bool {
+ return day.Weekday() == time.Saturday &&
+ strings.Contains(strings.ToLower(o.Title), "b. v. m")
+ },
+ field: "rank+colour",
+ reason: "RG 91 entry 27 (BVM Office on Saturday) is not built in lectio; both engines agree only that the day is unimpeded",
+ },
+ {
+ // RG 72-73 place 6-13 January within Christmastide itself ("a I
+ // Vesperis Nativitatis Domini usque ad diem 13 ianuarii inclusive");
+ // RG 119(a) backs the same boundary from the white-colour side
+ // ("...usque ad expletum tempus Epiphaniae"). lectio's efSeason
+ // (temporal_ef.go) instead starts "time after Epiphany" on 6 January,
+ // so a plain feria in this window gets green/TimeAfterEpiphany
+ // instead of white/Christmastide. This is a real, PRE-EXISTING season-
+ // boundary divergence -- colitur's own rules-register.md documents
+ // the identical gap on its side (§3c item 1) as deliberate and
+ // resolved in the OTHER engine's favour, not one of the seven named
+ // defects, and not touched here: efSeason is a season-boundary
+ // function with much wider reach than any of the seven, and changing
+ // it is out of this task's scope.
+ match: func(date string, day time.Time, o efOracleDay) bool {
+ return day.Month() == time.January && day.Day() >= 6 && day.Day() <= 13
+ },
+ field: "colour",
+ reason: "RG 72-73/RG 119(a): 6-13 Jan is Christmastide, not lectio's time-after-Epiphany reading (efSeason, not one of the seven defects; see colitur rules-register.md §3c item 1)",
+ },
+ {
+ // RG 33: "Vigilia II aut III classis penitus omittitur, si occurrat in
+ // dominica quavis..." -- a II/III-class vigil is ENTIRELY OMITTED on
+ // any Sunday. 9 August 2026 is a Sunday; the Vigil of St Lawrence (III
+ // class) must vanish and the Sunday itself be observed -- which is
+ // exactly what lectio does (see cmd/lectio-ef-dump: 2026-08-09
+ // observed is the II-class Sunday, the vigil demoted to a losing
+ // candidate). missalemeum's OWN data shows the vigil still winning
+ // that Sunday, an RG 33 gap on missalemeum's side, not lectio's --
+ // already identified and adjudicated in the primary source's favour
+ // by the sibling project (rules-register.md §6, "the romanus-vs-
+ // vigil-of-st-lawrence 9 August Sunday tie-break", M1).
+ date: "2026-08-09",
+ field: "rank+colour",
+ reason: "RG 33: a II/III-class vigil is omitted on any Sunday; missalemeum itself shows an RG 33 gap here (colitur rules-register.md §6, M1) -- lectio's Sunday-observed answer is the Missal-correct one",
+ },
+ {
+ // OPEN, not adjudicated by any primary text found so far. RG 91's
+ // plain table gives St Joseph (19 March, I class) the day outright
+ // over an ordinary III-class Friday of Passion Week -- no RG 96
+ // collision requiring a transfer -- yet missalemeum shows Joseph
+ // entirely displaced in 2027 specifically (the Friday's own office
+ // observed, the Seven Sorrows commemorated instead). The sibling
+ // project's much deeper primary-source pass over the SAME question
+ // left it explicitly open (colitur rules-register.md §6, M13: "three
+ // possibilities, none confirmed") rather than guess at an unfound
+ // rubric. Not attempted here either, for the same reason -- adjudicating
+ // it would mean finding a citation that a more thorough primary-source
+ // audit did not.
+ date: "2027-03-19",
+ field: "rank+colour",
+ reason: "unresolved even by primary-source review (colitur rules-register.md §6, M13); not adjudicated here either way",
+ },
+}
+
+func efAllowed(date string, day time.Time, oracle efOracleDay, field string) (bool, string) {
+ for _, a := range efAllowList {
+ if a.date != "" && a.date != date {
+ continue
+ }
+ if a.match != nil && !a.match(date, day, oracle) {
+ continue
+ }
+ if a.field == field || a.field == "rank+colour" {
+ return true, a.reason
+ }
+ }
+ return false, ""
+}
+
+// TestOracleEF diffs the EF engine against missalemeum (Divinum Officium data)
+// over 730 days (2026-01-01 .. 2027-12-31). Season, Rank and Colour are all
+// asserted strictly (membership for Colour, since missalemeum's colour is an
+// array); a prior version of this test asserted Season only, which is exactly
+// why several precedence/colour defects (see precedence_ef.go, temporal_ef.go)
+// shipped without ever failing a test.
func TestOracleEF(t *testing.T) {
raw, err := os.ReadFile("testdata/oracle-ef.json")
if err != nil {
@@ -85,8 +240,8 @@ func TestOracleEF(t *testing.T) {
}
sort.Strings(dates)
- var seasonMiss, skipped, total int
- shown := 0
+ var seasonMiss, rankMiss, colourMiss, rankAllowed, colourAllowed, skipped, total int
+ shownSeason, shownRank, shownColour := 0, 0, 0
for _, date := range dates {
od := oracle[date]
src := od.Tempora
@@ -95,22 +250,92 @@ func TestOracleEF(t *testing.T) {
}
day, _ := time.Parse("2006-01-02", date)
want := efSeasonFromString(src, int(day.Month()))
- if want == "" {
- skipped++
- continue
- }
total++
got := calendar.Compute(day.UTC(), sel, layers)
- if got.Season != want {
+
+ // An unrecognised season phrase skips the SEASON comparison only --
+ // it must not also skip Rank/Colour for that day. A `continue` here
+ // once did exactly that, silently zeroing oracle coverage for every
+ // day whose phrase efSeasonFromString doesn't recognise (Holy
+ // Thursday -- "maundy"/"holy week" don't match "Holy Thursday"
+ // itself; the six September Ember days -- no "ember" case exists at
+ // all, only Advent/Lent Ember days pass by an incidental substring
+ // match on "advent"/"lent"). Reverting the Holy Thursday colour fix
+ // (temporal_ef.go) left this test green under the old `continue`;
+ // it does not under this one.
+ if want == "" {
+ skipped++
+ } else if got.Season != want {
seasonMiss++
- if shown < 25 {
+ if shownSeason < 40 {
t.Errorf("%s: EF season got %q want %q (from %q)", date, got.Season, want, src)
- shown++
+ shownSeason++
+ }
+ }
+
+ wantRank, rankKnown := efRankNumbers[od.Rank]
+ if rankKnown && got.Observed.Rank != wantRank {
+ if ok, reason := efAllowed(date, day, od, "rank"); ok {
+ rankAllowed++
+ t.Logf("[allow-listed] %s: EF rank got %q want %q — %s", date, got.Observed.Rank, wantRank, reason)
+ } else {
+ rankMiss++
+ if shownRank < 40 {
+ t.Errorf("%s: EF rank got %q want %q (oracle rank %d, title %q)",
+ date, got.Observed.Rank, wantRank, od.Rank, od.Title)
+ shownRank++
+ }
+ }
+ }
+
+ colourOK := false
+ for _, c := range od.Colours {
+ if efColourLetters[c] == got.Colour {
+ colourOK = true
+ break
+ }
+ }
+ // Membership alone cannot catch a Rose regression: violet is a
+ // member of every rose/violet pair by construction (Gaudete/
+ // Laetare), so "got violet, want one of [rose violet]" passes even
+ // if RG 131's Rose support (temporal_ef.go) were reverted entirely.
+ // On the day the rose/violet pair actually names -- oracle rank 1,
+ // the Sunday itself, not a weekday reusing its propers (which
+ // carries a lower rank and is deliberately membership-only, this
+ // file's own doc comment item 3) -- a colour set containing rose
+ // demands rose specifically, not merely "some member".
+ if colourOK && od.Rank == 1 && got.Colour != calendar.Rose {
+ for _, c := range od.Colours {
+ if c == "p" {
+ colourOK = false
+ break
+ }
+ }
+ }
+ if !colourOK && len(od.Colours) > 0 {
+ if ok, reason := efAllowed(date, day, od, "colour"); ok {
+ colourAllowed++
+ t.Logf("[allow-listed] %s: EF colour got %q want one of %v — %s", date, got.Colour, od.Colours, reason)
+ } else {
+ colourMiss++
+ if shownColour < 40 {
+ t.Errorf("%s: EF colour got %q want one of %v (title %q)",
+ date, got.Colour, od.Colours, od.Title)
+ shownColour++
+ }
}
}
}
- t.Logf("EF oracle: %d checked, %d skipped(unmapped), %d season mismatches", total, skipped, seasonMiss)
+ t.Logf("EF oracle: %d checked, %d skipped(unmapped), %d season mismatches, "+
+ "%d rank mismatches (%d allow-listed), %d colour mismatches (%d allow-listed)",
+ total, skipped, seasonMiss, rankMiss, rankAllowed, colourMiss, colourAllowed)
if seasonMiss > 0 {
t.Fatalf("%d/%d EF season mismatches vs missalemeum — temporalEF is wrong", seasonMiss, total)
}
+ if rankMiss > 0 {
+ t.Fatalf("%d/%d EF rank mismatches vs missalemeum (not allow-listed)", rankMiss, total)
+ }
+ if colourMiss > 0 {
+ t.Fatalf("%d/%d EF colour mismatches vs missalemeum (not allow-listed)", colourMiss, total)
+ }
}
diff --git a/internal/calendar/precedence_ef.go b/internal/calendar/precedence_ef.go
index 4fda626..9f5016f 100644
--- a/internal/calendar/precedence_ef.go
+++ b/internal/calendar/precedence_ef.go
@@ -1,6 +1,9 @@
package calendar
-import "sort"
+import (
+ "sort"
+ "strings"
+)
// efRankOrder orders EF (1960) ranks: class-1 highest, then class-2..4,
// commemoration, ferial lowest.
@@ -22,34 +25,89 @@ func efRankOrder(r Rank) int {
}
// precedenceEF ranks an EF candidate for occurrence (lower = higher precedence):
-// by class first, then a tie-break at equal class (1960 occurrence table).
+// by class first, then a tie-break at equal class (RG 91's Table of
+// Precedence, read entry-by-entry within each class).
//
-// The tie-break is asymmetric by season. An ORDINARY feria (per annum, Advent,
-// Septuagesima) yields to an equal-class feast — the feast is celebrated and the
-// feria commemorated (e.g. St Francis Xavier, Dec 3, on an Advent feria). The
-// ferias of Lent and Passiontide are privileged: they outrank an equal-class
-// feast, which is only commemorated. Sundays and named temporal feasts likewise
-// win their ties. The *2 class spacing means the ±1 tie-break never crosses a
-// class boundary.
+// The tie-break is asymmetric by season AND, at class 2, by whether the
+// temporal day is a Sunday or a privileged FERIA:
+// - III/IV class: an ORDINARY feria (per annum, Advent to 16 Dec,
+// Septuagesima) yields to an equal-class feast (entries 24/25 above the
+// feria) -- the feast is celebrated, the feria commemorated (e.g. St
+// Francis Xavier, Dec 3). The ferias of Lent and Passiontide are
+// privileged (entry 22, ABOVE entry 24): they outrank an equal-class
+// feast, which is only commemorated.
+// - II class: an ordinary SUNDAY wins its tie (entry 15, above entry 16's
+// feasts) -- but a II-class privileged FERIA (the Ember days, the
+// late-Advent 17-23 Dec ferias, entry 18) sits BELOW entry 16 and yields
+// to an equal-class feast, the opposite of a Sunday's own tie. This is
+// the same table shape as III class's Advent/per-annum ferias, just
+// inverted for which II-class temporal days count as "privileged".
+// - I class: Sundays (entry 6) and I-class ferias (entry 7, Ash
+// Wednesday/Holy Week) both sit above entry 11's feasts, so they always
+// win -- there is no I-class "ordinary feria yields" case at all.
+//
+// The *2 class spacing means the ±1 tie-break never crosses a class boundary.
func precedenceEF(c candidate) int {
p := (6 - efRankOrder(c.Cel.Rank)) * 2 // class-1 -> 2, ferial -> 12
if c.Temporal {
- ordinaryFeria := (c.Cel.Rank == RankClass3 || c.Cel.Rank == RankClass4) &&
- c.Season != Lent && c.Season != Passiontide
- if ordinaryFeria {
- p++ // an ordinary feria yields to an equal-class feast
+ yieldsToFeast := false
+ switch c.Cel.Rank {
+ case RankClass3, RankClass4:
+ yieldsToFeast = c.Season != Lent && c.Season != Passiontide
+ case RankClass2:
+ // A II-class Sunday (entry 15) wins; a II-class privileged feria --
+ // Ember days, the late-Advent 17-23 Dec ferias (entry 18) -- yields
+ // to an equal-class feast (entry 16), e.g. St Matthew (21 Sep)
+ // beating the September Ember Wednesday, or St Thomas (21 Dec)
+ // beating an Advent late feria.
+ yieldsToFeast = !c.Sunday
+ }
+ if yieldsToFeast {
+ p++
} else {
- p-- // Sundays, named feasts, and penitential ferias win their ties
+ p-- // Sundays, I-class ferias, and penitential (Lent/Passiontide) ferias win their ties
}
} else if c.Cel.Class == ClassLord && c.Cel.Rank == RankClass2 {
// A II class feast of the Lord takes the place of a II class Sunday it
// falls on (unlike a saint's feast, which is only commemorated). Give it
// the edge over the Sunday's tie-break bonus.
p -= 2
+ } else if c.Cel.Rank == RankClass1 && beatsClass1Sunday(c.Cel.Slug) {
+ // RG 91 entries 4 (Immaculate Conception, Assumption BVM) and 5
+ // (Vigil & Octave day of the Nativity) both sit ABOVE entry 6
+ // (Sundays of Advent/Lent/Passiontide, Low Sunday) -- unlike an
+ // ORDINARY I-class feast (entry 11, e.g. St Joseph, the Precious
+ // Blood), which yields to a I-class Sunday, these win the tie.
+ // Found while verifying defect 4 (Sunday ranks): before that fix,
+ // Advent/Lent Sundays were wrongly II class, so these feasts beat
+ // them outright on base class alone, accidentally right; once
+ // Sundays became I class the tie-break mattered, and without this
+ // branch the Sunday would wrongly win. Two live cases in the fixed
+ // calendar: the Immaculate Conception (8 December) on an Advent
+ // Sunday, and the Vigil of Christmas (24 December) on Advent IV --
+ // the latter is not merely a wrong winner but a WORSE bug without
+ // this branch: transferIfImpededEF has no way to re-place a
+ // transfer that crosses the Dec31/Jan1 boundary (celebrationDate
+ // re-resolves a fixed date using the YEAR OF THE DAY BEING QUERIED,
+ // so a walk landing in January of the following year can never
+ // match the query that produced it), so the Vigil simply vanished
+ // for the year instead of landing on the wrong day. The Assumption
+ // (15 August) never falls in Advent or Lent, so its own share of
+ // this branch is citation-complete but not live.
+ p -= 2
}
return p
}
+// beatsClass1Sunday reports whether slug is one of RG 91's entries 4 or 5 --
+// the Immaculate Conception, the Assumption, or the Vigil of Christmas --
+// all of which sit above entry 6's Sundays in the Table of Precedence.
+func beatsClass1Sunday(slug string) bool {
+ return strings.Contains(slug, "immaculate-conception") ||
+ strings.Contains(slug, "assumption-of-the-blessed-virgin-mary") ||
+ slug == "vigil-of-christmas"
+}
+
// pickEF returns the observed EF celebration and the commemorations, ordered
// deterministically by precedence then slug.
func pickEF(cands []candidate) (candidate, []candidate) {
diff --git a/internal/calendar/precedence_ef_repro_test.go b/internal/calendar/precedence_ef_repro_test.go
new file mode 100644
index 0000000..11fd6ca
--- /dev/null
+++ b/internal/calendar/precedence_ef_repro_test.go
@@ -0,0 +1,160 @@
+package calendar_test
+
+// Reproduction tests for three named EF precedence defects (colitur's
+// differential/oracle audit against lectio's ~66-line EF precedence
+// approximation, internal/calendar/precedence_ef.go). Each test fails before
+// its fix and passes after -- see the report for the exact pre-fix failure
+// message. These exercise the REAL tridentine sanctoral data
+// (caldata.Tridentine()) through the public Compute entry point, since the
+// bugs are about how real fixed-date feasts interact with the temporal
+// calendar, not about precedenceEF's arithmetic in isolation (that is
+// covered separately, package-internal, in precedence_ef_test.go).
+
+import (
+ "testing"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/caldata"
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+func efCompute(date string) calendar.LiturgicalDay {
+ sel := calendar.DefaultSelection()
+ sel.Form = "old"
+ d, _ := time.Parse("2006-01-02", date)
+ return calendar.Compute(d.UTC(), sel, []calendar.Layer{caldata.Tridentine()})
+}
+
+// TestJosephYieldsToSundayOfLent: RG 91 entry 6 (Sundays of Lent, I class)
+// vs entry 11 (I-class feasts of the universal Church not above) -- the
+// Sunday holds; St Joseph (19 March) transfers to the next free day (RG 95,
+// RG 96), landing on the 20th. Before the fix (defect 4: Sunday ranks), Lent
+// Sundays were wrongly II class, so Joseph (I class) won outright every time
+// this collision occurred.
+func TestJosephYieldsToSundayOfLent(t *testing.T) {
+ for _, year := range []string{"2006", "2017", "2023", "2028", "2034", "2045"} {
+ sunday := efCompute(year + "-03-19")
+ if sunday.Observed.Slug == "joseph-spouse-of-the-bl-virgin-mary" {
+ t.Errorf("%s-03-19 observed = %q want the Lent Sunday (RG 91 entry 6 beats entry 11)", year, sunday.Observed.Slug)
+ }
+ next := efCompute(year + "-03-20")
+ if next.Observed.Slug != "joseph-spouse-of-the-bl-virgin-mary" {
+ t.Errorf("%s-03-20 observed = %q want joseph-spouse-of-the-bl-virgin-mary (transferred one day, RG 96)", year, next.Observed.Slug)
+ }
+ }
+}
+
+// TestTransferSkipsBothIAndIIClass: RG 96 -- an impeded I-class feast
+// transfers to the next day that is NOT ITSELF I OR II CLASS (not merely "not
+// I class"). 2011: the Sacred Heart (Friday after the Corpus Christi octave)
+// falls on 1 July and impedes the Precious Blood (also 1 July, fixed). 2
+// July is the Visitation (II class, fixed); 3 July is an ordinary II-class
+// Sunday. Both must be skipped; the Precious Blood lands on 4 July, and the
+// Visitation is observed, undisplaced, on its own day.
+func TestTransferSkipsBothIAndIIClass(t *testing.T) {
+ if got := efCompute("2011-07-01").Observed.Slug; got != "ef-sacred-heart" {
+ t.Fatalf("2011-07-01 observed = %q want ef-sacred-heart", got)
+ }
+ if got := efCompute("2011-07-02").Observed.Slug; got != "visitation-of-the-blessed-virgin-mary" {
+ t.Errorf("2011-07-02 observed = %q want visitation-of-the-blessed-virgin-mary (RG 96: the Precious Blood must skip past it, not displace it)", got)
+ }
+ if got := efCompute("2011-07-03").Observed.Slug; got != "ef-time-after-pentecost-sunday-3" {
+ t.Errorf("2011-07-03 observed = %q want ef-time-after-pentecost-sunday-3 (an ordinary II-class Sunday also blocks a I-class transfer)", got)
+ }
+ if got := efCompute("2011-07-04").Observed.Slug; got != "precious-blood-of-our-lord-jesus-christ" {
+ t.Errorf("2011-07-04 observed = %q want precious-blood-of-our-lord-jesus-christ (RG 96: first day that is neither I nor II class)", got)
+ }
+}
+
+// TestMatthewBeatsSeptemberEmberWednesday: RG 91 -- St Matthew (21 September,
+// II class) and the September Ember Wednesday are both II class; the table
+// decides the tie. Entry 16 (II-class feasts of the universal Church) sits
+// ABOVE entry 18 (II-class ferias, including the Ember days) -- the feast
+// wins, the Ember feria is only commemorated.
+func TestMatthewBeatsSeptemberEmberWednesday(t *testing.T) {
+ // 21 September falls on the September Ember Wednesday whenever the third
+ // Sunday of September is the 18th -- 2016 and 2022 both qualify.
+ for _, date := range []string{"2016-09-21", "2022-09-21"} {
+ day := efCompute(date)
+ if day.Observed.Slug != "matthew" {
+ t.Errorf("%s observed = %q want matthew (RG 91 entry 16 beats entry 18)", date, day.Observed.Slug)
+ }
+ }
+}
+
+// TestImmaculateConceptionBeatsAdventSunday: found, not named among the
+// seven, while verifying defect 4 (Sunday ranks) against the real sanctoral
+// data. RG 91 entry 4 (Immaculate Conception, Assumption BVM) sits ABOVE
+// entry 6 (Sundays of Advent/Lent/Passiontide) -- unlike an ORDINARY
+// I-class feast (entry 11, e.g. St Joseph), the Immaculate Conception
+// (8 December) is not impeded by the Advent Sunday it falls on at all. Before
+// defect 4's own fix, Advent Sundays were wrongly II class, so this was
+// accidentally right (I class beats II class outright); once Sundays became
+// I class the tie mattered for the first time.
+func TestImmaculateConceptionBeatsAdventSunday(t *testing.T) {
+ for _, year := range []string{"2013", "2019", "2024"} {
+ day := efCompute(year + "-12-08")
+ if day.Observed.Slug != "immaculate-conception-of-the-blessed-virgin-mary" {
+ t.Errorf("%s-12-08 observed = %q want immaculate-conception-of-the-blessed-virgin-mary (RG 91 entry 4 beats entry 6)", year, day.Observed.Slug)
+ }
+ }
+}
+
+// TestVigilOfChristmasSurvivesAdventSunday: found, not named among the
+// seven, while verifying defect 4 against the real sanctoral data -- and
+// worse than a wrong winner. RG 91 entry 5 (Vigil & Octave day of the
+// Nativity) also sits above entry 6, so the Vigil of Christmas (24 December)
+// is not impeded by falling on Advent IV either. Without this, defect 4
+// alone would have made the Advent Sunday win a genuine tie, sending the
+// Vigil into transferIfImpededEF's forward walk -- which has no way to
+// re-place a transfer that crosses the Dec31/Jan1 boundary (celebrationDate
+// re-resolves a fixed date using the year of whatever day is being queried,
+// so a walk landing in the following January can never match the query that
+// produced it). The Vigil did not move to the wrong day; it vanished for the
+// whole year.
+func TestVigilOfChristmasSurvivesAdventSunday(t *testing.T) {
+ for _, year := range []string{"2006", "2017", "2023", "2028"} {
+ day := efCompute(year + "-12-24")
+ if day.Observed.Slug != "vigil-of-christmas" {
+ t.Errorf("%s-12-24 observed = %q want vigil-of-christmas (RG 91 entry 5 beats entry 6; must not vanish)", year, day.Observed.Slug)
+ }
+ }
+}
+
+// TestPurificationBeatsFebruarySunday: the fixture behind classOf's own
+// Purification decision (scripts/gen-sanctoral-ef.go), committed here rather
+// than left as an assertion only, because the deciding years (2 February on
+// a Sunday: 2014, 2020, 2025, 2031, 2042, 2048) fall entirely outside this
+// repo's own missalemeum oracle snapshot (sources/snapshot.tar.gz,
+// 2026-01-01 .. 2027-12-31), so nothing else in this repository can
+// reproduce the evidence classOf's decision rests on. (2036, sometimes
+// quoted alongside 2025/2031 as a third example: 2 February 2036 is in fact
+// a SATURDAY, `date -d 2036-02-02 +%A` -- checked here rather than repeated;
+// 2042 is the correct next instance after 2031.)
+//
+// RG 91 entry 14 ("Festa Domini II classis") sits above entry 15
+// ("Dominicae II classis"), which sits above entry 16 ("Festa II classis
+// Ecclesiae universae, quae non [sunt Domini]") -- entry 14 takes an
+// occurring Sunday's place outright, entry 16 is merely commemorated on
+// one. The Purification (2 February) matches entry 14's pattern, not entry
+// 16's, independently fetched live from missalemeum for five different
+// Sundays: 2014-02-02, 2020-02-02, 2025-02-02, 2031-02-02, 2042-02-02 (all
+// "id":"sancti:02-02:2:w", "commemorations":[]). Control:
+// nativity-of-the-blessed-virgin-mary (8 September, an undisputed ordinary
+// Marian feast, entry 16's own pattern) on a Sunday -- 2019-09-08 -- shows
+// the SUNDAY observed, the feast commemorated instead: the opposite shape,
+// proving the Purification's own treatment is a deliberate pattern in the
+// oracle, not a gap. This is a decision AGAINST the calendarium's own title
+// ("IN PURIFICATIONE B. MARIAE VIRG.") and RG 120(b) (which files 2
+// February under the white-colour rule's "B. Mariae Virg." heading, kept
+// separate from 120(a)'s "Domini" heading) -- both point BVM; the
+// occurrence-behaviour evidence here points Domini. See classOf's own doc
+// comment for the fuller account; not re-argued here.
+func TestPurificationBeatsFebruarySunday(t *testing.T) {
+ for _, year := range []string{"2014", "2020", "2025", "2031", "2042"} {
+ day := efCompute(year + "-02-02")
+ if day.Observed.Slug != "purification-of-the-blessed-virgin-mary" {
+ t.Errorf("%s-02-02 observed = %q want purification-of-the-blessed-virgin-mary (RG 91 entry 14 beats entry 15)", year, day.Observed.Slug)
+ }
+ }
+}
diff --git a/internal/calendar/precedence_ef_test.go b/internal/calendar/precedence_ef_test.go
index 818b452..d5e254f 100644
--- a/internal/calendar/precedence_ef_test.go
+++ b/internal/calendar/precedence_ef_test.go
@@ -6,6 +6,14 @@ func efCand(rank Rank, temporal bool) candidate {
return candidate{Cel: Celebration{Rank: rank}, Temporal: temporal}
}
+// efTemporalCand builds a temporal candidate with the season/Sunday flags
+// precedenceEF's tie-break actually reads, so a test claiming to exercise
+// "a Sunday" or "a Lenten feria" genuinely sets those fields rather than
+// happening to pass for an unrelated reason (e.g. a class difference alone).
+func efTemporalCand(rank Rank, season Season, sunday bool) candidate {
+ return candidate{Cel: Celebration{Rank: rank}, Temporal: true, Season: season, Sunday: sunday}
+}
+
func TestPrecedenceEF(t *testing.T) {
c1 := efCand(RankClass1, false)
c3 := efCand(RankClass3, false)
@@ -14,16 +22,57 @@ func TestPrecedenceEF(t *testing.T) {
if !(precedenceEF(c1) < precedenceEF(c3) && precedenceEF(c3) < precedenceEF(c4) && precedenceEF(c4) < precedenceEF(comm)) {
t.Error("EF class ordering broken (class-1 > class-3 > class-4 > commemoration)")
}
- // at equal class, the temporal office wins.
- temp := efCand(RankClass2, true)
+ // a I-class feast beats a II-class Sunday (RG 91 entry 11 vs entry 15 --
+ // different classes, so this holds regardless of the tie-break, but the
+ // candidate is still marked Sunday so the test means what its name says).
+ sunday2 := efTemporalCand(RankClass2, TimeAfterPentecost, true)
+ feast1 := efCand(RankClass1, false)
+ obs, others := pickEF([]candidate{sunday2, feast1})
+ if obs.Cel.Rank != RankClass1 || len(others) != 1 {
+ t.Errorf("I-class feast should win over II-class Sunday, got %+v", obs.Cel)
+ }
+}
+
+// TestPrecedenceEFClass2SundayVsFeria: RG 91 entries 15/16/18 -- at class 2,
+// a SUNDAY wins its tie against an equal-class feast (entry 15 above 16), but
+// a privileged FERIA (the Ember days, the late-Advent 17-23 Dec ferias, entry
+// 18) YIELDS to one (entry 16 above 18) -- the opposite of the Sunday case.
+// This is defect 2's own witness: before the fix, precedenceEF treated every
+// non-ordinaryFeria temporal candidate the same way, so an Ember/late-Advent
+// feria wrongly won its tie exactly like a Sunday does.
+func TestPrecedenceEFClass2SundayVsFeria(t *testing.T) {
saint := efCand(RankClass2, false)
- obs, _ := pickEF([]candidate{saint, temp})
- if !obs.Temporal {
- t.Error("equal-class: temporal office should be observed")
+
+ sunday := efTemporalCand(RankClass2, TimeAfterPentecost, true)
+ obsSunday, _ := pickEF([]candidate{saint, sunday})
+ if !obsSunday.Temporal {
+ t.Errorf("a II-class Sunday should win its tie against an equal-class feast, got %+v observed", obsSunday.Cel)
}
- // a I-class feast beats a II-class Sunday.
- obs2, others := pickEF([]candidate{efCand(RankClass2, true), efCand(RankClass1, false)})
- if obs2.Cel.Rank != RankClass1 || len(others) != 1 {
- t.Errorf("I-class feast should win over II-class Sunday, got %+v", obs2.Cel)
+
+ emberFeria := efTemporalCand(RankClass2, TimeAfterPentecost, false) // e.g. the September Ember Wednesday
+ obsFeria, _ := pickEF([]candidate{saint, emberFeria})
+ if obsFeria.Temporal {
+ t.Errorf("a II-class privileged feria should yield its tie to an equal-class feast, got %+v observed (temporal)", obsFeria.Cel)
+ }
+}
+
+// TestPrecedenceEFClass3FeriaSeason: the pre-existing rule (unchanged by
+// defect 2's fix), stated explicitly rather than left implicit: an ORDINARY
+// III/IV-class feria (per annum, Advent to 16 Dec, Septuagesima) yields to an
+// equal-class feast; the ferias of Lent and Passiontide are privileged and
+// win instead.
+func TestPrecedenceEFClass3FeriaSeason(t *testing.T) {
+ saint := efCand(RankClass3, false)
+
+ ordinary := efTemporalCand(RankClass3, Advent, false)
+ obsOrdinary, _ := pickEF([]candidate{saint, ordinary})
+ if obsOrdinary.Temporal {
+ t.Errorf("an ordinary Advent feria should yield to an equal-class feast, got %+v observed (temporal)", obsOrdinary.Cel)
+ }
+
+ lenten := efTemporalCand(RankClass3, Lent, false)
+ obsLenten, _ := pickEF([]candidate{saint, lenten})
+ if !obsLenten.Temporal {
+ t.Errorf("a Lenten feria should win its tie against an equal-class feast, got %+v observed", obsLenten.Cel)
}
}
diff --git a/internal/calendar/temporal_ef.go b/internal/calendar/temporal_ef.go
index 7420ebd..c649405 100644
--- a/internal/calendar/temporal_ef.go
+++ b/internal/calendar/temporal_ef.go
@@ -74,6 +74,16 @@ func efCel(season Season, slug string, col Colour, rank Rank, week int) temporal
}
}
+// efSunday is efCel plus the Sunday flag, for the temporal candidates that
+// are actual Sundays -- precedenceEF's tie-break needs to tell a Sunday (RG
+// 91 entry 15, wins its tie) apart from an equal-class privileged FERIA
+// (entry 18, the Ember/late-Advent days, which yields).
+func efSunday(season Season, slug string, col Colour, rank Rank, week int) temporalDay {
+ day := efCel(season, slug, col, rank, week)
+ day.Sunday = true
+ return day
+}
+
// 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 {
@@ -87,6 +97,15 @@ func temporalEF(date time.Time) temporalDay {
if !date.Before(easter.AddDate(0, 0, 50)) && !date.After(easter.AddDate(0, 0, 55)) {
col = Red
}
+ // Holy Thursday (the Mass of Chrism and the Mass in Cena Domini) is white,
+ // a whole-Mass exception to Passiontide's violet -- RG 128(b)'s own named
+ // exception list, and RG 122 stating the same fact affirmatively in the
+ // White section. Good Friday and Holy Saturday, either side, are NOT
+ // exceptions here (RG 132's black for Good Friday's liturgical action is a
+ // separate, unmodelled gap -- see precedence_ef.go's doc comments).
+ if sameDay(date, easter.AddDate(0, 0, -3)) {
+ col = White
+ }
sun := date.Weekday() == time.Sunday
// Major feasts of the Lord (I class): nice titles + colour.
@@ -129,8 +148,20 @@ func temporalEF(date time.Time) temporalDay {
if sun {
week := efWeek(date, season, y, easter)
rank := RankClass2
- if season == Advent && sameDay(date, adventStart(y)) {
- rank = RankClass1 // 1st Sunday of Advent
+ sundayColour := col
+ if season == Advent || season == Lent {
+ // RG 11-12 / RG 91 entry 6: every Sunday of Advent and every Sunday
+ // of Lent is I class -- not only Advent I, the sole case previously
+ // handled (Passiontide's own two Sundays, Passion and Palm, are
+ // already I class via the named-feast switch above; Low Sunday
+ // likewise).
+ rank = RankClass1
+ switch {
+ case season == Advent && week == 3:
+ sundayColour = Rose // Gaudete -- RG 131, that Sunday's Office/Mass only
+ case season == Lent && week == 4:
+ sundayColour = Rose // Laetare -- RG 131, that Sunday's Office/Mass only
+ }
}
// Resumed Sundays after Pentecost (1960 Rubrics): when Easter is early
// there are more than 24 Sundays after Pentecost. The LAST Sunday before
@@ -140,17 +171,17 @@ func temporalEF(date time.Time) temporalDay {
if season == TimeAfterPentecost {
lastSun := adventStart(y).AddDate(0, 0, -7)
if sameDay(date, lastSun) {
- return efCel(TimeAfterPentecost, "ef-time-after-pentecost-sunday-24", col, rank, 24)
+ return efSunday(TimeAfterPentecost, "ef-time-after-pentecost-sunday-24", col, rank, 24)
}
if week > 23 {
p := daysBetween(easter.AddDate(0, 0, 49), lastSun) / 7 // total Sundays after Pentecost
e := week - p + 7 // resumed Sunday after Epiphany
// Season stays time-after-pentecost (calendrical); the Epiphany
// slug only routes the readings to the resumed Mass.
- return efCel(TimeAfterPentecost, "ef-time-after-epiphany-sunday-"+strconv.Itoa(e), Green, rank, e)
+ return efSunday(TimeAfterPentecost, "ef-time-after-epiphany-sunday-"+strconv.Itoa(e), Green, rank, e)
}
}
- return efCel(season, "ef-"+string(season)+"-sunday-"+strconv.Itoa(week), col, rank, week)
+ return efSunday(season, "ef-"+string(season)+"-sunday-"+strconv.Itoa(week), sundayColour, rank, week)
}
// The days between Ash Wednesday and the 1st Sunday of Lent have their own
// proper Masses (not part of a numbered Lenten week).
@@ -166,6 +197,17 @@ func temporalEF(date time.Time) temporalDay {
// saint (e.g. the Conversion of St Paul, Jan 25) displaces them.
rank = RankClass3
}
+ if season == Advent && date.Month() == time.December && date.Day() >= 17 && date.Day() <= 23 {
+ // RG 91 entry 18: the late-Advent ferias (17-23 Dec) are II class
+ // privileged ferias, not the ordinary III class of the rest of Advent.
+ rank = RankClass2
+ }
+ if season == Christmas && date.Month() == time.December && date.Day() >= 26 {
+ // RG 67/68: "Dies infra octavam sunt II classis" -- days within the
+ // Octave of the Nativity (26-31 Dec) are II class; the octave day
+ // itself (1 Jan) is I class, already handled above as a named feast.
+ rank = RankClass2
+ }
if efPrivilegedFeria(date, easter) {
// The days of Holy Week and the privileged octaves of Easter and
// Pentecost are I class; no saint's feast is admitted (the feast is
@@ -173,12 +215,11 @@ func temporalEF(date time.Time) temporalDay {
// the temporal office win, matching the 1962 occurrence rules.
rank = RankClass1
}
- // The Ember Days of September and Advent (Wednesday, Friday, Saturday after
- // the third Sunday of the month/season) are II class privileged ferias in
- // violet; they displace a III class saint (only commemorated). The Lenten
- // Ember days are already III class Lenten ferias, and the Whitsun Ember days
- // fall inside the I class Pentecost octave handled above.
- if es, ok := efEmberSlug(date, y); ok {
+ // The Ember Days of September, Advent and Lent (Wednesday, Friday, Saturday
+ // after the anchor Sunday) are II class privileged ferias in violet; they
+ // displace a III class saint (only commemorated) -- RG 91 entry 18. The
+ // Whitsun Ember days fall inside the I class Pentecost octave handled above.
+ if es, ok := efEmberSlug(date, y, easter); ok {
return efCel(season, es, Violet, RankClass2, week)
}
// Unique ferial slug (season-week-weekday) so proper ferias (Lent, Advent,
@@ -197,22 +238,26 @@ func thirdSundayOfSeptember(y int) time.Time {
}
// efEmberSlug returns the Ember-day slug for date, if it is an Ember Wednesday,
-// Friday or Saturday of September or Advent (the day after the third Sunday +
-// 3/5/6). Returns ("", false) otherwise.
-func efEmberSlug(date time.Time, y int) (string, bool) {
+// Friday or Saturday of September, Advent or Lent (the day after the anchor
+// Sunday + 3/5/6: the third Sunday of September, Advent III, or Lent I --
+// MR1962, "De anno et eius partibus", Quatuor Tempora). Returns ("", false)
+// otherwise. The Whitsun Ember days are not listed here: they fall inside the
+// I class Pentecost octave, handled separately by efPrivilegedFeria.
+func efEmberSlug(date time.Time, y int, easter time.Time) (string, bool) {
for _, e := range []struct {
- third time.Time
+ anchor time.Time
season string
}{
{thirdSundayOfSeptember(y), "september"},
{adventStart(y).AddDate(0, 0, 14), "advent"}, // 3rd Sunday of Advent
+ {easter.AddDate(0, 0, -42), "lent"}, // 1st Sunday of Lent
} {
switch {
- case sameDay(date, e.third.AddDate(0, 0, 3)):
+ case sameDay(date, e.anchor.AddDate(0, 0, 3)):
return "ef-" + e.season + "-ember-wed", true
- case sameDay(date, e.third.AddDate(0, 0, 5)):
+ case sameDay(date, e.anchor.AddDate(0, 0, 5)):
return "ef-" + e.season + "-ember-fri", true
- case sameDay(date, e.third.AddDate(0, 0, 6)):
+ case sameDay(date, e.anchor.AddDate(0, 0, 6)):
return "ef-" + e.season + "-ember-sat", true
}
}
diff --git a/internal/calendar/temporal_ef_test.go b/internal/calendar/temporal_ef_test.go
index 6d632e2..8b1ffa5 100644
--- a/internal/calendar/temporal_ef_test.go
+++ b/internal/calendar/temporal_ef_test.go
@@ -38,3 +38,95 @@ func TestTemporalEFChristTheKing(t *testing.T) {
t.Errorf("efChristTheKing(2025) = %s want 2025-10-26", got)
}
}
+
+// TestTemporalEFSundayRanks: RG 11-12 / RG 91 entry 6 -- Sundays of Advent,
+// Lent and Passiontide, and Low Sunday, are I class; all others (including
+// Septuagesima's own three Sundays and the ordinary Sundays after Epiphany/
+// Pentecost) stay II class. Before the fix, only Advent I was I class; every
+// other Advent/Lent Sunday was left at the generic-Sunday II-class default.
+func TestTemporalEFSundayRanks(t *testing.T) {
+ cases := []struct {
+ date string
+ want Rank
+ }{
+ {"2025-11-30", RankClass1}, // Advent I
+ {"2025-12-07", RankClass1}, // Advent II
+ {"2025-12-14", RankClass1}, // Advent III (Gaudete)
+ {"2025-12-21", RankClass1}, // Advent IV
+ {"2025-03-09", RankClass1}, // Lent I
+ {"2025-03-16", RankClass1}, // Lent II
+ {"2025-03-23", RankClass1}, // Lent III
+ {"2025-03-30", RankClass1}, // Lent IV (Laetare)
+ {"2025-04-06", RankClass1}, // Passion Sunday
+ {"2025-04-13", RankClass1}, // Palm Sunday
+ {"2025-04-27", RankClass1}, // Low Sunday
+ {"2025-02-16", RankClass2}, // Septuagesima Sunday -- NOT I class
+ {"2025-02-23", RankClass2}, // Sexagesima Sunday -- NOT I class
+ {"2025-03-02", RankClass2}, // Quinquagesima Sunday -- NOT I class
+ {"2025-06-22", RankClass2}, // II Sunday after Pentecost -- ordinary II class
+ }
+ for _, c := range cases {
+ got := temporalEF(d(c.date))
+ if got.Cel.Rank != c.want {
+ t.Errorf("temporalEF(%s).Cel.Rank = %s, want %s (%s)", c.date, got.Cel.Rank, c.want, got.Cel.Slug)
+ }
+ }
+}
+
+// TestTemporalEFRoseSundays: RG 131 -- rose is permitted on Gaudete (Advent
+// III) and Laetare (Lent IV), for that Sunday's own Office and Mass only.
+// Before the fix, efColour had no Rose case at all.
+func TestTemporalEFRoseSundays(t *testing.T) {
+ if got := temporalEF(d("2025-12-14")).Cel.Colour; got != Rose {
+ t.Errorf("Gaudete (2025-12-14) colour = %s, want rose", got)
+ }
+ if got := temporalEF(d("2025-03-30")).Cel.Colour; got != Rose {
+ t.Errorf("Laetare (2025-03-30) colour = %s, want rose", got)
+ }
+ // The Advent/Lent Sundays either side stay violet -- rose is an indult for
+ // that one Sunday, not a season colour.
+ if got := temporalEF(d("2025-12-07")).Cel.Colour; got != Violet {
+ t.Errorf("Advent II (2025-12-07) colour = %s, want violet", got)
+ }
+ if got := temporalEF(d("2025-03-23")).Cel.Colour; got != Violet {
+ t.Errorf("Lent III (2025-03-23) colour = %s, want violet", got)
+ }
+}
+
+// TestTemporalEFHolyThursdayColour: RG 128(b)/RG 122 -- Holy Thursday's Mass
+// of Chrism and Mass in Cena Domini are white, a whole-day exception to
+// Passiontide's violet. Good Friday and Holy Saturday, either side, stay
+// violet (their own black/no-colour exceptions are a separate, unmodelled
+// gap -- see RG 132, precedence_ef.go's own doc comments).
+func TestTemporalEFHolyThursdayColour(t *testing.T) {
+ easter := d("2025-04-20")
+ holyThu := easter.AddDate(0, 0, -3)
+ goodFri := easter.AddDate(0, 0, -2)
+ holySat := easter.AddDate(0, 0, -1)
+ if got := temporalEF(holyThu).Cel.Colour; got != White {
+ t.Errorf("Holy Thursday colour = %s, want white", got)
+ }
+ if got := temporalEF(goodFri).Cel.Colour; got != Violet {
+ t.Errorf("Good Friday colour = %s, want violet (unchanged)", got)
+ }
+ if got := temporalEF(holySat).Cel.Colour; got != Violet {
+ t.Errorf("Holy Saturday colour = %s, want violet (unchanged)", got)
+ }
+}
+
+// TestTemporalEFEmberDayRanks: RG 91 entry 18 -- the Ember days of Lent (and,
+// unchanged, September and Advent) are II-class privileged ferias, not the
+// ordinary III-class Lenten ferias around them. Before the fix, efEmberSlug
+// had no Lent case at all, so Lent's Ember Wed/Fri/Sat fell through to the
+// ordinary III-class Lenten-feria rank.
+func TestTemporalEFEmberDayRanks(t *testing.T) {
+ // Lent I Sunday 2025 = 2025-03-09; Ember Wed/Fri/Sat = +3/+5/+6.
+ lentI := d("2025-03-09")
+ for _, off := range []int{3, 5, 6} {
+ day := lentI.AddDate(0, 0, off)
+ got := temporalEF(day)
+ if got.Cel.Rank != RankClass2 {
+ t.Errorf("Lent Ember day %s rank = %s, want class-2 (%s)", day.Format("2006-01-02"), got.Cel.Rank, got.Cel.Slug)
+ }
+ }
+}
diff --git a/internal/calendar/testdata/oracle-ef.json b/internal/calendar/testdata/oracle-ef.json
index 3ddf10c..f857288 100644
--- a/internal/calendar/testdata/oracle-ef.json
+++ b/internal/calendar/testdata/oracle-ef.json
@@ -1,4382 +1,6586 @@
{
- "2025-01-01": {
+ "2026-01-01": {
+ "id": "sancti:01-01:1:w",
"tempora": "",
"title": "Octave Day of Christmas",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-06": {
+ "2026-01-02": {
+ "id": "sancti:01-01:1:w",
"tempora": "",
- "title": "Epiphany of the Lord",
- "rank": 1,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-01-10": {
+ "2026-01-03": {
+ "id": "commune:C10b:4:w",
"tempora": "",
- "title": "Feria",
+ "title": "II Mass of the B. V. M. – Vultum Tuum",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-07": {
+ "2026-01-04": {
+ "id": "tempora:Nat2-0:2:w",
+ "tempora": "",
+ "title": "Holy Name of Jesus",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-01-05": {
+ "id": "sancti:01-01:1:w",
"tempora": "",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-02": {
+ "2026-01-06": {
+ "id": "sancti:01-06:1:w",
+ "tempora": "",
+ "title": "Epiphany of the Lord",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-01-07": {
+ "id": "sancti:01-06:1:w",
"tempora": "",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-03": {
+ "2026-01-08": {
+ "id": "sancti:01-06:1:w",
"tempora": "",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-05": {
+ "2026-01-09": {
+ "id": "sancti:01-06:1:w",
"tempora": "",
- "title": "Holy Name of Jesus",
- "rank": 2,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-01-09": {
+ "2026-01-10": {
+ "id": "commune:C10b:4:w",
"tempora": "",
- "title": "Feria",
+ "title": "II Mass of the B. V. M. – Vultum Tuum",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-12": {
+ "2026-01-11": {
+ "id": "tempora:Epi1-0:2:w",
"tempora": "",
"title": "The Holy Family: Jesus, Mary & Joseph",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-20": {
- "tempora": "Feria II after II Sunday after Epiphany",
- "title": "Sts. Fabian & Sebastian",
- "rank": 3,
- "colour": "r"
- },
- "2025-01-16": {
- "tempora": "Feria V after Epiphany",
- "title": "St. Marcellus I",
- "rank": 3,
- "colour": "r"
- },
- "2025-01-04": {
- "tempora": "",
- "title": "II Mass of the B. V. M. – Vultum Tuum",
+ "2026-01-12": {
+ "id": "tempora:Epi1-0a:2:w",
+ "tempora": "Feria II after Epiphany",
+ "title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-13": {
- "tempora": "Feria II after Epiphany",
+ "2026-01-13": {
+ "id": "sancti:01-13:2:w",
+ "tempora": "Feria III after Epiphany",
"title": "Commemoration of the Baptism of the Lord",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-19": {
- "tempora": "",
- "title": "II Sunday after Epiphany",
- "rank": 2,
- "colour": "g"
+ "2026-01-14": {
+ "id": "sancti:01-14:3:w",
+ "tempora": "Feria IV after Epiphany",
+ "title": "St. Hilary",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-01-08": {
- "tempora": "",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
+ "2026-01-15": {
+ "id": "sancti:01-15:3:w",
+ "tempora": "Feria V after Epiphany",
+ "title": "St. Paul, the First Hermit",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-01-17": {
+ "2026-01-16": {
+ "id": "sancti:01-16:3:r",
"tempora": "Feria VI after Epiphany",
+ "title": "St. Marcellus I",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-01-17": {
+ "id": "sancti:01-17:3:w",
+ "tempora": "Saturday after Epiphany",
"title": "St. Anthony",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-26": {
+ "2026-01-18": {
+ "id": "tempora:Epi2-0:2:g",
"tempora": "",
- "title": "III Sunday after Epiphany",
+ "title": "II Sunday after Epiphany",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-01-25": {
- "tempora": "Saturday after II Sunday after Epiphany",
- "title": "Conversion of St. Paul",
- "rank": 3,
- "colour": "w"
- },
- "2025-01-11": {
- "tempora": "",
- "title": "II Mass of the B. V. M. – Vultum Tuum",
+ "2026-01-19": {
+ "id": "tempora:Epi2-0:2:g",
+ "tempora": "Feria II after II Sunday after Epiphany",
+ "title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2025-01-21": {
+ "2026-01-20": {
+ "id": "sancti:01-20:3:r",
"tempora": "Feria III after II Sunday after Epiphany",
- "title": "St. Agnes",
- "rank": 3,
- "colour": "r"
- },
- "2025-01-27": {
- "tempora": "Feria II after III Sunday after Epiphany",
- "title": "St. John Chrysostom",
+ "title": "Sts. Fabian & Sebastian",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-01-22": {
+ "2026-01-21": {
+ "id": "sancti:01-21:3:r",
"tempora": "Feria IV after II Sunday after Epiphany",
- "title": "Sts. Vincent & Anastasius",
- "rank": 3,
- "colour": "r"
- },
- "2025-01-14": {
- "tempora": "Feria III after Epiphany",
- "title": "St. Hilary",
- "rank": 3,
- "colour": "w"
- },
- "2025-01-31": {
- "tempora": "Feria VI after III Sunday after Epiphany",
- "title": "St. John Bosco",
+ "title": "St. Agnes",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-01-15": {
- "tempora": "Feria IV after Epiphany",
- "title": "St. Paul, the First Hermit",
+ "2026-01-22": {
+ "id": "sancti:01-22:3:r",
+ "tempora": "Feria V after II Sunday after Epiphany",
+ "title": "Sts. Vincent & Anastasius",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-01-28": {
- "tempora": "Feria III after III Sunday after Epiphany",
- "title": "St. Peter Nolasco",
+ "2026-01-23": {
+ "id": "sancti:01-23:3:w",
+ "tempora": "Feria VI after II Sunday after Epiphany",
+ "title": "St. Raymond of Peñafort",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-01-24": {
- "tempora": "Feria VI after II Sunday after Epiphany",
+ "2026-01-24": {
+ "id": "sancti:01-24:3:r",
+ "tempora": "Saturday after II Sunday after Epiphany",
"title": "St. Timothy",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-01-18": {
- "tempora": "Saturday after Epiphany",
- "title": "II Mass of the B. V. M. – Vultum Tuum",
- "rank": 4,
- "colour": "w"
+ "2026-01-25": {
+ "id": "tempora:Epi3-0:2:g",
+ "tempora": "",
+ "title": "III Sunday after Epiphany",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2025-02-01": {
- "tempora": "Saturday after III Sunday after Epiphany",
- "title": "St. Ignatius of Antioch",
+ "2026-01-26": {
+ "id": "sancti:01-26:3:r",
+ "tempora": "Feria II after III Sunday after Epiphany",
+ "title": "St. Polycarp",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-01-29": {
- "tempora": "Feria IV after III Sunday after Epiphany",
- "title": "St. Francis de Sales",
+ "2026-01-27": {
+ "id": "sancti:01-27:3:w",
+ "tempora": "Feria III after III Sunday after Epiphany",
+ "title": "St. John Chrysostom",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-05": {
- "tempora": "Feria IV after IV Sunday after Epiphany",
- "title": "St. Agatha",
+ "2026-01-28": {
+ "id": "sancti:01-28:3:w",
+ "tempora": "Feria IV after III Sunday after Epiphany",
+ "title": "St. Peter Nolasco",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-09": {
- "tempora": "",
- "title": "V Sunday after Epiphany",
- "rank": 2,
- "colour": "g"
- },
- "2025-02-03": {
- "tempora": "Feria II after IV Sunday after Epiphany",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
- },
- "2025-02-07": {
- "tempora": "Feria VI after IV Sunday after Epiphany",
- "title": "St. Romuald",
+ "2026-01-29": {
+ "id": "sancti:01-29:3:w",
+ "tempora": "Feria V after III Sunday after Epiphany",
+ "title": "St. Francis de Sales",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-10": {
- "tempora": "Feria II after V Sunday after Epiphany",
- "title": "St. Scholastica",
+ "2026-01-30": {
+ "id": "sancti:01-30:3:r",
+ "tempora": "Feria VI after III Sunday after Epiphany",
+ "title": "St. Martina",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-02-11": {
- "tempora": "Feria III after V Sunday after Epiphany",
- "title": "Our Lady of Lourdes",
+ "2026-01-31": {
+ "id": "sancti:01-31:3:w",
+ "tempora": "Saturday after III Sunday after Epiphany",
+ "title": "St. John Bosco",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-16": {
+ "2026-02-01": {
+ "id": "tempora:Quadp1-0:2:v",
"tempora": "",
"title": "Septuagesima Sunday",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-02-13": {
- "tempora": "Feria V after V Sunday after Epiphany",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
- },
- "2025-02-02": {
- "tempora": "IV Sunday after Epiphany",
+ "2026-02-02": {
+ "id": "sancti:02-02:2:w",
+ "tempora": "Feria II after Septuagesima",
"title": "Purification of the Blessed Virgin Mary",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-12": {
- "tempora": "Feria IV after V Sunday after Epiphany",
- "title": "Seven Holy Servite Founders",
- "rank": 3,
- "colour": "w"
+ "2026-02-03": {
+ "id": "tempora:Quadp1-0:2:v",
+ "tempora": "Feria III after Septuagesima",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "v"
+ ]
},
- "2025-02-04": {
- "tempora": "Feria III after IV Sunday after Epiphany",
+ "2026-02-04": {
+ "id": "sancti:02-04:3:w",
+ "tempora": "Feria IV after Septuagesima",
"title": "St. Andrew Corsini",
"rank": 3,
- "colour": "w"
- },
- "2025-01-30": {
- "tempora": "Feria V after III Sunday after Epiphany",
- "title": "St. Martina",
- "rank": 3,
- "colour": "r"
- },
- "2025-01-23": {
- "tempora": "Feria V after II Sunday after Epiphany",
- "title": "St. Raymond of Peñafort",
- "rank": 3,
- "colour": "w"
- },
- "2025-02-17": {
- "tempora": "Feria II after Septuagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-20": {
+ "2026-02-05": {
+ "id": "sancti:02-05:3:r",
"tempora": "Feria V after Septuagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
- },
- "2025-02-19": {
- "tempora": "Feria IV after Septuagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
+ "title": "St. Agatha",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-02-21": {
+ "2026-02-06": {
+ "id": "sancti:02-06:3:w",
"tempora": "Feria VI after Septuagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
+ "title": "St. Titus",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-02-14": {
- "tempora": "Feria VI after V Sunday after Epiphany",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2026-02-07": {
+ "id": "sancti:02-07:3:w",
+ "tempora": "Saturday after Septuagesima",
+ "title": "St. Romuald",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-02-23": {
+ "2026-02-08": {
+ "id": "tempora:Quadp2-0:2:v",
"tempora": "",
"title": "Sexagesima Sunday",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-02-08": {
- "tempora": "Saturday after IV Sunday after Epiphany",
- "title": "St. John of Matha",
- "rank": 3,
- "colour": "w"
- },
- "2025-02-06": {
- "tempora": "Feria V after IV Sunday after Epiphany",
- "title": "St. Titus",
+ "2026-02-09": {
+ "id": "sancti:02-09:3:w",
+ "tempora": "Feria II after Sexagesima",
+ "title": "St. Cyril of Alexandria",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-25": {
+ "2026-02-10": {
+ "id": "sancti:02-10:3:w",
"tempora": "Feria III after Sexagesimæ",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
- },
- "2025-03-05": {
- "tempora": "",
- "title": "Ash Wednesday",
- "rank": 1,
- "colour": "v"
- },
- "2025-02-24": {
- "tempora": "Feria II after Sexagesima",
- "title": "St. Matthias",
- "rank": 2,
- "colour": "r"
+ "title": "St. Scholastica",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-02-26": {
+ "2026-02-11": {
+ "id": "sancti:02-11:3:w",
"tempora": "Feria IV after Sexagesimæ",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
+ "title": "Our Lady of Lourdes",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-02-27": {
+ "2026-02-12": {
+ "id": "sancti:02-12:3:w",
"tempora": "Feria V after Sexagesimæ",
- "title": "St. Gabriel of Our Lady of Sorrows",
+ "title": "Seven Holy Servite Founders",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-02-28": {
+ "2026-02-13": {
+ "id": "tempora:Quadp2-0:2:v",
"tempora": "Feria VI after Sexagesimæ",
"title": "Feria",
"rank": 4,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-02-22": {
- "tempora": "Saturday after Septuagesima",
- "title": "Chair of St. Peter",
+ "2026-02-14": {
+ "id": "commune:C10c:4:w",
+ "tempora": "Saturday after Sexagesimæ",
+ "title": "III Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-02-15": {
+ "id": "tempora:Quadp3-0:2:v",
+ "tempora": "",
+ "title": "Quinquagesima Sunday",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-03": {
+ "2026-02-16": {
+ "id": "tempora:Quadp3-0:2:v",
"tempora": "Feria II after Quinquagesima",
"title": "Feria",
"rank": 4,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-02-17": {
+ "id": "tempora:Quadp3-0:2:v",
+ "tempora": "Feria III after Quinquagesima",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "v"
+ ]
},
- "2025-03-02": {
+ "2026-02-18": {
+ "id": "tempora:Quadp3-3:1:v",
"tempora": "",
- "title": "Quinquagesima Sunday",
- "rank": 2,
- "colour": "v"
+ "title": "Ash Wednesday",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2025-03-13": {
+ "2026-02-19": {
+ "id": "tempora:Quadp3-4:3:v",
"tempora": "",
- "title": "Feria V after the I Sunday of Lent",
+ "title": "Feria V after Ash Wednesday",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-11": {
+ "2026-02-20": {
+ "id": "tempora:Quadp3-5:3:v",
"tempora": "",
- "title": "Feria III after the I Sunday of Lent",
+ "title": "Feria VI after Ash Wednesday",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-09": {
+ "2026-02-21": {
+ "id": "tempora:Quadp3-6:3:v",
+ "tempora": "",
+ "title": "Saturday after Ash Wednesday",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-02-22": {
+ "id": "tempora:Quad1-0:1:v",
"tempora": "",
"title": "I Sunday of Lent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-06": {
+ "2026-02-23": {
+ "id": "tempora:Quad1-1:3:v",
"tempora": "",
- "title": "Feria V after Ash Wednesday",
+ "title": "Feria II after the I Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-02-15": {
- "tempora": "Saturday after V Sunday after Epiphany",
- "title": "III Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "2026-02-24": {
+ "id": "sancti:02-24:2:r",
+ "tempora": "Feria III after the I Sunday of Lent",
+ "title": "St. Matthias",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
},
- "2025-02-18": {
- "tempora": "Feria III after Septuagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
+ "2026-02-25": {
+ "id": "tempora:Quad1-3:2:v",
+ "tempora": "",
+ "title": "Ember Wednesday of Lent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-02-26": {
+ "id": "tempora:Quad1-4:3:v",
+ "tempora": "",
+ "title": "Feria V after the I Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2025-03-14": {
+ "2026-02-27": {
+ "id": "tempora:Quad1-5:2:v",
"tempora": "",
"title": "Ember Friday of Lent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-16": {
+ "2026-02-28": {
+ "id": "tempora:Quad1-6:2:v",
+ "tempora": "",
+ "title": "Ember Saturday of Lent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-03-01": {
+ "id": "tempora:Quad2-0:1:v",
"tempora": "",
"title": "II Sunday of Lent",
"rank": 1,
- "colour": "v"
- },
- "2025-03-01": {
- "tempora": "Saturday after Sexagesimæ",
- "title": "III Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-12": {
+ "2026-03-02": {
+ "id": "tempora:Quad2-1:3:v",
"tempora": "",
- "title": "Ember Wednesday of Lent",
- "rank": 2,
- "colour": "v"
+ "title": "Feria II after the II Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2025-03-07": {
+ "2026-03-03": {
+ "id": "tempora:Quad2-2:3:v",
"tempora": "",
- "title": "Feria VI after Ash Wednesday",
+ "title": "Feria III after the II Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-10": {
+ "2026-03-04": {
+ "id": "tempora:Quad2-3:3:v",
"tempora": "",
- "title": "Feria II after the I Sunday of Lent",
+ "title": "Feria IV after the II Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-20": {
+ "2026-03-05": {
+ "id": "tempora:Quad2-4:3:v",
"tempora": "",
"title": "Feria V after the II Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-04": {
- "tempora": "Feria III after Quinquagesima",
- "title": "St. Casimir",
+ "2026-03-06": {
+ "id": "tempora:Quad2-5:3:v",
+ "tempora": "",
+ "title": "Feria VI after the II Sunday of Lent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-22": {
+ "2026-03-07": {
+ "id": "tempora:Quad2-6:3:v",
"tempora": "",
"title": "Saturday after the II Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-23": {
+ "2026-03-08": {
+ "id": "tempora:Quad3-0:1:v",
"tempora": "",
"title": "III Sunday of Lent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-08": {
+ "2026-03-09": {
+ "id": "tempora:Quad3-1:3:v",
"tempora": "",
- "title": "Saturday after Ash Wednesday",
+ "title": "Feria II after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-26": {
+ "2026-03-10": {
+ "id": "tempora:Quad3-2:3:v",
"tempora": "",
- "title": "Feria IV after the III Sunday of Lent",
+ "title": "Feria III after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-30": {
+ "2026-03-11": {
+ "id": "tempora:Quad3-3:3:v",
"tempora": "",
- "title": "IV Sunday of Lent",
- "rank": 1,
- "colour": "pv"
+ "title": "Feria IV after the III Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2025-03-18": {
+ "2026-03-12": {
+ "id": "tempora:Quad3-4:3:v",
"tempora": "",
- "title": "Feria III after the II Sunday of Lent",
+ "title": "Feria V after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-19": {
- "tempora": "Feria IV after the II Sunday of Lent",
- "title": "St. Joseph, Spouse of the Bl. Virgin Mary",
- "rank": 1,
- "colour": "w"
- },
- "2025-03-15": {
+ "2026-03-13": {
+ "id": "tempora:Quad3-5:3:v",
"tempora": "",
- "title": "Ember Saturday of Lent",
- "rank": 2,
- "colour": "v"
+ "title": "Feria VI after the III Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2025-03-29": {
+ "2026-03-14": {
+ "id": "tempora:Quad3-6:3:v",
"tempora": "",
"title": "Saturday after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
- },
- "2025-03-25": {
- "tempora": "Feria III after the III Sunday of Lent",
- "title": "Annunciation of the Blessed Virgin Mary",
- "rank": 1,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-06": {
+ "2026-03-15": {
+ "id": "tempora:Quad4-0:1:pv",
"tempora": "",
- "title": "Passion Sunday",
+ "title": "IV Sunday of Lent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2025-03-31": {
+ "2026-03-16": {
+ "id": "tempora:Quad4-1:3:v",
"tempora": "",
"title": "Feria II after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-01": {
+ "2026-03-17": {
+ "id": "tempora:Quad4-2:3:v",
"tempora": "",
"title": "Feria III after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-28": {
+ "2026-03-18": {
+ "id": "tempora:Quad4-3:3:v",
"tempora": "",
- "title": "Feria VI after the III Sunday of Lent",
+ "title": "Feria IV after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-24": {
- "tempora": "",
- "title": "Feria II after the III Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "2026-03-19": {
+ "id": "sancti:03-19:1:w",
+ "tempora": "Feria V after the IV Sunday of Lent",
+ "title": "St. Joseph, Spouse of the Bl. Virgin Mary",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2025-03-21": {
+ "2026-03-20": {
+ "id": "tempora:Quad4-5:3:v",
"tempora": "",
- "title": "Feria VI after the II Sunday of Lent",
+ "title": "Feria VI after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-03": {
+ "2026-03-21": {
+ "id": "tempora:Quad4-6:3:v",
"tempora": "",
- "title": "Feria V after the IV Sunday of Lent",
+ "title": "Saturday after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-13": {
+ "2026-03-22": {
+ "id": "tempora:Quad5-0:1:v",
"tempora": "",
- "title": "Palm Sunday",
+ "title": "Passion Sunday",
"rank": 1,
- "colour": "rv"
+ "colours": [
+ "v"
+ ]
},
- "2025-03-27": {
+ "2026-03-23": {
+ "id": "tempora:Quad5-1:3:v",
"tempora": "",
- "title": "Feria V after the III Sunday of Lent",
+ "title": "Feria II of Passion Week",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-08": {
+ "2026-03-24": {
+ "id": "tempora:Quad5-2:3:v",
"tempora": "",
"title": "Feria III of Passion Week",
"rank": 3,
- "colour": "v"
- },
- "2025-03-17": {
- "tempora": "",
- "title": "Feria II after the II Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-12": {
- "tempora": "",
- "title": "Saturday of Passion Week",
- "rank": 3,
- "colour": "v"
+ "2026-03-25": {
+ "id": "sancti:03-25:1:w",
+ "tempora": "Feria IV of Passion Week",
+ "title": "Annunciation of the Blessed Virgin Mary",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2025-04-10": {
+ "2026-03-26": {
+ "id": "tempora:Quad5-4:3:v",
"tempora": "",
"title": "Feria V of Passion Week",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-07": {
+ "2026-03-27": {
+ "id": "tempora:Quad5-5Feria:3:v",
"tempora": "",
- "title": "Feria II of Passion Week",
+ "title": "Feria VI of Passion Week",
"rank": 3,
- "colour": "v"
- },
- "2025-04-20": {
- "tempora": "",
- "title": "Easter Sunday",
- "rank": 1,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-09": {
+ "2026-03-28": {
+ "id": "tempora:Quad5-6:3:v",
"tempora": "",
- "title": "Feria IV of Passion Week",
+ "title": "Saturday of Passion Week",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-17": {
+ "2026-03-29": {
+ "id": "tempora:Quad6-0r:1:rv",
"tempora": "",
- "title": "Holy Thursday",
+ "title": "Palm Sunday",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "r",
+ "v"
+ ]
},
- "2025-04-14": {
+ "2026-03-30": {
+ "id": "tempora:Quad6-1:1:v",
"tempora": "",
"title": "Feria II of Holy Week",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-04-02": {
+ "2026-03-31": {
+ "id": "tempora:Quad6-2:1:v",
"tempora": "",
- "title": "Feria IV after the IV Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "title": "Feria III of Holy Week",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2025-04-11": {
+ "2026-04-01": {
+ "id": "tempora:Quad6-3:1:v",
"tempora": "",
- "title": "Feria VI of Passion Week",
- "rank": 3,
- "colour": "v"
+ "title": "Feria IV of Holy Week",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2025-04-22": {
+ "2026-04-02": {
+ "id": "tempora:Quad6-4r:1:w",
"tempora": "",
- "title": "Tuesday in the Octave of Easter",
+ "title": "Holy Thursday",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-18": {
+ "2026-04-03": {
+ "id": "tempora:Quad6-5r:1:bv",
"tempora": "",
"title": "Good Friday",
"rank": 1,
- "colour": "bv"
+ "colours": [
+ "b",
+ "v"
+ ]
},
- "2025-04-27": {
+ "2026-04-04": {
+ "id": "tempora:Quad6-6r:1:vw",
"tempora": "",
- "title": "Low Sunday",
+ "title": "Holy Saturday",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "v",
+ "w"
+ ]
},
- "2025-04-25": {
+ "2026-04-05": {
+ "id": "tempora:Pasc0-0:1:w",
"tempora": "",
- "title": "Friday in the Octave of Easter",
+ "title": "Easter Sunday",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-19": {
+ "2026-04-06": {
+ "id": "tempora:Pasc0-1:1:w",
"tempora": "",
- "title": "Holy Saturday",
+ "title": "Monday in the Octave of Easter",
"rank": 1,
- "colour": "vw"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-23": {
+ "2026-04-07": {
+ "id": "tempora:Pasc0-2:1:w",
"tempora": "",
- "title": "Wednesday in the Octave of Easter",
+ "title": "Tuesday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-21": {
+ "2026-04-08": {
+ "id": "tempora:Pasc0-3:1:w",
"tempora": "",
- "title": "Monday in the Octave of Easter",
+ "title": "Wednesday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-24": {
+ "2026-04-09": {
+ "id": "tempora:Pasc0-4:1:w",
"tempora": "",
"title": "Thursday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-28": {
- "tempora": "Monday after I Sunday after Easter",
- "title": "St. Paul of the Cross",
- "rank": 3,
- "colour": "w"
+ "2026-04-10": {
+ "id": "tempora:Pasc0-5:1:w",
+ "tempora": "",
+ "title": "Friday in the Octave of Easter",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2025-04-26": {
+ "2026-04-11": {
+ "id": "tempora:Pasc0-6:1:w",
"tempora": "",
"title": "Saturday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-04": {
+ "2026-04-12": {
+ "id": "tempora:Pasc1-0:1:w",
"tempora": "",
- "title": "Feria VI after the IV Sunday of Lent",
+ "title": "Low Sunday",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-04-13": {
+ "id": "sancti:04-13:3:r",
+ "tempora": "Monday after I Sunday after Easter",
+ "title": "St. Hermenegild",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "r"
+ ]
},
- "2025-05-04": {
- "tempora": "",
- "title": "II Sunday after Easter",
- "rank": 2,
- "colour": "w"
+ "2026-04-14": {
+ "id": "sancti:04-14:3:r",
+ "tempora": "Tuesday after I Sunday after Easter",
+ "title": "St. Justin",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-04-15": {
+ "id": "tempora:Pasc1-0:1:w",
+ "tempora": "Wednesday after I Sunday after Easter",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-01": {
+ "2026-04-16": {
+ "id": "tempora:Pasc1-0:1:w",
"tempora": "Thursday after I Sunday after Easter",
- "title": "St. Joseph the Workman",
- "rank": 1,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-02": {
+ "2026-04-17": {
+ "id": "tempora:Pasc1-0:1:w",
"tempora": "Friday after I Sunday after Easter",
- "title": "St. Athanasius",
- "rank": 3,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-04-15": {
- "tempora": "",
- "title": "Feria III of Holy Week",
- "rank": 1,
- "colour": "v"
+ "2026-04-18": {
+ "id": "commune:C10Pasc:4:w",
+ "tempora": "Saturday after I Sunday after Easter",
+ "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-04-29": {
- "tempora": "Tuesday after I Sunday after Easter",
- "title": "St. Peter of Verona",
- "rank": 3,
- "colour": "r"
+ "2026-04-19": {
+ "id": "tempora:Pasc2-0:2:w",
+ "tempora": "",
+ "title": "II Sunday after Easter",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-08": {
- "tempora": "Thursday after II Sunday after Easter",
+ "2026-04-20": {
+ "id": "tempora:Pasc2-0:2:w",
+ "tempora": "Monday after II Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-06": {
+ "2026-04-21": {
+ "id": "sancti:04-21:3:w",
"tempora": "Tuesday after II Sunday after Easter",
+ "title": "St. Anselm",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-04-22": {
+ "id": "sancti:04-22:3:r",
+ "tempora": "Wednesday after II Sunday after Easter",
+ "title": "Sts. Soter & Caius",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-04-23": {
+ "id": "tempora:Pasc2-0:2:w",
+ "tempora": "Thursday after II Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-04-30": {
- "tempora": "Wednesday after I Sunday after Easter",
- "title": "St. Catherine of Siena",
+ "2026-04-24": {
+ "id": "sancti:04-24:3:r",
+ "tempora": "Friday after II Sunday after Easter",
+ "title": "St. Fidelis of Sigmaringen",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-04-05": {
- "tempora": "",
- "title": "Saturday after the IV Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "2026-04-25": {
+ "id": "sancti:04-25:2:r",
+ "tempora": "Saturday after II Sunday after Easter",
+ "title": "St. Mark",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
},
- "2025-04-16": {
+ "2026-04-26": {
+ "id": "tempora:Pasc3-0r:2:w",
"tempora": "",
- "title": "Feria IV of Holy Week",
- "rank": 1,
- "colour": "v"
+ "title": "III Sunday after Easter",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-05": {
- "tempora": "Monday after II Sunday after Easter",
- "title": "St. Pius V",
+ "2026-04-27": {
+ "id": "sancti:04-27:3:w",
+ "tempora": "Monday after III Sunday after Easter",
+ "title": "St. Peter Canisius",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-12": {
- "tempora": "Monday after III Sunday after Easter",
- "title": "Sts. Nereus, Achilleus, Domitilla, & Pancras",
+ "2026-04-28": {
+ "id": "sancti:04-28:3:w",
+ "tempora": "Tuesday after III Sunday after Easter",
+ "title": "St. Paul of the Cross",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-09": {
- "tempora": "Friday after II Sunday after Easter",
- "title": "St. Gregory of Nazianzen",
+ "2026-04-29": {
+ "id": "sancti:04-29:3:r",
+ "tempora": "Wednesday after III Sunday after Easter",
+ "title": "St. Peter of Verona",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-05-07": {
- "tempora": "Wednesday after II Sunday after Easter",
- "title": "St. Stanislaus",
+ "2026-04-30": {
+ "id": "sancti:04-30:3:w",
+ "tempora": "Thursday after III Sunday after Easter",
+ "title": "St. Catherine of Siena",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-13": {
- "tempora": "Tuesday after III Sunday after Easter",
- "title": "St. Robert Bellarmine",
+ "2026-05-01": {
+ "id": "sancti:05-01r:1:w",
+ "tempora": "Friday after III Sunday after Easter",
+ "title": "St. Joseph the Workman",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-05-02": {
+ "id": "sancti:05-02:3:w",
+ "tempora": "Saturday after III Sunday after Easter",
+ "title": "St. Athanasius",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-18": {
+ "2026-05-03": {
+ "id": "tempora:Pasc4-0:2:w",
"tempora": "",
"title": "IV Sunday after Easter",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-11": {
- "tempora": "",
- "title": "III Sunday after Easter",
- "rank": 2,
- "colour": "w"
+ "2026-05-04": {
+ "id": "sancti:05-04:3:w",
+ "tempora": "Monday after IV Sunday after Easter",
+ "title": "St. Monica",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-22": {
- "tempora": "Thursday after IV Sunday after Easter",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
+ "2026-05-05": {
+ "id": "sancti:05-05:3:w",
+ "tempora": "Tuesday after IV Sunday after Easter",
+ "title": "St. Pius V",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-21": {
+ "2026-05-06": {
+ "id": "tempora:Pasc4-0:2:w",
"tempora": "Wednesday after IV Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-03": {
- "tempora": "Saturday after I Sunday after Easter",
- "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "2026-05-07": {
+ "id": "sancti:05-07:3:r",
+ "tempora": "Thursday after IV Sunday after Easter",
+ "title": "St. Stanislaus",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-05-23": {
+ "2026-05-08": {
+ "id": "tempora:Pasc4-0:2:w",
"tempora": "Friday after IV Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-14": {
- "tempora": "Wednesday after III Sunday after Easter",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
+ "2026-05-09": {
+ "id": "sancti:05-09:3:w",
+ "tempora": "Saturday after IV Sunday after Easter",
+ "title": "St. Gregory of Nazianzen",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-29": {
+ "2026-05-10": {
+ "id": "tempora:Pasc5-0:2:w",
"tempora": "",
- "title": "Ascension of the Lord",
- "rank": 1,
- "colour": "w"
+ "title": "V Sunday after Easter",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-05-11": {
+ "id": "sancti:05-11r:2:r",
+ "tempora": "The Minor Litanies – Rogations",
+ "title": "Sts. Philip & James",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-05-12": {
+ "id": "sancti:05-12:3:r",
+ "tempora": "The Minor Litanies – Rogations",
+ "title": "Sts. Nereus, Achilleus, Domitilla, & Pancras",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-05-25": {
+ "2026-05-13": {
+ "id": "tempora:Pasc5-3:2:w",
"tempora": "",
- "title": "V Sunday after Easter",
+ "title": "Vigil of the Ascension",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-15": {
- "tempora": "Thursday after III Sunday after Easter",
+ "2026-05-14": {
+ "id": "tempora:Pasc5-4:1:w",
+ "tempora": "",
+ "title": "Ascension of the Lord",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-05-15": {
+ "id": "sancti:05-15:3:w",
+ "tempora": "Feria VI after the Ascension",
"title": "St. John Baptist de la Salle",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-17": {
- "tempora": "Saturday after III Sunday after Easter",
- "title": "St. Paschal Baylon",
- "rank": 3,
- "colour": "w"
+ "2026-05-16": {
+ "id": "commune:C10Pasc:4:w",
+ "tempora": "Saturday after the Ascension",
+ "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-20": {
- "tempora": "Tuesday after IV Sunday after Easter",
- "title": "St. Bernardine of Siena",
- "rank": 3,
- "colour": "w"
+ "2026-05-17": {
+ "id": "tempora:Pasc6-0:2:w",
+ "tempora": "",
+ "title": "Sunday after the Ascension",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2025-05-10": {
- "tempora": "Saturday after II Sunday after Easter",
- "title": "St. Antoninus",
+ "2026-05-18": {
+ "id": "sancti:05-18:3:r",
+ "tempora": "Feria II after the Ascension",
+ "title": "St. Venantius",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-05-19": {
- "tempora": "Monday after IV Sunday after Easter",
+ "2026-05-19": {
+ "id": "sancti:05-19:3:w",
+ "tempora": "Feria III after the Ascension",
"title": "St. Peter Celestine",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-05-26": {
- "tempora": "The Minor Litanies – Rogations",
- "title": "St. Philip Neri",
+ "2026-05-20": {
+ "id": "sancti:05-20:3:w",
+ "tempora": "Feria IV after the Ascension",
+ "title": "St. Bernardine of Siena",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-06-01": {
- "tempora": "",
- "title": "Sunday after the Ascension",
- "rank": 2,
- "colour": "w"
- },
- "2025-05-30": {
- "tempora": "Feria VI after the Ascension",
+ "2026-05-21": {
+ "id": "tempora:Pasc6-0:2:w",
+ "tempora": "Feria V after the Ascension",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-06-08": {
- "tempora": "",
- "title": "Pentecost Sunday",
- "rank": 1,
- "colour": "r"
- },
- "2025-05-28": {
- "tempora": "",
- "title": "Vigil of the Ascension",
- "rank": 2,
- "colour": "w"
- },
- "2025-06-03": {
- "tempora": "Feria III after the Ascension",
+ "2026-05-22": {
+ "id": "tempora:Pasc6-0:2:w",
+ "tempora": "Feria VI after the Ascension",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-06-07": {
+ "2026-05-23": {
+ "id": "tempora:Pasc6-6:1:r",
"tempora": "",
"title": "Saturday after the Ascension",
"rank": 1,
- "colour": "r"
- },
- "2025-05-24": {
- "tempora": "Saturday after IV Sunday after Easter",
- "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
- },
- "2025-05-31": {
- "tempora": "Saturday after the Ascension",
- "title": "Queenship of the Blessed Virgin Mary",
- "rank": 2,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-06-05": {
- "tempora": "Feria V after the Ascension",
- "title": "St. Boniface",
- "rank": 3,
- "colour": "r"
- },
- "2025-06-09": {
+ "2026-05-24": {
+ "id": "tempora:Pasc7-0:1:r",
"tempora": "",
- "title": "Monday after Pentecost",
+ "title": "Pentecost Sunday",
"rank": 1,
- "colour": "r"
- },
- "2025-05-16": {
- "tempora": "Friday after III Sunday after Easter",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
- },
- "2025-06-04": {
- "tempora": "Feria IV after the Ascension",
- "title": "St. Francis Caracciolo",
- "rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-06-15": {
+ "2026-05-25": {
+ "id": "tempora:Pasc7-1:1:r",
"tempora": "",
- "title": "Trinity Sunday",
+ "title": "Monday after Pentecost",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-06-10": {
+ "2026-05-26": {
+ "id": "tempora:Pasc7-2:1:r",
"tempora": "",
"title": "Tuesday after Pentecost",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-05-27": {
- "tempora": "The Minor Litanies – Rogations",
- "title": "St. Bede the Venerable",
- "rank": 3,
- "colour": "w"
+ "2026-05-27": {
+ "id": "tempora:Pasc7-3:1:r",
+ "tempora": "",
+ "title": "Ember Wednesday of Pentecost",
+ "rank": 1,
+ "colours": [
+ "r"
+ ]
},
- "2025-06-02": {
- "tempora": "Feria II after the Ascension",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
+ "2026-05-28": {
+ "id": "tempora:Pasc7-4:1:r",
+ "tempora": "",
+ "title": "Thursday after Pentecost",
+ "rank": 1,
+ "colours": [
+ "r"
+ ]
},
- "2025-06-13": {
+ "2026-05-29": {
+ "id": "tempora:Pasc7-5:1:r",
"tempora": "",
"title": "Ember Friday of Pentecost",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-06-12": {
+ "2026-05-30": {
+ "id": "tempora:Pasc7-6:1:r",
"tempora": "",
- "title": "Thursday after Pentecost",
+ "title": "Ember Saturday of Pentecost",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-06-22": {
+ "2026-05-31": {
+ "id": "tempora:Pent01-0r:1:w",
"tempora": "",
- "title": "II Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
+ "title": "Trinity Sunday",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2025-06-16": {
+ "2026-06-01": {
+ "id": "sancti:06-01:3:w",
"tempora": "Feria II after I Sunday after Pentecost",
+ "title": "St. Angela Merici",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-06-02": {
+ "id": "tempora:Pent01-0a:2:g",
+ "tempora": "Feria III after I Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-06-11": {
- "tempora": "",
- "title": "Ember Wednesday of Pentecost",
- "rank": 1,
- "colour": "r"
- },
- "2025-06-23": {
- "tempora": "Feria II after II Sunday after Pentecost",
- "title": "Vigil of the Nativity of St. John the Baptist",
- "rank": 2,
- "colour": "v"
+ "2026-06-03": {
+ "id": "tempora:Pent01-0a:2:g",
+ "tempora": "Feria IV after I Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-06-19": {
+ "2026-06-04": {
+ "id": "tempora:Pent01-4:1:w",
"tempora": "",
"title": "Corpus Christi",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-06-24": {
- "tempora": "Feria III after II Sunday after Pentecost",
- "title": "Nativity of St. John the Baptist",
- "rank": 1,
- "colour": "w"
- },
- "2025-06-21": {
- "tempora": "Saturday after I Sunday after Pentecost",
- "title": "St. Aloysius Gongzaga",
+ "2026-06-05": {
+ "id": "sancti:06-05:3:r",
+ "tempora": "Feria V after I Sunday after Pentecost",
+ "title": "St. Boniface",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-06-26": {
- "tempora": "Feria V after II Sunday after Pentecost",
- "title": "Sts. John & Paul",
+ "2026-06-06": {
+ "id": "sancti:06-06:3:w",
+ "tempora": "Saturday after I Sunday after Pentecost",
+ "title": "St. Norbert",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-06-25": {
- "tempora": "Feria IV after II Sunday after Pentecost",
- "title": "St. William",
- "rank": 3,
- "colour": "w"
+ "2026-06-07": {
+ "id": "tempora:Pent02-0r:2:g",
+ "tempora": "",
+ "title": "II Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2025-06-20": {
- "tempora": "Feria V after I Sunday after Pentecost",
+ "2026-06-08": {
+ "id": "tempora:Pent02-0r:2:g",
+ "tempora": "Feria II after II Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-06-27": {
- "tempora": "",
- "title": "Sacred Heart of Jesus",
- "rank": 1,
- "colour": "w"
+ "2026-06-09": {
+ "id": "tempora:Pent02-0r:2:g",
+ "tempora": "Feria III after II Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-06-28": {
- "tempora": "Saturday after II Sunday after Pentecost",
- "title": "Vigil of Sts. Peter & Paul",
- "rank": 2,
- "colour": "v"
+ "2026-06-10": {
+ "id": "sancti:06-10:3:w",
+ "tempora": "Feria IV after II Sunday after Pentecost",
+ "title": "St. Margaret of Scotland",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-06-06": {
- "tempora": "Feria VI after the Ascension",
- "title": "St. Norbert",
+ "2026-06-11": {
+ "id": "sancti:06-11:3:r",
+ "tempora": "Feria V after II Sunday after Pentecost",
+ "title": "St. Barnabas",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-07-01": {
- "tempora": "Feria III after III Sunday after Pentecost",
- "title": "The Precious Blood of Our Lord Jesus Christ",
+ "2026-06-12": {
+ "id": "tempora:Pent02-5:1:w",
+ "tempora": "",
+ "title": "Sacred Heart of Jesus",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-06-18": {
- "tempora": "Feria IV after I Sunday after Pentecost",
- "title": "St. Ephrem of Syria",
+ "2026-06-13": {
+ "id": "sancti:06-13:3:w",
+ "tempora": "Saturday after II Sunday after Pentecost",
+ "title": "St. Anthony of Padua",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-07-06": {
+ "2026-06-14": {
+ "id": "tempora:Pent03-0r:2:g",
"tempora": "",
- "title": "IV Sunday after Pentecost",
+ "title": "III Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-06-30": {
+ "2026-06-15": {
+ "id": "tempora:Pent03-0r:2:g",
"tempora": "Feria II after III Sunday after Pentecost",
- "title": "In Commemoratione Sancti Pauli Apostoli",
- "rank": 3,
- "colour": "r"
- },
- "2025-06-29": {
- "tempora": "III Sunday after Pentecost",
- "title": "Sts. Peter & Paul",
- "rank": 1,
- "colour": "r"
- },
- "2025-07-03": {
- "tempora": "Feria V after III Sunday after Pentecost",
- "title": "St. Irenaeus",
- "rank": 3,
- "colour": "r"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-07-04": {
- "tempora": "Feria VI after III Sunday after Pentecost",
+ "2026-06-16": {
+ "id": "tempora:Pent03-0r:2:g",
+ "tempora": "Feria III after III Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-06-17": {
- "tempora": "Feria III after I Sunday after Pentecost",
+ "2026-06-17": {
+ "id": "sancti:06-17r:3:w",
+ "tempora": "Feria IV after III Sunday after Pentecost",
"title": "St. Gregory Barbarigo",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-07-05": {
- "tempora": "Saturday after III Sunday after Pentecost",
- "title": "St. Anthony Mary Zaccariah",
+ "2026-06-18": {
+ "id": "sancti:06-18:3:r",
+ "tempora": "Feria V after III Sunday after Pentecost",
+ "title": "St. Ephrem of Syria",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-07-09": {
- "tempora": "Feria IV after IV Sunday after Pentecost",
- "title": "Feria",
+ "2026-06-19": {
+ "id": "sancti:06-19:3:r",
+ "tempora": "Feria VI after III Sunday after Pentecost",
+ "title": "St. Julia of Falconieri",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-06-20": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after III Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2025-07-08": {
- "tempora": "Feria III after IV Sunday after Pentecost",
- "title": "St. Elizabeth of Portugal",
- "rank": 3,
- "colour": "w"
+ "2026-06-21": {
+ "id": "tempora:Pent04-0:2:g",
+ "tempora": "",
+ "title": "IV Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2025-07-07": {
+ "2026-06-22": {
+ "id": "sancti:06-22:3:w",
"tempora": "Feria II after IV Sunday after Pentecost",
- "title": "Sts. Cyril & Methodius",
+ "title": "St. Paulinus of Nola",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-07-10": {
+ "2026-06-23": {
+ "id": "sancti:06-23:2:v",
+ "tempora": "Feria III after IV Sunday after Pentecost",
+ "title": "Vigil of the Nativity of St. John the Baptist",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-06-24": {
+ "id": "sancti:06-24:1:w",
+ "tempora": "Feria IV after IV Sunday after Pentecost",
+ "title": "Nativity of St. John the Baptist",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-06-25": {
+ "id": "sancti:06-25:3:w",
"tempora": "Feria V after IV Sunday after Pentecost",
- "title": "Seven Holy Brothers and Sts. Rufina & Secunda",
+ "title": "St. William",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-07-13": {
+ "2026-06-26": {
+ "id": "sancti:06-26:3:r",
+ "tempora": "Feria VI after IV Sunday after Pentecost",
+ "title": "Sts. John & Paul",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-06-27": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after IV Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-06-28": {
+ "id": "tempora:Pent05-0:2:g",
"tempora": "",
"title": "V Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-07-19": {
- "tempora": "Saturday after V Sunday after Pentecost",
- "title": "St. Vincent de Paul",
+ "2026-06-29": {
+ "id": "sancti:06-29:1:r",
+ "tempora": "Feria II after V Sunday after Pentecost",
+ "title": "Sts. Peter & Paul",
+ "rank": 1,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-06-30": {
+ "id": "sancti:06-30:3:r",
+ "tempora": "Feria III after V Sunday after Pentecost",
+ "title": "In Commemoratione Sancti Pauli Apostoli",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-07-02": {
- "tempora": "Feria IV after III Sunday after Pentecost",
+ "2026-07-01": {
+ "id": "sancti:07-01:1:r",
+ "tempora": "Feria IV after V Sunday after Pentecost",
+ "title": "The Precious Blood of Our Lord Jesus Christ",
+ "rank": 1,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-07-02": {
+ "id": "sancti:07-02:2:w",
+ "tempora": "Feria V after V Sunday after Pentecost",
"title": "Visitation of the Blessed Virgin Mary",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-06-14": {
- "tempora": "",
- "title": "Ember Saturday of Pentecost",
- "rank": 1,
- "colour": "r"
+ "2026-07-03": {
+ "id": "sancti:07-03r:3:r",
+ "tempora": "Feria VI after V Sunday after Pentecost",
+ "title": "St. Irenaeus",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-07-20": {
+ "2026-07-04": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after V Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-07-05": {
+ "id": "tempora:Pent06-0:2:g",
"tempora": "",
"title": "VI Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-07-11": {
- "tempora": "Feria VI after IV Sunday after Pentecost",
+ "2026-07-06": {
+ "id": "tempora:Pent06-0:2:g",
+ "tempora": "Feria II after VI Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-07-22": {
+ "2026-07-07": {
+ "id": "sancti:07-07:3:w",
"tempora": "Feria III after VI Sunday after Pentecost",
- "title": "St. Mary Magdalene",
+ "title": "Sts. Cyril & Methodius",
"rank": 3,
- "colour": "w"
- },
- "2025-07-16": {
- "tempora": "Feria IV after V Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2025-07-12": {
- "tempora": "Saturday after IV Sunday after Pentecost",
- "title": "St. John Gualbert",
+ "2026-07-08": {
+ "id": "sancti:07-08:3:w",
+ "tempora": "Feria IV after VI Sunday after Pentecost",
+ "title": "St. Elizabeth of Portugal",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-07-14": {
- "tempora": "Feria II after V Sunday after Pentecost",
- "title": "St. Bonaventure",
- "rank": 3,
- "colour": "w"
+ "2026-07-09": {
+ "id": "tempora:Pent06-0:2:g",
+ "tempora": "Feria V after VI Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-07-18": {
- "tempora": "Feria VI after V Sunday after Pentecost",
- "title": "Camillus de Lellis",
+ "2026-07-10": {
+ "id": "sancti:07-10:3:r",
+ "tempora": "Feria VI after VI Sunday after Pentecost",
+ "title": "Seven Holy Brothers and Sts. Rufina & Secunda",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-07-26": {
+ "2026-07-11": {
+ "id": "commune:C10t:4:w",
"tempora": "Saturday after VI Sunday after Pentecost",
- "title": "St. Anne, Mother of the Blessed Virgin",
- "rank": 2,
- "colour": "w"
- },
- "2025-07-15": {
- "tempora": "Feria III after V Sunday after Pentecost",
- "title": "St. Henry the Emperor",
- "rank": 3,
- "colour": "w"
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2025-07-27": {
+ "2026-07-12": {
+ "id": "tempora:Pent07-0:2:g",
"tempora": "",
"title": "VII Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-07-25": {
- "tempora": "Feria VI after VI Sunday after Pentecost",
- "title": "St. James the Greater",
- "rank": 2,
- "colour": "r"
- },
- "2025-07-24": {
- "tempora": "Feria V after VI Sunday after Pentecost",
+ "2026-07-13": {
+ "id": "tempora:Pent07-0:2:g",
+ "tempora": "Feria II after VII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-07-31": {
- "tempora": "Feria V after VII Sunday after Pentecost",
- "title": "St. Ignatius Loyola",
+ "2026-07-14": {
+ "id": "sancti:07-14:3:w",
+ "tempora": "Feria III after VII Sunday after Pentecost",
+ "title": "St. Bonaventure",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-03": {
- "tempora": "",
- "title": "VIII Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
- },
- "2025-07-17": {
- "tempora": "Feria V after V Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
- },
- "2025-07-21": {
- "tempora": "Feria II after VI Sunday after Pentecost",
- "title": "St. Laurence of Brindisi",
+ "2026-07-15": {
+ "id": "sancti:07-15:3:w",
+ "tempora": "Feria IV after VII Sunday after Pentecost",
+ "title": "St. Henry the Emperor",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-01": {
- "tempora": "Feria VI after VII Sunday after Pentecost",
+ "2026-07-16": {
+ "id": "tempora:Pent07-0:2:g",
+ "tempora": "Feria V after VII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
- },
- "2025-07-29": {
- "tempora": "Feria III after VII Sunday after Pentecost",
- "title": "St. Martha",
- "rank": 3,
- "colour": "r"
+ "colours": [
+ "g"
+ ]
},
- "2025-07-28": {
- "tempora": "Feria II after VII Sunday after Pentecost",
- "title": "Sts. Nazarius & Celsus, St. Victor I & St. Innocent I",
- "rank": 3,
- "colour": "r"
- },
- "2025-07-30": {
- "tempora": "Feria IV after VII Sunday after Pentecost",
+ "2026-07-17": {
+ "id": "tempora:Pent07-0:2:g",
+ "tempora": "Feria VI after VII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-08-02": {
+ "2026-07-18": {
+ "id": "sancti:07-18:3:r",
"tempora": "Saturday after VII Sunday after Pentecost",
- "title": "St. Alphonsus Liguori",
+ "title": "Camillus de Lellis",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-07-19": {
+ "id": "tempora:Pent08-0:2:g",
+ "tempora": "",
+ "title": "VIII Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2025-08-04": {
+ "2026-07-20": {
+ "id": "sancti:07-20:3:r",
"tempora": "Feria II after VIII Sunday after Pentecost",
- "title": "St. Dominic",
+ "title": "St. Jerome Emiliani",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-08-15": {
- "tempora": "Feria VI after IX Sunday after Pentecost",
- "title": "Assumption of the Blessed Virgin Mary",
- "rank": 1,
- "colour": "w"
+ "2026-07-21": {
+ "id": "sancti:07-21r:3:w",
+ "tempora": "Feria III after VIII Sunday after Pentecost",
+ "title": "St. Laurence of Brindisi",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-08-06": {
+ "2026-07-22": {
+ "id": "sancti:07-22:3:w",
"tempora": "Feria IV after VIII Sunday after Pentecost",
- "title": "Transfiguration of Our Lord",
- "rank": 2,
- "colour": "w"
+ "title": "St. Mary Magdalene",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-08-12": {
- "tempora": "Feria III after IX Sunday after Pentecost",
- "title": "St. Clare",
+ "2026-07-23": {
+ "id": "sancti:07-23:3:w",
+ "tempora": "Feria V after VIII Sunday after Pentecost",
+ "title": "St. Apollinaris",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-11": {
- "tempora": "Feria II after IX Sunday after Pentecost",
+ "2026-07-24": {
+ "id": "tempora:Pent08-0:2:g",
+ "tempora": "Feria VI after VIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-08-17": {
- "tempora": "",
- "title": "X Sunday after Pentecost",
+ "2026-07-25": {
+ "id": "sancti:07-25:2:r",
+ "tempora": "Saturday after VIII Sunday after Pentecost",
+ "title": "St. James the Greater",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "r"
+ ]
},
- "2025-08-10": {
+ "2026-07-26": {
+ "id": "tempora:Pent09-0:2:g",
"tempora": "",
"title": "IX Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-08-13": {
- "tempora": "Feria IV after IX Sunday after Pentecost",
+ "2026-07-27": {
+ "id": "tempora:Pent09-0:2:g",
+ "tempora": "Feria II after IX Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-08-05": {
- "tempora": "Feria III after VIII Sunday after Pentecost",
- "title": "Dedication of the Basilica of St. Mary Major",
+ "2026-07-28": {
+ "id": "sancti:07-28:3:r",
+ "tempora": "Feria III after IX Sunday after Pentecost",
+ "title": "Sts. Nazarius & Celsus, St. Victor I & St. Innocent I",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-08-07": {
- "tempora": "Feria V after VIII Sunday after Pentecost",
- "title": "St. Cajetan",
+ "2026-07-29": {
+ "id": "sancti:07-29:3:r",
+ "tempora": "Feria IV after IX Sunday after Pentecost",
+ "title": "St. Martha",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-08-16": {
- "tempora": "Saturday after IX Sunday after Pentecost",
- "title": "St. Joachim, Father of the Blessed Virgin",
- "rank": 2,
- "colour": "w"
+ "2026-07-30": {
+ "id": "tempora:Pent09-0:2:g",
+ "tempora": "Feria V after IX Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-08-21": {
- "tempora": "Feria V after X Sunday after Pentecost",
- "title": "St. Jane Frances de Chantal",
+ "2026-07-31": {
+ "id": "sancti:07-31:3:w",
+ "tempora": "Feria VI after IX Sunday after Pentecost",
+ "title": "St. Ignatius Loyola",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-08-01": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after IX Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-08-02": {
+ "id": "tempora:Pent10-0:2:g",
+ "tempora": "",
+ "title": "X Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2025-08-18": {
+ "2026-08-03": {
+ "id": "tempora:Pent10-0:2:g",
"tempora": "Feria II after X Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-07-23": {
- "tempora": "Feria IV after VI Sunday after Pentecost",
- "title": "St. Apollinaris",
+ "2026-08-04": {
+ "id": "sancti:08-04:3:w",
+ "tempora": "Feria III after X Sunday after Pentecost",
+ "title": "St. Dominic",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-08": {
- "tempora": "Feria VI after VIII Sunday after Pentecost",
- "title": "St. John Mary Vianney",
+ "2026-08-05": {
+ "id": "sancti:08-05:3:w",
+ "tempora": "Feria IV after X Sunday after Pentecost",
+ "title": "Dedication of the Basilica of St. Mary Major",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-23": {
- "tempora": "Saturday after X Sunday after Pentecost",
- "title": "St. Philip Benizi",
+ "2026-08-06": {
+ "id": "sancti:08-06:2:w",
+ "tempora": "Feria V after X Sunday after Pentecost",
+ "title": "Transfiguration of Our Lord",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-08-07": {
+ "id": "sancti:08-07:3:w",
+ "tempora": "Feria VI after X Sunday after Pentecost",
+ "title": "St. Cajetan",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-20": {
- "tempora": "Feria IV after X Sunday after Pentecost",
- "title": "St. Bernard of Clairvaux",
+ "2026-08-08": {
+ "id": "sancti:08-08:3:w",
+ "tempora": "Saturday after X Sunday after Pentecost",
+ "title": "St. John Mary Vianney",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-22": {
- "tempora": "Feria VI after X Sunday after Pentecost",
- "title": "Immaculate Heart of Mary",
- "rank": 2,
- "colour": "w"
+ "2026-08-09": {
+ "id": "sancti:08-09t:3:r",
+ "tempora": "XI Sunday after Pentecost",
+ "title": "Vigil of St. Lawrence",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-08-14": {
- "tempora": "Feria V after IX Sunday after Pentecost",
- "title": "Vigil of the Assumption",
+ "2026-08-10": {
+ "id": "sancti:08-10:2:r",
+ "tempora": "Feria II after XI Sunday after Pentecost",
+ "title": "St. Lawrence",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-08-25": {
- "tempora": "Feria II after XI Sunday after Pentecost",
- "title": "St. Louis IX",
- "rank": 3,
- "colour": "w"
+ "2026-08-11": {
+ "id": "tempora:Pent11-0:2:g",
+ "tempora": "Feria III after XI Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-08-27": {
+ "2026-08-12": {
+ "id": "sancti:08-12:3:w",
"tempora": "Feria IV after XI Sunday after Pentecost",
- "title": "St. Joseph Calasance",
+ "title": "St. Clare",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-31": {
- "tempora": "",
- "title": "XII Sunday after Pentecost",
+ "2026-08-13": {
+ "id": "tempora:Pent11-0:2:g",
+ "tempora": "Feria V after XI Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-08-14": {
+ "id": "sancti:08-14:2:w",
+ "tempora": "Feria VI after XI Sunday after Pentecost",
+ "title": "Vigil of the Assumption",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-24": {
+ "2026-08-15": {
+ "id": "sancti:08-15:1:w",
+ "tempora": "Saturday after XI Sunday after Pentecost",
+ "title": "Assumption of the Blessed Virgin Mary",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-08-16": {
+ "id": "tempora:Pent12-0:2:g",
"tempora": "",
- "title": "XI Sunday after Pentecost",
+ "title": "XII Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-08-09": {
- "tempora": "Saturday after VIII Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "2026-08-17": {
+ "id": "sancti:08-17:3:w",
+ "tempora": "Feria II after XII Sunday after Pentecost",
+ "title": "St. Hyacinth",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-08-26": {
- "tempora": "Feria III after XI Sunday after Pentecost",
+ "2026-08-18": {
+ "id": "tempora:Pent12-0:2:g",
+ "tempora": "Feria III after XII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-08-19": {
- "tempora": "Feria III after X Sunday after Pentecost",
+ "2026-08-19": {
+ "id": "sancti:08-19:3:w",
+ "tempora": "Feria IV after XII Sunday after Pentecost",
"title": "St. John Eudes",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-04": {
+ "2026-08-20": {
+ "id": "sancti:08-20:3:w",
"tempora": "Feria V after XII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Bernard of Clairvaux",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-09-03": {
- "tempora": "Feria IV after XII Sunday after Pentecost",
- "title": "St. Pius X",
+ "2026-08-21": {
+ "id": "sancti:08-21:3:w",
+ "tempora": "Feria VI after XII Sunday after Pentecost",
+ "title": "St. Jane Frances de Chantal",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-07": {
+ "2026-08-22": {
+ "id": "sancti:08-22:2:w",
+ "tempora": "Saturday after XII Sunday after Pentecost",
+ "title": "Immaculate Heart of Mary",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-08-23": {
+ "id": "tempora:Pent13-0:2:g",
"tempora": "",
"title": "XIII Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-09-02": {
- "tempora": "Feria III after XII Sunday after Pentecost",
- "title": "St. Stephen of Hungary",
+ "2026-08-24": {
+ "id": "sancti:08-24:2:r",
+ "tempora": "Feria II after XIII Sunday after Pentecost",
+ "title": "St. Bartholomew",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-08-25": {
+ "id": "sancti:08-25:3:w",
+ "tempora": "Feria III after XIII Sunday after Pentecost",
+ "title": "St. Louis IX",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-30": {
- "tempora": "Saturday after XI Sunday after Pentecost",
- "title": "St. Rose of Lima",
+ "2026-08-26": {
+ "id": "tempora:Pent13-0:2:g",
+ "tempora": "Feria IV after XIII Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-08-27": {
+ "id": "sancti:08-27:3:w",
+ "tempora": "Feria V after XIII Sunday after Pentecost",
+ "title": "St. Joseph Calasance",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-08-28": {
- "tempora": "Feria V after XI Sunday after Pentecost",
+ "2026-08-28": {
+ "id": "sancti:08-28:3:r",
+ "tempora": "Feria VI after XIII Sunday after Pentecost",
"title": "St. Augustine",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-09-10": {
- "tempora": "Feria IV after XIII Sunday after Pentecost",
- "title": "St. Nicholas of Tolentino",
+ "2026-08-29": {
+ "id": "sancti:08-29:3:r",
+ "tempora": "Saturday after XIII Sunday after Pentecost",
+ "title": "Beheading of St. John the Baptist",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-08-29": {
- "tempora": "Feria VI after XI Sunday after Pentecost",
- "title": "Beheading of St. John the Baptist",
+ "2026-08-30": {
+ "id": "tempora:Pent14-0:2:g",
+ "tempora": "",
+ "title": "XIV Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-08-31": {
+ "id": "sancti:08-31:3:w",
+ "tempora": "Feria II after XIV Sunday after Pentecost",
+ "title": "St. Raymond Nonnatus",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-09": {
- "tempora": "Feria III after XIII Sunday after Pentecost",
+ "2026-09-01": {
+ "id": "tempora:Pent14-0:2:g",
+ "tempora": "Feria III after XIV Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-09-14": {
- "tempora": "XIV Sunday after Pentecost",
- "title": "Exaltation of the Holy Cross",
+ "2026-09-02": {
+ "id": "sancti:09-02:3:w",
+ "tempora": "Feria IV after XIV Sunday after Pentecost",
+ "title": "St. Stephen of Hungary",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-09-03": {
+ "id": "sancti:09-03:3:w",
+ "tempora": "Feria V after XIV Sunday after Pentecost",
+ "title": "St. Pius X",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-09-04": {
+ "id": "tempora:Pent14-0:2:g",
+ "tempora": "Feria VI after XIV Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-09-05": {
+ "id": "sancti:09-05:3:w",
+ "tempora": "Saturday after XIV Sunday after Pentecost",
+ "title": "St. Lawrence Justinian",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-09-06": {
+ "id": "tempora:Pent15-0:2:g",
+ "tempora": "",
+ "title": "XV Sunday after Pentecost",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "g"
+ ]
},
- "2025-09-08": {
- "tempora": "Feria II after XIII Sunday after Pentecost",
+ "2026-09-07": {
+ "id": "tempora:Pent15-0:2:g",
+ "tempora": "Feria II after XV Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-09-08": {
+ "id": "sancti:09-08:2:w",
+ "tempora": "Feria III after XV Sunday after Pentecost",
"title": "Nativity of the Blessed Virgin Mary",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-11": {
- "tempora": "Feria V after XIII Sunday after Pentecost",
+ "2026-09-09": {
+ "id": "tempora:Pent15-0:2:g",
+ "tempora": "Feria IV after XV Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-09-01": {
- "tempora": "Feria II after XII Sunday after Pentecost",
+ "2026-09-10": {
+ "id": "sancti:09-10:3:w",
+ "tempora": "Feria V after XV Sunday after Pentecost",
+ "title": "St. Nicholas of Tolentino",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-09-11": {
+ "id": "tempora:Pent15-0:2:g",
+ "tempora": "Feria VI after XV Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-09-12": {
- "tempora": "Feria VI after XIII Sunday after Pentecost",
+ "2026-09-12": {
+ "id": "sancti:09-12:3:w",
+ "tempora": "Saturday after XV Sunday after Pentecost",
"title": "Most Holy Name of Mary",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-19": {
- "tempora": "Feria VI after XIV Sunday after Pentecost",
- "title": "St. Januarius & Companions",
+ "2026-09-13": {
+ "id": "tempora:Pent16-0:2:g",
+ "tempora": "",
+ "title": "XVI Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-09-14": {
+ "id": "sancti:09-14:2:r",
+ "tempora": "Feria II after XVI Sunday after Pentecost",
+ "title": "Exaltation of the Holy Cross",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
+ },
+ "2026-09-15": {
+ "id": "sancti:09-15:2:w",
+ "tempora": "Feria III after XVI Sunday after Pentecost",
+ "title": "Seven Sorrows of the Blessed Virgin Mary",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-09-16": {
+ "id": "sancti:09-16:3:r",
+ "tempora": "Feria IV after XVI Sunday after Pentecost",
+ "title": "Sts. Cornelius & Cyprian",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-09-18": {
- "tempora": "Feria V after XIV Sunday after Pentecost",
+ "2026-09-17": {
+ "id": "tempora:Pent16-0:2:g",
+ "tempora": "Feria V after XVI Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-09-18": {
+ "id": "sancti:09-18:3:w",
+ "tempora": "Feria VI after XVI Sunday after Pentecost",
"title": "St. Joseph of Cupertino",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-05": {
- "tempora": "Feria VI after XII Sunday after Pentecost",
- "title": "St. Lawrence Justinian",
+ "2026-09-19": {
+ "id": "sancti:09-19:3:r",
+ "tempora": "Saturday after XVI Sunday after Pentecost",
+ "title": "St. Januarius & Companions",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-09-06": {
- "tempora": "Saturday after XII Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "2026-09-20": {
+ "id": "tempora:Pent17-0:2:g",
+ "tempora": "",
+ "title": "XVII Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2025-09-25": {
- "tempora": "Feria V after XV Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2026-09-21": {
+ "id": "sancti:09-21:2:r",
+ "tempora": "Feria II after XVII Sunday after Pentecost",
+ "title": "St. Matthew",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
},
- "2025-09-17": {
- "tempora": "Feria IV after XIV Sunday after Pentecost",
+ "2026-09-22": {
+ "id": "sancti:09-22:3:w",
+ "tempora": "Feria III after XVII Sunday after Pentecost",
+ "title": "St. Thomas of Villanova",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-09-23": {
+ "id": "tempora:093-3:2:v",
+ "tempora": "",
+ "title": "Ember Wednesday of September",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-09-24": {
+ "id": "tempora:Pent17-0:2:g",
+ "tempora": "Feria V after XVII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-09-15": {
- "tempora": "Feria II after XIV Sunday after Pentecost",
- "title": "Seven Sorrows of the Blessed Virgin Mary",
+ "2026-09-25": {
+ "id": "tempora:093-5:2:v",
+ "tempora": "",
+ "title": "Ember Friday of September",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-09-26": {
+ "2026-09-26": {
+ "id": "tempora:093-6:2:v",
"tempora": "",
- "title": "Ember Friday of September",
+ "title": "Ember Saturday of September",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-09-21": {
+ "2026-09-27": {
+ "id": "tempora:Pent18-0:2:g",
"tempora": "",
- "title": "XV Sunday after Pentecost",
+ "title": "XVIII Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-09-13": {
- "tempora": "Saturday after XIII Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "2026-09-28": {
+ "id": "sancti:09-28:3:r",
+ "tempora": "Feria II after XVIII Sunday after Pentecost",
+ "title": "St. Wenceslaus",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-09-29": {
- "tempora": "Feria II after XVI Sunday after Pentecost",
+ "2026-09-29": {
+ "id": "sancti:09-29:1:w",
+ "tempora": "Feria III after XVIII Sunday after Pentecost",
"title": "Dedication of St. Michael the Archangel",
"rank": 1,
- "colour": "w"
- },
- "2025-09-28": {
- "tempora": "",
- "title": "XVI Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-30": {
- "tempora": "Feria III after XVI Sunday after Pentecost",
+ "2026-09-30": {
+ "id": "sancti:09-30:3:w",
+ "tempora": "Feria IV after XVIII Sunday after Pentecost",
"title": "St. Jerome",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-03": {
- "tempora": "Feria VI after XVI Sunday after Pentecost",
- "title": "St. Theresa of the Infant Jesus",
- "rank": 3,
- "colour": "w"
- },
- "2025-10-05": {
- "tempora": "",
- "title": "XVII Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
+ "2026-10-01": {
+ "id": "tempora:Pent18-0:2:g",
+ "tempora": "Feria V after XVIII Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-10-02": {
- "tempora": "Feria V after XVI Sunday after Pentecost",
+ "2026-10-02": {
+ "id": "sancti:10-02:3:w",
+ "tempora": "Feria VI after XVIII Sunday after Pentecost",
"title": "Holy Guardian Angels",
"rank": 3,
- "colour": "w"
- },
- "2025-10-04": {
- "tempora": "Saturday after XVI Sunday after Pentecost",
- "title": "St. Francis of Assisi",
- "rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-20": {
- "tempora": "Saturday after XIV Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
- },
- "2025-10-09": {
- "tempora": "Feria V after XVII Sunday after Pentecost",
- "title": "St. John Leonardi",
+ "2026-10-03": {
+ "id": "sancti:10-03:3:w",
+ "tempora": "Saturday after XVIII Sunday after Pentecost",
+ "title": "St. Theresa of the Infant Jesus",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-24": {
+ "2026-10-04": {
+ "id": "tempora:Pent19-0:2:g",
"tempora": "",
- "title": "Ember Wednesday of September",
+ "title": "XIX Sunday after Pentecost",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-08": {
- "tempora": "Feria IV after XVII Sunday after Pentecost",
- "title": "St. Bridget of Sweden",
- "rank": 3,
- "colour": "w"
- },
- "2025-09-23": {
- "tempora": "Feria III after XV Sunday after Pentecost",
- "title": "St. Linus",
- "rank": 3,
- "colour": "r"
+ "2026-10-05": {
+ "id": "tempora:Pent19-0:2:g",
+ "tempora": "Feria II after XIX Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-10-10": {
- "tempora": "Feria VI after XVII Sunday after Pentecost",
- "title": "St. Francis Borgia",
+ "2026-10-06": {
+ "id": "sancti:10-06:3:w",
+ "tempora": "Feria III after XIX Sunday after Pentecost",
+ "title": "St. Bruno",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-07": {
- "tempora": "Feria III after XVII Sunday after Pentecost",
+ "2026-10-07": {
+ "id": "sancti:10-07:2:w",
+ "tempora": "Feria IV after XIX Sunday after Pentecost",
"title": "Our Lady of the Rosary",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-14": {
- "tempora": "Feria III after XVIII Sunday after Pentecost",
- "title": "St. Callistus I",
+ "2026-10-08": {
+ "id": "sancti:10-08:3:w",
+ "tempora": "Feria V after XIX Sunday after Pentecost",
+ "title": "St. Bridget of Sweden",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-06": {
- "tempora": "Feria II after XVII Sunday after Pentecost",
- "title": "St. Bruno",
+ "2026-10-09": {
+ "id": "sancti:10-09:3:w",
+ "tempora": "Feria VI after XIX Sunday after Pentecost",
+ "title": "St. John Leonardi",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-27": {
- "tempora": "",
- "title": "Ember Saturday of September",
- "rank": 2,
- "colour": "v"
+ "2026-10-10": {
+ "id": "sancti:10-10:3:w",
+ "tempora": "Saturday after XIX Sunday after Pentecost",
+ "title": "St. Francis Borgia",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-10-12": {
+ "2026-10-11": {
+ "id": "tempora:Pent20-0:2:g",
"tempora": "",
- "title": "XVIII Sunday after Pentecost",
+ "title": "XX Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-11": {
- "tempora": "Saturday after XVII Sunday after Pentecost",
- "title": "Maternity of the Blessed Virgin Mary",
- "rank": 2,
- "colour": "w"
+ "2026-10-12": {
+ "id": "tempora:Pent20-0:2:g",
+ "tempora": "Feria II after XX Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2025-09-22": {
- "tempora": "Feria II after XV Sunday after Pentecost",
- "title": "St. Thomas of Villanova",
+ "2026-10-13": {
+ "id": "sancti:10-13:3:w",
+ "tempora": "Feria III after XX Sunday after Pentecost",
+ "title": "St. Edward",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-09-16": {
- "tempora": "Feria III after XIV Sunday after Pentecost",
- "title": "Sts. Cornelius & Cyprian",
+ "2026-10-14": {
+ "id": "sancti:10-14:3:r",
+ "tempora": "Feria IV after XX Sunday after Pentecost",
+ "title": "St. Callistus I",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-10-15": {
- "tempora": "Feria IV after XVIII Sunday after Pentecost",
+ "2026-10-15": {
+ "id": "sancti:10-15:3:w",
+ "tempora": "Feria V after XX Sunday after Pentecost",
"title": "St. Teresa of Avila",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-16": {
- "tempora": "Feria V after XVIII Sunday after Pentecost",
+ "2026-10-16": {
+ "id": "sancti:10-16:3:w",
+ "tempora": "Feria VI after XX Sunday after Pentecost",
"title": "St. Hedwig",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-26": {
- "tempora": "XX Sunday after Pentecost",
- "title": "Christ the King",
- "rank": 1,
- "colour": "w"
- },
- "2025-10-18": {
- "tempora": "Saturday after XVIII Sunday after Pentecost",
- "title": "St. Luke the Evangelist",
- "rank": 2,
- "colour": "r"
- },
- "2025-10-17": {
- "tempora": "Feria VI after XVIII Sunday after Pentecost",
+ "2026-10-17": {
+ "id": "sancti:10-17:3:w",
+ "tempora": "Saturday after XX Sunday after Pentecost",
"title": "St. Margaret Mary Alacoque",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-19": {
+ "2026-10-18": {
+ "id": "tempora:Pent21-0:2:g",
"tempora": "",
- "title": "XIX Sunday after Pentecost",
+ "title": "XXI Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-20": {
- "tempora": "Feria II after XIX Sunday after Pentecost",
+ "2026-10-19": {
+ "id": "sancti:10-19:3:w",
+ "tempora": "Feria II after XXI Sunday after Pentecost",
+ "title": "St. Peter of Alcantara",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-10-20": {
+ "id": "sancti:10-20:3:w",
+ "tempora": "Feria III after XXI Sunday after Pentecost",
"title": "St. John Cantius",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-22": {
- "tempora": "Feria IV after XIX Sunday after Pentecost",
+ "2026-10-21": {
+ "id": "tempora:Pent21-0:2:g",
+ "tempora": "Feria IV after XXI Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-23": {
- "tempora": "Feria V after XIX Sunday after Pentecost",
+ "2026-10-22": {
+ "id": "tempora:Pent21-0:2:g",
+ "tempora": "Feria V after XXI Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-10-23": {
+ "id": "sancti:10-23r:3:w",
+ "tempora": "Feria VI after XXI Sunday after Pentecost",
"title": "St. Anthony Mary Claret",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-24": {
- "tempora": "Feria VI after XIX Sunday after Pentecost",
+ "2026-10-24": {
+ "id": "sancti:10-24:3:w",
+ "tempora": "Saturday after XXI Sunday after Pentecost",
"title": "St. Raphael the Archangel",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-29": {
- "tempora": "Feria IV after XX Sunday after Pentecost",
+ "2026-10-25": {
+ "id": "sancti:10-DU:1:w",
+ "tempora": "XXII Sunday after Pentecost",
+ "title": "Christ the King",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-10-26": {
+ "id": "tempora:Pent22-0:2:g",
+ "tempora": "Feria II after XXII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-27": {
- "tempora": "Feria II after XX Sunday after Pentecost",
+ "2026-10-27": {
+ "id": "tempora:Pent22-0:2:g",
+ "tempora": "Feria III after XXII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-28": {
- "tempora": "Feria III after XX Sunday after Pentecost",
+ "2026-10-28": {
+ "id": "sancti:10-28:2:r",
+ "tempora": "Feria IV after XXII Sunday after Pentecost",
"title": "Sts. Simon & Jude",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-10-31": {
- "tempora": "Feria VI after XX Sunday after Pentecost",
+ "2026-10-29": {
+ "id": "tempora:Pent22-0:2:g",
+ "tempora": "Feria V after XXII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-01": {
- "tempora": "Feria IV after XVI Sunday after Pentecost",
+ "2026-10-30": {
+ "id": "tempora:Pent22-0:2:g",
+ "tempora": "Feria VI after XXII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-11-01": {
- "tempora": "Saturday after XX Sunday after Pentecost",
+ "2026-10-31": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after XXII Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-11-01": {
+ "id": "sancti:11-01:1:w",
+ "tempora": "XXIII Sunday after Pentecost",
"title": "All Saints",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-10-30": {
- "tempora": "Feria V after XX Sunday after Pentecost",
+ "2026-11-02": {
+ "id": "sancti:11-02m1:1:b",
+ "tempora": "Feria II after XXIII Sunday after Pentecost",
+ "title": "Commemoration of All Souls",
+ "rank": 1,
+ "colours": [
+ "b"
+ ]
+ },
+ "2026-11-03": {
+ "id": "tempora:Pent23-0:2:g",
+ "tempora": "Feria III after XXIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-10-13": {
- "tempora": "Feria II after XVIII Sunday after Pentecost",
- "title": "St. Edward",
+ "2026-11-04": {
+ "id": "sancti:11-04:3:w",
+ "tempora": "Feria IV after XXIII Sunday after Pentecost",
+ "title": "St. Charles Borromeo",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-05": {
- "tempora": "Feria IV after XXI Sunday after Pentecost",
+ "2026-11-05": {
+ "id": "tempora:Pent23-0:2:g",
+ "tempora": "Feria V after XXIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-11-02": {
- "tempora": "",
- "title": "XXI Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
- },
- "2025-11-07": {
- "tempora": "Feria VI after XXI Sunday after Pentecost",
+ "2026-11-06": {
+ "id": "tempora:Pent23-0:2:g",
+ "tempora": "Feria VI after XXIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-11-06": {
- "tempora": "Feria V after XXI Sunday after Pentecost",
- "title": "Feria",
+ "2026-11-07": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after XXIII Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-14": {
- "tempora": "Feria VI after XXII Sunday after Pentecost",
- "title": "St. Josaphat",
+ "2026-11-08": {
+ "id": "tempora:Epi5-0:2:g",
+ "tempora": "",
+ "title": "V Sunday after Epiphany",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-11-09": {
+ "id": "sancti:11-09:2:w",
+ "tempora": "Feria II after V Sunday after Epiphany",
+ "title": "Dedication of the Archbasilica of Our Holy Savior",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-11-10": {
+ "id": "sancti:11-10:3:w",
+ "tempora": "Feria III after V Sunday after Epiphany",
+ "title": "St. Andrew Avellino",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-12": {
- "tempora": "Feria IV after XXII Sunday after Pentecost",
+ "2026-11-11": {
+ "id": "sancti:11-11:3:w",
+ "tempora": "Feria IV after V Sunday after Epiphany",
+ "title": "St. Martin of Tours",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-11-12": {
+ "id": "sancti:11-12:3:r",
+ "tempora": "Feria V after V Sunday after Epiphany",
"title": "St. Martin I",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-11-16": {
+ "2026-11-13": {
+ "id": "tempora:Epi5-0:2:g",
+ "tempora": "Feria VI after V Sunday after Epiphany",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2026-11-14": {
+ "id": "sancti:11-14:3:w",
+ "tempora": "Saturday after V Sunday after Epiphany",
+ "title": "St. Josaphat",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2026-11-15": {
+ "id": "tempora:Epi6-0:2:g",
"tempora": "",
- "title": "XXIII Sunday after Pentecost",
+ "title": "VI Sunday after Epiphany",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-11-15": {
- "tempora": "Saturday after XXII Sunday after Pentecost",
- "title": "St. Albert the Great",
+ "2026-11-16": {
+ "id": "sancti:11-16:3:w",
+ "tempora": "Feria II after VI Sunday after Epiphany",
+ "title": "St. Gertrude the Great",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-13": {
- "tempora": "Feria V after XXII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2026-11-17": {
+ "id": "sancti:11-17:3:w",
+ "tempora": "Feria III after VI Sunday after Epiphany",
+ "title": "St. Gregory the Wonderworker",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-11-11": {
- "tempora": "Feria III after XXII Sunday after Pentecost",
- "title": "St. Martin of Tours",
+ "2026-11-18": {
+ "id": "sancti:11-18r:3:w",
+ "tempora": "Feria IV after VI Sunday after Epiphany",
+ "title": "Dedication of the Basilicas of Sts. Peter & Paul",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-04": {
- "tempora": "Feria III after XXI Sunday after Pentecost",
- "title": "St. Charles Borromeo",
+ "2026-11-19": {
+ "id": "sancti:11-19:3:w",
+ "tempora": "Feria V after VI Sunday after Epiphany",
+ "title": "St. Elizabeth of Hungary",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-20": {
- "tempora": "Feria V after XXIII Sunday after Pentecost",
+ "2026-11-20": {
+ "id": "sancti:11-20:3:w",
+ "tempora": "Feria VI after VI Sunday after Epiphany",
"title": "St. Felix of Valois",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-10": {
- "tempora": "Feria II after XXII Sunday after Pentecost",
- "title": "St. Andrew Avellino",
+ "2026-11-21": {
+ "id": "sancti:11-21:3:w",
+ "tempora": "Saturday after VI Sunday after Epiphany",
+ "title": "Presentation of the Blessed Virgin Mary",
"rank": 3,
- "colour": "w"
- },
- "2025-10-21": {
- "tempora": "Feria III after XIX Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-23": {
+ "2026-11-22": {
+ "id": "tempora:Pent24-0:2:g",
"tempora": "",
"title": "XXIV Sunday after Pentecost",
"rank": 2,
- "colour": "g"
- },
- "2025-10-25": {
- "tempora": "Saturday after XIX Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2025-11-17": {
- "tempora": "Feria II after XXIII Sunday after Pentecost",
- "title": "St. Gregory the Wonderworker",
+ "2026-11-23": {
+ "id": "sancti:11-23:3:r",
+ "tempora": "Feria II after XXIV Sunday after Pentecost",
+ "title": "St. Clement I",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2025-11-19": {
- "tempora": "Feria IV after XXIII Sunday after Pentecost",
- "title": "St. Elizabeth of Hungary",
+ "2026-11-24": {
+ "id": "sancti:11-24:3:w",
+ "tempora": "Feria III after XXIV Sunday after Pentecost",
+ "title": "St. John of the Cross",
"rank": 3,
- "colour": "w"
- },
- "2025-11-09": {
- "tempora": "XXII Sunday after Pentecost",
- "title": "Dedication of the Archbasilica of Our Holy Savior",
- "rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-30": {
- "tempora": "",
- "title": "I Sunday of Advent",
- "rank": 1,
- "colour": "v"
+ "2026-11-25": {
+ "id": "sancti:11-25:3:r",
+ "tempora": "Feria IV after XXIV Sunday after Pentecost",
+ "title": "St. Catherine of Alexandria",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-11-27": {
+ "2026-11-26": {
+ "id": "sancti:11-26:3:w",
"tempora": "Feria V after XXIV Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Sylvester",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2025-11-28": {
+ "2026-11-27": {
+ "id": "tempora:Pent24-0:2:g",
"tempora": "Feria VI after XXIV Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2025-11-08": {
- "tempora": "Saturday after XXI Sunday after Pentecost",
+ "2026-11-28": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after XXIV Sunday after Pentecost",
"title": "V Mass of the B. V. M. – Salve, Sancta Parens",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-01": {
+ "2026-11-29": {
+ "id": "tempora:Adv1-0:1:v",
"tempora": "",
- "title": "Feria II after I Sunday of Advent",
- "rank": 3,
- "colour": "v"
+ "title": "I Sunday of Advent",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2025-11-22": {
- "tempora": "Saturday after XXIII Sunday after Pentecost",
- "title": "St. Cecilia",
- "rank": 3,
- "colour": "r"
+ "2026-11-30": {
+ "id": "sancti:11-30:2:r",
+ "tempora": "Feria II after I Sunday of Advent",
+ "title": "St. Andrew",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
},
- "2025-11-21": {
- "tempora": "Feria VI after XXIII Sunday after Pentecost",
- "title": "Presentation of the Blessed Virgin Mary",
+ "2026-12-01": {
+ "id": "tempora:Adv1-0:1:v",
+ "tempora": "",
+ "title": "Feria III after I Sunday of Advent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-11-03": {
- "tempora": "Feria II after XXI Sunday after Pentecost",
- "title": "Commemoration of All Souls",
- "rank": 1,
- "colour": "b"
+ "2026-12-02": {
+ "id": "sancti:12-02:3:r",
+ "tempora": "Feria IV after I Sunday of Advent",
+ "title": "St. Vivian",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2025-11-24": {
- "tempora": "Feria II after XXIV Sunday after Pentecost",
- "title": "St. John of the Cross",
+ "2026-12-03": {
+ "id": "sancti:12-03:3:w",
+ "tempora": "Feria V after I Sunday of Advent",
+ "title": "St. Francis Xavier",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-02": {
- "tempora": "Feria III after I Sunday of Advent",
- "title": "St. Vivian",
+ "2026-12-04": {
+ "id": "sancti:12-04:3:w",
+ "tempora": "Feria VI after I Sunday of Advent",
+ "title": "St. Peter Chrysologus",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-08": {
- "tempora": "Feria II after II Sunday of Advent",
- "title": "Immaculate Conception of the Blessed Virgin Mary",
- "rank": 1,
- "colour": "w"
+ "2026-12-05": {
+ "id": "tempora:Adv1-0:1:v",
+ "tempora": "",
+ "title": "Sabbato after I Sunday of Advent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2025-12-07": {
+ "2026-12-06": {
+ "id": "tempora:Adv2-0:1:v",
"tempora": "",
"title": "II Sunday of Advent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-11-25": {
- "tempora": "Feria III after XXIV Sunday after Pentecost",
- "title": "St. Catherine of Alexandria",
+ "2026-12-07": {
+ "id": "sancti:12-07:3:w",
+ "tempora": "Feria II after II Sunday of Advent",
+ "title": "St. Ambrose",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2025-11-18": {
- "tempora": "Feria III after XXIII Sunday after Pentecost",
- "title": "Dedication of the Basilicas of Sts. Peter & Paul",
- "rank": 3,
- "colour": "w"
+ "2026-12-08": {
+ "id": "sancti:12-08:1:w",
+ "tempora": "Feria III after II Sunday of Advent",
+ "title": "Immaculate Conception of the Blessed Virgin Mary",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2025-12-09": {
+ "2026-12-09": {
+ "id": "tempora:Adv2-0:1:v",
"tempora": "",
- "title": "Feria III after II Sunday of Advent",
- "rank": 3,
- "colour": "v"
- },
- "2025-12-03": {
- "tempora": "Feria IV after I Sunday of Advent",
- "title": "St. Francis Xavier",
+ "title": "Feria IV after II Sunday of Advent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-12-12": {
+ "2026-12-10": {
+ "id": "tempora:Adv2-0:1:v",
"tempora": "",
- "title": "Feria VI after II Sunday of Advent",
+ "title": "Feria V after II Sunday of Advent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-12-04": {
- "tempora": "Feria V after I Sunday of Advent",
- "title": "St. Peter Chrysologus",
+ "2026-12-11": {
+ "id": "sancti:12-11:3:w",
+ "tempora": "Feria VI after II Sunday of Advent",
+ "title": "St. Damasus I",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-05": {
+ "2026-12-12": {
+ "id": "tempora:Adv2-0:1:v",
"tempora": "",
- "title": "Feria VI after I Sunday of Advent",
- "rank": 3,
- "colour": "v"
- },
- "2025-12-06": {
- "tempora": "Sabbato after I Sunday of Advent",
- "title": "St. Nicholas",
+ "title": "Sabbato after II Sunday of Advent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2025-12-14": {
+ "2026-12-13": {
+ "id": "tempora:Adv3-0:1:pv",
"tempora": "",
"title": "III Sunday of Advent",
"rank": 1,
- "colour": "pv"
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2025-12-15": {
+ "2026-12-14": {
+ "id": "tempora:Adv3-0:1:pv",
"tempora": "",
"title": "Feria II after III Sunday of Advent",
"rank": 3,
- "colour": "pv"
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2025-11-29": {
- "tempora": "Saturday after XXIV Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
- },
- "2025-12-18": {
+ "2026-12-15": {
+ "id": "tempora:Adv3-0:1:pv",
"tempora": "",
- "title": "Feria V after III Sunday of Advent",
- "rank": 2,
- "colour": "pv"
- },
- "2025-12-11": {
- "tempora": "Feria V after II Sunday of Advent",
- "title": "St. Damasus I",
+ "title": "Feria III after III Sunday of Advent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2025-12-25": {
+ "2026-12-16": {
+ "id": "tempora:Adv3-3:2:v",
"tempora": "",
- "title": "The Nativity of Our Lord",
- "rank": 1,
- "colour": "w"
+ "title": "Ember Wednesday of Advent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2025-12-19": {
+ "2026-12-17": {
+ "id": "tempora:Adv3-0:1:pv",
"tempora": "",
- "title": "Ember Friday of Advent",
+ "title": "Feria V after III Sunday of Advent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2025-12-10": {
+ "2026-12-18": {
+ "id": "tempora:Adv3-5:2:v",
"tempora": "",
- "title": "Feria IV after II Sunday of Advent",
- "rank": 3,
- "colour": "v"
+ "title": "Ember Friday of Advent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2025-12-17": {
+ "2026-12-19": {
+ "id": "tempora:Adv3-6:2:v",
"tempora": "",
- "title": "Ember Wednesday of Advent",
+ "title": "Ember Saturday of Advent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-11-26": {
- "tempora": "Feria IV after XXIV Sunday after Pentecost",
- "title": "St. Sylvester",
- "rank": 3,
- "colour": "w"
- },
- "2025-12-21": {
+ "2026-12-20": {
+ "id": "tempora:Adv4-0:1:v",
"tempora": "",
"title": "IV Sunday of Advent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-12-21": {
+ "id": "sancti:12-21:2:r",
+ "tempora": "Feria II after IV Sunday of Advent",
+ "title": "St. Thomas",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
},
- "2025-12-22": {
+ "2026-12-22": {
+ "id": "tempora:Adv4-0:1:v",
"tempora": "",
- "title": "Feria II after IV Sunday of Advent",
+ "title": "Feria III after IV Sunday of Advent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-12-24": {
+ "2026-12-23": {
+ "id": "tempora:Adv4-0:1:v",
+ "tempora": "",
+ "title": "Feria IV after IV Sunday of Advent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2026-12-24": {
+ "id": "sancti:12-24:1:v",
"tempora": "",
"title": "Vigil of Christmas",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2025-12-13": {
- "tempora": "Sabbato after II Sunday of Advent",
- "title": "St. Lucy",
- "rank": 3,
- "colour": "r"
+ "2026-12-25": {
+ "id": "sancti:12-25m1:1:w",
+ "tempora": "",
+ "title": "The Nativity of Our Lord",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2025-12-23": {
+ "2026-12-26": {
+ "id": "sancti:12-26:2:r",
"tempora": "",
- "title": "Feria III after IV Sunday of Advent",
+ "title": "St. Stephen",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "r"
+ ]
},
- "2025-12-16": {
- "tempora": "Feria III after III Sunday of Advent",
- "title": "St. Eusebius",
- "rank": 3,
- "colour": "r"
- },
- "2025-12-30": {
+ "2026-12-27": {
+ "id": "tempora:Nat1-0:2:w",
"tempora": "",
- "title": "Feria in the Octave of Christmas",
+ "title": "Sunday in the Octave of Christmas",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-26": {
+ "2026-12-28": {
+ "id": "sancti:12-28:2:r",
"tempora": "",
- "title": "St. Stephen",
+ "title": "Holy Innocents",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2025-12-31": {
+ "2026-12-29": {
+ "id": "tempora:Nat1-1:2:w",
"tempora": "",
"title": "Feria in the Octave of Christmas",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-29": {
+ "2026-12-30": {
+ "id": "tempora:Nat1-1:2:w",
"tempora": "",
"title": "Feria in the Octave of Christmas",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-27": {
+ "2026-12-31": {
+ "id": "tempora:Nat1-1:2:w",
"tempora": "",
- "title": "St. John the Evangelist",
+ "title": "Feria in the Octave of Christmas",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2025-12-20": {
+ "2027-01-01": {
+ "id": "sancti:01-01:1:w",
"tempora": "",
- "title": "Ember Saturday of Advent",
- "rank": 2,
- "colour": "v"
+ "title": "Octave Day of Christmas",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2025-12-28": {
+ "2027-01-02": {
+ "id": "commune:C10b:4:w",
"tempora": "",
- "title": "Sunday in the Octave of Christmas",
+ "title": "II Mass of the B. V. M. – Vultum Tuum",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-01-03": {
+ "id": "tempora:Nat2-0:2:w",
+ "tempora": "",
+ "title": "Holy Name of Jesus",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-01": {
+ "2027-01-04": {
+ "id": "sancti:01-01:1:w",
"tempora": "",
- "title": "Octave Day of Christmas",
- "rank": 1,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2026-01-02": {
+ "2027-01-05": {
+ "id": "sancti:01-01:1:w",
"tempora": "",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-06": {
+ "2027-01-06": {
+ "id": "sancti:01-06:1:w",
"tempora": "",
"title": "Epiphany of the Lord",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-04": {
+ "2027-01-07": {
+ "id": "sancti:01-06:1:w",
"tempora": "",
- "title": "Holy Name of Jesus",
- "rank": 2,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2026-01-07": {
+ "2027-01-08": {
+ "id": "sancti:01-06:1:w",
"tempora": "",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-05": {
+ "2027-01-09": {
+ "id": "commune:C10b:4:w",
"tempora": "",
- "title": "Feria",
+ "title": "II Mass of the B. V. M. – Vultum Tuum",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-12": {
+ "2027-01-10": {
+ "id": "tempora:Epi1-0:2:w",
+ "tempora": "",
+ "title": "The Holy Family: Jesus, Mary & Joseph",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-01-11": {
+ "id": "tempora:Epi1-0a:2:w",
"tempora": "Feria II after Epiphany",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-18": {
- "tempora": "",
- "title": "II Sunday after Epiphany",
- "rank": 2,
- "colour": "g"
+ "2027-01-12": {
+ "id": "tempora:Epi1-0a:2:w",
+ "tempora": "Feria III after Epiphany",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2026-01-11": {
- "tempora": "",
- "title": "The Holy Family: Jesus, Mary & Joseph",
+ "2027-01-13": {
+ "id": "sancti:01-13:2:w",
+ "tempora": "Feria IV after Epiphany",
+ "title": "Commemoration of the Baptism of the Lord",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-17": {
- "tempora": "Saturday after Epiphany",
- "title": "St. Anthony",
+ "2027-01-14": {
+ "id": "sancti:01-14:3:w",
+ "tempora": "Feria V after Epiphany",
+ "title": "St. Hilary",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-20": {
- "tempora": "Feria III after II Sunday after Epiphany",
- "title": "Sts. Fabian & Sebastian",
+ "2027-01-15": {
+ "id": "sancti:01-15:3:w",
+ "tempora": "Feria VI after Epiphany",
+ "title": "St. Paul, the First Hermit",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-21": {
- "tempora": "Feria IV after II Sunday after Epiphany",
- "title": "St. Agnes",
+ "2027-01-16": {
+ "id": "sancti:01-16:3:r",
+ "tempora": "Saturday after Epiphany",
+ "title": "St. Marcellus I",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-01-19": {
+ "2027-01-17": {
+ "id": "tempora:Epi2-0:2:g",
+ "tempora": "",
+ "title": "II Sunday after Epiphany",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-01-18": {
+ "id": "tempora:Epi2-0:2:g",
"tempora": "Feria II after II Sunday after Epiphany",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-01-22": {
+ "2027-01-19": {
+ "id": "tempora:Epi2-0:2:g",
+ "tempora": "Feria III after II Sunday after Epiphany",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-01-20": {
+ "id": "sancti:01-20:3:r",
+ "tempora": "Feria IV after II Sunday after Epiphany",
+ "title": "Sts. Fabian & Sebastian",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-01-21": {
+ "id": "sancti:01-21:3:r",
"tempora": "Feria V after II Sunday after Epiphany",
+ "title": "St. Agnes",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-01-22": {
+ "id": "sancti:01-22:3:r",
+ "tempora": "Feria VI after II Sunday after Epiphany",
"title": "Sts. Vincent & Anastasius",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-01-25": {
+ "2027-01-23": {
+ "id": "sancti:01-23:3:w",
+ "tempora": "Saturday after II Sunday after Epiphany",
+ "title": "St. Raymond of Peñafort",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-01-24": {
+ "id": "tempora:Quadp1-0:2:v",
"tempora": "",
- "title": "III Sunday after Epiphany",
+ "title": "Septuagesima Sunday",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "v"
+ ]
},
- "2026-01-26": {
- "tempora": "Feria II after III Sunday after Epiphany",
- "title": "St. Polycarp",
+ "2027-01-25": {
+ "id": "sancti:01-25r:3:w",
+ "tempora": "Feria II after Septuagesima",
+ "title": "Conversion of St. Paul",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-24": {
- "tempora": "Saturday after II Sunday after Epiphany",
- "title": "St. Timothy",
+ "2027-01-26": {
+ "id": "sancti:01-26:3:r",
+ "tempora": "Feria III after Septuagesima",
+ "title": "St. Polycarp",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-01-27": {
- "tempora": "Feria III after III Sunday after Epiphany",
+ "2027-01-27": {
+ "id": "sancti:01-27:3:w",
+ "tempora": "Feria IV after Septuagesima",
"title": "St. John Chrysostom",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-28": {
- "tempora": "Feria IV after III Sunday after Epiphany",
+ "2027-01-28": {
+ "id": "sancti:01-28:3:w",
+ "tempora": "Feria V after Septuagesima",
"title": "St. Peter Nolasco",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-29": {
- "tempora": "Feria V after III Sunday after Epiphany",
+ "2027-01-29": {
+ "id": "sancti:01-29:3:w",
+ "tempora": "Feria VI after Septuagesima",
"title": "St. Francis de Sales",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-31": {
- "tempora": "Saturday after III Sunday after Epiphany",
- "title": "St. John Bosco",
- "rank": 3,
- "colour": "w"
- },
- "2026-01-23": {
- "tempora": "Feria VI after II Sunday after Epiphany",
- "title": "St. Raymond of Peñafort",
+ "2027-01-30": {
+ "id": "sancti:01-30:3:r",
+ "tempora": "Saturday after Septuagesima",
+ "title": "St. Martina",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-02-01": {
+ "2027-01-31": {
+ "id": "tempora:Quadp2-0:2:v",
"tempora": "",
- "title": "Septuagesima Sunday",
- "rank": 2,
- "colour": "v"
- },
- "2026-01-13": {
- "tempora": "Feria III after Epiphany",
- "title": "Commemoration of the Baptism of the Lord",
+ "title": "Sexagesima Sunday",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-01-30": {
- "tempora": "Feria VI after III Sunday after Epiphany",
- "title": "St. Martina",
+ "2027-02-01": {
+ "id": "sancti:02-01:3:r",
+ "tempora": "Feria II after Sexagesima",
+ "title": "St. Ignatius of Antioch",
"rank": 3,
- "colour": "r"
- },
- "2026-02-03": {
- "tempora": "Feria III after Septuagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
- },
- "2026-01-08": {
- "tempora": "",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
- },
- "2026-01-09": {
- "tempora": "",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-02-02": {
- "tempora": "Feria II after Septuagesima",
+ "2027-02-02": {
+ "id": "sancti:02-02:2:w",
+ "tempora": "Feria III after Sexagesimæ",
"title": "Purification of the Blessed Virgin Mary",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-10": {
- "tempora": "",
- "title": "II Mass of the B. V. M. – Vultum Tuum",
- "rank": 4,
- "colour": "w"
- },
- "2026-01-03": {
- "tempora": "",
- "title": "II Mass of the B. V. M. – Vultum Tuum",
+ "2027-02-03": {
+ "id": "tempora:Quadp2-0:2:v",
+ "tempora": "Feria IV after Sexagesimæ",
+ "title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-01-16": {
- "tempora": "Feria VI after Epiphany",
- "title": "St. Marcellus I",
- "rank": 3,
- "colour": "r"
- },
- "2026-02-08": {
- "tempora": "",
- "title": "Sexagesima Sunday",
- "rank": 2,
- "colour": "v"
- },
- "2026-02-07": {
- "tempora": "Saturday after Septuagesima",
- "title": "St. Romuald",
- "rank": 3,
- "colour": "w"
- },
- "2026-02-10": {
- "tempora": "Feria III after Sexagesimæ",
- "title": "St. Scholastica",
+ "2027-02-04": {
+ "id": "sancti:02-04:3:w",
+ "tempora": "Feria V after Sexagesimæ",
+ "title": "St. Andrew Corsini",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-02-05": {
- "tempora": "Feria V after Septuagesima",
+ "2027-02-05": {
+ "id": "sancti:02-05:3:r",
+ "tempora": "Feria VI after Sexagesimæ",
"title": "St. Agatha",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-01-15": {
- "tempora": "Feria V after Epiphany",
- "title": "St. Paul, the First Hermit",
+ "2027-02-06": {
+ "id": "sancti:02-06:3:w",
+ "tempora": "Saturday after Sexagesimæ",
+ "title": "St. Titus",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-02-11": {
- "tempora": "Feria IV after Sexagesimæ",
- "title": "Our Lady of Lourdes",
- "rank": 3,
- "colour": "w"
+ "2027-02-07": {
+ "id": "tempora:Quadp3-0:2:v",
+ "tempora": "",
+ "title": "Quinquagesima Sunday",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2026-02-12": {
- "tempora": "Feria V after Sexagesimæ",
- "title": "Seven Holy Servite Founders",
+ "2027-02-08": {
+ "id": "sancti:02-08:3:w",
+ "tempora": "Feria II after Quinquagesima",
+ "title": "St. John of Matha",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-02-13": {
- "tempora": "Feria VI after Sexagesimæ",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
- },
- "2026-02-04": {
- "tempora": "Feria IV after Septuagesima",
- "title": "St. Andrew Corsini",
+ "2027-02-09": {
+ "id": "sancti:02-09:3:w",
+ "tempora": "Feria III after Quinquagesima",
+ "title": "St. Cyril of Alexandria",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-01-14": {
- "tempora": "Feria IV after Epiphany",
- "title": "St. Hilary",
- "rank": 3,
- "colour": "w"
+ "2027-02-10": {
+ "id": "tempora:Quadp3-3:1:v",
+ "tempora": "",
+ "title": "Ash Wednesday",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2026-02-20": {
+ "2027-02-11": {
+ "id": "tempora:Quadp3-4:3:v",
"tempora": "",
- "title": "Feria VI after Ash Wednesday",
+ "title": "Feria V after Ash Wednesday",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-15": {
+ "2027-02-12": {
+ "id": "tempora:Quadp3-5:3:v",
"tempora": "",
- "title": "Quinquagesima Sunday",
- "rank": 2,
- "colour": "v"
+ "title": "Feria VI after Ash Wednesday",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2026-02-19": {
+ "2027-02-13": {
+ "id": "tempora:Quadp3-6:3:v",
"tempora": "",
- "title": "Feria V after Ash Wednesday",
+ "title": "Saturday after Ash Wednesday",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-22": {
+ "2027-02-14": {
+ "id": "tempora:Quad1-0:1:v",
"tempora": "",
"title": "I Sunday of Lent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-21": {
+ "2027-02-15": {
+ "id": "tempora:Quad1-1:3:v",
"tempora": "",
- "title": "Saturday after Ash Wednesday",
+ "title": "Feria II after the I Sunday of Lent",
"rank": 3,
- "colour": "v"
- },
- "2026-02-17": {
- "tempora": "Feria III after Quinquagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
- },
- "2026-02-16": {
- "tempora": "Feria II after Quinquagesima",
- "title": "Feria",
- "rank": 4,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-06": {
- "tempora": "Feria VI after Septuagesima",
- "title": "St. Titus",
+ "2027-02-16": {
+ "id": "tempora:Quad1-2:3:v",
+ "tempora": "",
+ "title": "Feria III after the I Sunday of Lent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-25": {
+ "2027-02-17": {
+ "id": "tempora:Quad1-3:2:v",
"tempora": "",
"title": "Ember Wednesday of Lent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-18": {
+ "2027-02-18": {
+ "id": "tempora:Quad1-4:3:v",
"tempora": "",
- "title": "Ash Wednesday",
- "rank": 1,
- "colour": "v"
+ "title": "Feria V after the I Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2026-03-02": {
+ "2027-02-19": {
+ "id": "tempora:Quad1-5:2:v",
"tempora": "",
- "title": "Feria II after the II Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "title": "Ember Friday of Lent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2026-03-01": {
+ "2027-02-20": {
+ "id": "tempora:Quad1-6:2:v",
+ "tempora": "",
+ "title": "Ember Saturday of Lent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2027-02-21": {
+ "id": "tempora:Quad2-0:1:v",
"tempora": "",
"title": "II Sunday of Lent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-26": {
- "tempora": "",
- "title": "Feria V after the I Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "2027-02-22": {
+ "id": "sancti:02-22:2:w",
+ "tempora": "Feria II after the II Sunday of Lent",
+ "title": "Chair of St. Peter",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2026-03-03": {
+ "2027-02-23": {
+ "id": "tempora:Quad2-2:3:v",
"tempora": "",
"title": "Feria III after the II Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-24": {
- "tempora": "Feria III after the I Sunday of Lent",
+ "2027-02-24": {
+ "id": "sancti:02-24:2:r",
+ "tempora": "Feria IV after the II Sunday of Lent",
"title": "St. Matthias",
"rank": 2,
- "colour": "r"
- },
- "2026-02-14": {
- "tempora": "Saturday after Sexagesimæ",
- "title": "III Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-02-09": {
- "tempora": "Feria II after Sexagesima",
- "title": "St. Cyril of Alexandria",
+ "2027-02-25": {
+ "id": "tempora:Quad2-4:3:v",
+ "tempora": "",
+ "title": "Feria V after the II Sunday of Lent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-23": {
+ "2027-02-26": {
+ "id": "tempora:Quad2-5:3:v",
"tempora": "",
- "title": "Feria II after the I Sunday of Lent",
+ "title": "Feria VI after the II Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-02-27": {
+ "2027-02-27": {
+ "id": "tempora:Quad2-6:3:v",
"tempora": "",
- "title": "Ember Friday of Lent",
- "rank": 2,
- "colour": "v"
+ "title": "Saturday after the II Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
},
- "2026-03-08": {
+ "2027-02-28": {
+ "id": "tempora:Quad3-0:1:v",
"tempora": "",
"title": "III Sunday of Lent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-05": {
+ "2027-03-01": {
+ "id": "tempora:Quad3-1:3:v",
"tempora": "",
- "title": "Feria V after the II Sunday of Lent",
+ "title": "Feria II after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-11": {
+ "2027-03-02": {
+ "id": "tempora:Quad3-2:3:v",
+ "tempora": "",
+ "title": "Feria III after the III Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
+ },
+ "2027-03-03": {
+ "id": "tempora:Quad3-3:3:v",
"tempora": "",
"title": "Feria IV after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-13": {
+ "2027-03-04": {
+ "id": "tempora:Quad3-4:3:v",
+ "tempora": "",
+ "title": "Feria V after the III Sunday of Lent",
+ "rank": 3,
+ "colours": [
+ "v"
+ ]
+ },
+ "2027-03-05": {
+ "id": "tempora:Quad3-5:3:v",
"tempora": "",
"title": "Feria VI after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-14": {
+ "2027-03-06": {
+ "id": "tempora:Quad3-6:3:v",
"tempora": "",
"title": "Saturday after the III Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-15": {
+ "2027-03-07": {
+ "id": "tempora:Quad4-0:1:pv",
"tempora": "",
"title": "IV Sunday of Lent",
"rank": 1,
- "colour": "pv"
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2026-02-28": {
- "tempora": "",
- "title": "Ember Saturday of Lent",
- "rank": 2,
- "colour": "v"
- },
- "2026-03-16": {
+ "2027-03-08": {
+ "id": "tempora:Quad4-1:3:v",
"tempora": "",
"title": "Feria II after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-12": {
+ "2027-03-09": {
+ "id": "tempora:Quad4-2:3:v",
"tempora": "",
- "title": "Feria V after the III Sunday of Lent",
+ "title": "Feria III after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-06": {
+ "2027-03-10": {
+ "id": "tempora:Quad4-3:3:v",
"tempora": "",
- "title": "Feria VI after the II Sunday of Lent",
+ "title": "Feria IV after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-10": {
+ "2027-03-11": {
+ "id": "tempora:Quad4-4:3:v",
"tempora": "",
- "title": "Feria III after the III Sunday of Lent",
+ "title": "Feria V after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-09": {
+ "2027-03-12": {
+ "id": "tempora:Quad4-5:3:v",
"tempora": "",
- "title": "Feria II after the III Sunday of Lent",
+ "title": "Feria VI after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-07": {
+ "2027-03-13": {
+ "id": "tempora:Quad4-6:3:v",
"tempora": "",
- "title": "Saturday after the II Sunday of Lent",
+ "title": "Saturday after the IV Sunday of Lent",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-22": {
+ "2027-03-14": {
+ "id": "tempora:Quad5-0:1:v",
"tempora": "",
"title": "Passion Sunday",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-20": {
+ "2027-03-15": {
+ "id": "tempora:Quad5-1:3:v",
"tempora": "",
- "title": "Feria VI after the IV Sunday of Lent",
+ "title": "Feria II of Passion Week",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-23": {
+ "2027-03-16": {
+ "id": "tempora:Quad5-2:3:v",
"tempora": "",
- "title": "Feria II of Passion Week",
+ "title": "Feria III of Passion Week",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-26": {
+ "2027-03-17": {
+ "id": "tempora:Quad5-3:3:v",
"tempora": "",
- "title": "Feria V of Passion Week",
+ "title": "Feria IV of Passion Week",
"rank": 3,
- "colour": "v"
- },
- "2026-03-19": {
- "tempora": "Feria V after the IV Sunday of Lent",
- "title": "St. Joseph, Spouse of the Bl. Virgin Mary",
- "rank": 1,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-24": {
+ "2027-03-18": {
+ "id": "tempora:Quad5-4:3:v",
"tempora": "",
- "title": "Feria III of Passion Week",
+ "title": "Feria V of Passion Week",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-27": {
+ "2027-03-19": {
+ "id": "tempora:Quad5-5Feria:3:v",
"tempora": "",
"title": "Feria VI of Passion Week",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-30": {
+ "2027-03-20": {
+ "id": "tempora:Quad5-6:3:v",
"tempora": "",
- "title": "Feria II of Holy Week",
- "rank": 1,
- "colour": "v"
- },
- "2026-03-21": {
- "tempora": "",
- "title": "Saturday after the IV Sunday of Lent",
+ "title": "Saturday of Passion Week",
"rank": 3,
- "colour": "v"
- },
- "2026-03-25": {
- "tempora": "Feria IV of Passion Week",
- "title": "Annunciation of the Blessed Virgin Mary",
- "rank": 1,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-04-02": {
+ "2027-03-21": {
+ "id": "tempora:Quad6-0r:1:rv",
"tempora": "",
- "title": "Holy Thursday",
+ "title": "Palm Sunday",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "r",
+ "v"
+ ]
},
- "2026-04-03": {
+ "2027-03-22": {
+ "id": "tempora:Quad6-1:1:v",
"tempora": "",
- "title": "Good Friday",
+ "title": "Feria II of Holy Week",
"rank": 1,
- "colour": "bv"
+ "colours": [
+ "v"
+ ]
},
- "2026-03-28": {
+ "2027-03-23": {
+ "id": "tempora:Quad6-2:1:v",
"tempora": "",
- "title": "Saturday of Passion Week",
- "rank": 3,
- "colour": "v"
+ "title": "Feria III of Holy Week",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2026-03-18": {
+ "2027-03-24": {
+ "id": "tempora:Quad6-3:1:v",
"tempora": "",
- "title": "Feria IV after the IV Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "title": "Feria IV of Holy Week",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2026-03-04": {
+ "2027-03-25": {
+ "id": "tempora:Quad6-4r:1:w",
"tempora": "",
- "title": "Feria IV after the II Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "title": "Holy Thursday",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2026-03-17": {
+ "2027-03-26": {
+ "id": "tempora:Quad6-5r:1:bv",
"tempora": "",
- "title": "Feria III after the IV Sunday of Lent",
- "rank": 3,
- "colour": "v"
+ "title": "Good Friday",
+ "rank": 1,
+ "colours": [
+ "b",
+ "v"
+ ]
},
- "2026-04-04": {
+ "2027-03-27": {
+ "id": "tempora:Quad6-6r:1:vw",
"tempora": "",
"title": "Holy Saturday",
"rank": 1,
- "colour": "vw"
+ "colours": [
+ "v",
+ "w"
+ ]
},
- "2026-04-06": {
+ "2027-03-28": {
+ "id": "tempora:Pasc0-0:1:w",
"tempora": "",
- "title": "Monday in the Octave of Easter",
+ "title": "Easter Sunday",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-05": {
+ "2027-03-29": {
+ "id": "tempora:Pasc0-1:1:w",
"tempora": "",
- "title": "Easter Sunday",
+ "title": "Monday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-07": {
+ "2027-03-30": {
+ "id": "tempora:Pasc0-2:1:w",
"tempora": "",
"title": "Tuesday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-03-29": {
+ "2027-03-31": {
+ "id": "tempora:Pasc0-3:1:w",
"tempora": "",
- "title": "Palm Sunday",
+ "title": "Wednesday in the Octave of Easter",
"rank": 1,
- "colour": "rv"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-09": {
+ "2027-04-01": {
+ "id": "tempora:Pasc0-4:1:w",
"tempora": "",
"title": "Thursday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-03-31": {
+ "2027-04-02": {
+ "id": "tempora:Pasc0-5:1:w",
"tempora": "",
- "title": "Feria III of Holy Week",
+ "title": "Friday in the Octave of Easter",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-08": {
+ "2027-04-03": {
+ "id": "tempora:Pasc0-6:1:w",
"tempora": "",
- "title": "Wednesday in the Octave of Easter",
+ "title": "Saturday in the Octave of Easter",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-10": {
- "tempora": "",
- "title": "Friday in the Octave of Easter",
- "rank": 1,
- "colour": "w"
- },
- "2026-04-12": {
+ "2027-04-04": {
+ "id": "tempora:Pasc1-0:1:w",
"tempora": "",
"title": "Low Sunday",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-11": {
- "tempora": "",
- "title": "Saturday in the Octave of Easter",
+ "2027-04-05": {
+ "id": "sancti:03-25:1:w",
+ "tempora": "Monday after I Sunday after Easter",
+ "title": "Annunciation of the Blessed Virgin Mary",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-16": {
- "tempora": "Thursday after I Sunday after Easter",
+ "2027-04-06": {
+ "id": "tempora:Pasc1-0:1:w",
+ "tempora": "Tuesday after I Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-15": {
+ "2027-04-07": {
+ "id": "tempora:Pasc1-0:1:w",
"tempora": "Wednesday after I Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-19": {
+ "2027-04-08": {
+ "id": "tempora:Pasc1-0:1:w",
+ "tempora": "Thursday after I Sunday after Easter",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-04-09": {
+ "id": "tempora:Pasc1-0:1:w",
+ "tempora": "Friday after I Sunday after Easter",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-04-10": {
+ "id": "commune:C10Pasc:4:w",
+ "tempora": "Saturday after I Sunday after Easter",
+ "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-04-11": {
+ "id": "tempora:Pasc2-0:2:w",
"tempora": "",
"title": "II Sunday after Easter",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-13": {
- "tempora": "Monday after I Sunday after Easter",
- "title": "St. Hermenegild",
- "rank": 3,
- "colour": "r"
- },
- "2026-04-20": {
+ "2027-04-12": {
+ "id": "tempora:Pasc2-0:2:w",
"tempora": "Monday after II Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-01": {
- "tempora": "",
- "title": "Feria IV of Holy Week",
- "rank": 1,
- "colour": "v"
- },
- "2026-04-14": {
- "tempora": "Tuesday after I Sunday after Easter",
- "title": "St. Justin",
+ "2027-04-13": {
+ "id": "sancti:04-13:3:r",
+ "tempora": "Tuesday after II Sunday after Easter",
+ "title": "St. Hermenegild",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-04-22": {
+ "2027-04-14": {
+ "id": "sancti:04-14:3:r",
"tempora": "Wednesday after II Sunday after Easter",
- "title": "Sts. Soter & Caius",
+ "title": "St. Justin",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-04-25": {
+ "2027-04-15": {
+ "id": "tempora:Pasc2-0:2:w",
+ "tempora": "Thursday after II Sunday after Easter",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-04-16": {
+ "id": "tempora:Pasc2-0:2:w",
+ "tempora": "Friday after II Sunday after Easter",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-04-17": {
+ "id": "commune:C10Pasc:4:w",
"tempora": "Saturday after II Sunday after Easter",
- "title": "St. Mark",
- "rank": 2,
- "colour": "r"
+ "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2026-04-26": {
+ "2027-04-18": {
+ "id": "tempora:Pasc3-0r:2:w",
"tempora": "",
"title": "III Sunday after Easter",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-28": {
- "tempora": "Tuesday after III Sunday after Easter",
- "title": "St. Paul of the Cross",
- "rank": 3,
- "colour": "w"
- },
- "2026-04-17": {
- "tempora": "Friday after I Sunday after Easter",
+ "2027-04-19": {
+ "id": "tempora:Pasc3-0r:2:w",
+ "tempora": "Monday after III Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
- },
- "2026-04-24": {
- "tempora": "Friday after II Sunday after Easter",
- "title": "St. Fidelis of Sigmaringen",
- "rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-23": {
- "tempora": "Thursday after II Sunday after Easter",
+ "2027-04-20": {
+ "id": "tempora:Pasc3-0r:2:w",
+ "tempora": "Tuesday after III Sunday after Easter",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-21": {
- "tempora": "Tuesday after II Sunday after Easter",
+ "2027-04-21": {
+ "id": "sancti:04-21:3:w",
+ "tempora": "Wednesday after III Sunday after Easter",
"title": "St. Anselm",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-01": {
- "tempora": "Friday after III Sunday after Easter",
- "title": "St. Joseph the Workman",
- "rank": 1,
- "colour": "w"
- },
- "2026-04-29": {
- "tempora": "Wednesday after III Sunday after Easter",
- "title": "St. Peter of Verona",
+ "2027-04-22": {
+ "id": "sancti:04-22:3:r",
+ "tempora": "Thursday after III Sunday after Easter",
+ "title": "Sts. Soter & Caius",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-02": {
+ "2027-04-23": {
+ "id": "tempora:Pasc3-0r:2:w",
+ "tempora": "Friday after III Sunday after Easter",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-04-24": {
+ "id": "sancti:04-24:3:r",
"tempora": "Saturday after III Sunday after Easter",
- "title": "St. Athanasius",
+ "title": "St. Fidelis of Sigmaringen",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-03": {
+ "2027-04-25": {
+ "id": "tempora:Pasc4-0:2:w",
"tempora": "",
"title": "IV Sunday after Easter",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-04-30": {
- "tempora": "Thursday after III Sunday after Easter",
- "title": "St. Catherine of Siena",
+ "2027-04-26": {
+ "id": "sancti:04-26:3:r",
+ "tempora": "Monday after IV Sunday after Easter",
+ "title": "Sts. Cletus & Marcellinus",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-04-27": {
- "tempora": "Monday after III Sunday after Easter",
+ "2027-04-27": {
+ "id": "sancti:04-27:3:w",
+ "tempora": "Tuesday after IV Sunday after Easter",
"title": "St. Peter Canisius",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-10": {
- "tempora": "",
- "title": "V Sunday after Easter",
- "rank": 2,
- "colour": "w"
+ "2027-04-28": {
+ "id": "sancti:04-28:3:w",
+ "tempora": "Wednesday after IV Sunday after Easter",
+ "title": "St. Paul of the Cross",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-05-04": {
- "tempora": "Monday after IV Sunday after Easter",
- "title": "St. Monica",
+ "2027-04-29": {
+ "id": "sancti:04-29:3:r",
+ "tempora": "Thursday after IV Sunday after Easter",
+ "title": "St. Peter of Verona",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-08": {
+ "2027-04-30": {
+ "id": "sancti:04-30:3:w",
"tempora": "Friday after IV Sunday after Easter",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
+ "title": "St. Catherine of Siena",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-04-18": {
- "tempora": "Saturday after I Sunday after Easter",
- "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "2027-05-01": {
+ "id": "sancti:05-01r:1:w",
+ "tempora": "Saturday after IV Sunday after Easter",
+ "title": "St. Joseph the Workman",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2026-05-06": {
- "tempora": "Wednesday after IV Sunday after Easter",
+ "2027-05-02": {
+ "id": "tempora:Pasc5-0:2:w",
+ "tempora": "",
+ "title": "V Sunday after Easter",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-05-03": {
+ "id": "tempora:Pasc5-0:2:w",
+ "tempora": "The Minor Litanies – Rogations",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-05": {
- "tempora": "Tuesday after IV Sunday after Easter",
- "title": "St. Pius V",
- "rank": 3,
- "colour": "w"
- },
- "2026-05-12": {
+ "2027-05-04": {
+ "id": "sancti:05-04:3:w",
"tempora": "The Minor Litanies – Rogations",
- "title": "Sts. Nereus, Achilleus, Domitilla, & Pancras",
+ "title": "St. Monica",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-11": {
- "tempora": "The Minor Litanies – Rogations",
- "title": "Sts. Philip & James",
+ "2027-05-05": {
+ "id": "tempora:Pasc5-3:2:w",
+ "tempora": "",
+ "title": "Vigil of the Ascension",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-07": {
- "tempora": "Thursday after IV Sunday after Easter",
- "title": "St. Stanislaus",
- "rank": 3,
- "colour": "r"
- },
- "2026-05-14": {
+ "2027-05-06": {
+ "id": "tempora:Pasc5-4:1:w",
"tempora": "",
"title": "Ascension of the Lord",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-17": {
+ "2027-05-07": {
+ "id": "sancti:05-07:3:r",
+ "tempora": "Feria VI after the Ascension",
+ "title": "St. Stanislaus",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-05-08": {
+ "id": "commune:C10Pasc:4:w",
+ "tempora": "Saturday after the Ascension",
+ "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-05-09": {
+ "id": "tempora:Pasc6-0:2:w",
"tempora": "",
"title": "Sunday after the Ascension",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-21": {
- "tempora": "Feria V after the Ascension",
- "title": "Feria",
- "rank": 4,
- "colour": "w"
- },
- "2026-05-18": {
+ "2027-05-10": {
+ "id": "sancti:05-10:3:w",
"tempora": "Feria II after the Ascension",
- "title": "St. Venantius",
+ "title": "St. Antoninus",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-09": {
- "tempora": "Saturday after IV Sunday after Easter",
- "title": "St. Gregory of Nazianzen",
+ "2027-05-11": {
+ "id": "sancti:05-11r:2:r",
+ "tempora": "Feria III after the Ascension",
+ "title": "Sts. Philip & James",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-05-12": {
+ "id": "sancti:05-12:3:r",
+ "tempora": "Feria IV after the Ascension",
+ "title": "Sts. Nereus, Achilleus, Domitilla, & Pancras",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-23": {
- "tempora": "",
- "title": "Saturday after the Ascension",
- "rank": 1,
- "colour": "r"
+ "2027-05-13": {
+ "id": "sancti:05-13:3:w",
+ "tempora": "Feria V after the Ascension",
+ "title": "St. Robert Bellarmine",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-05-22": {
+ "2027-05-14": {
+ "id": "tempora:Pasc6-0:2:w",
"tempora": "Feria VI after the Ascension",
"title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-05-24": {
+ "2027-05-15": {
+ "id": "tempora:Pasc6-6:1:r",
"tempora": "",
- "title": "Pentecost Sunday",
+ "title": "Saturday after the Ascension",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-26": {
+ "2027-05-16": {
+ "id": "tempora:Pasc7-0:1:r",
"tempora": "",
- "title": "Tuesday after Pentecost",
+ "title": "Pentecost Sunday",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-13": {
- "tempora": "",
- "title": "Vigil of the Ascension",
- "rank": 2,
- "colour": "w"
- },
- "2026-05-25": {
+ "2027-05-17": {
+ "id": "tempora:Pasc7-1:1:r",
"tempora": "",
"title": "Monday after Pentecost",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-15": {
- "tempora": "Feria VI after the Ascension",
- "title": "St. John Baptist de la Salle",
- "rank": 3,
- "colour": "w"
- },
- "2026-05-28": {
+ "2027-05-18": {
+ "id": "tempora:Pasc7-2:1:r",
"tempora": "",
- "title": "Thursday after Pentecost",
+ "title": "Tuesday after Pentecost",
"rank": 1,
- "colour": "r"
- },
- "2026-05-19": {
- "tempora": "Feria III after the Ascension",
- "title": "St. Peter Celestine",
- "rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-20": {
- "tempora": "Feria IV after the Ascension",
- "title": "St. Bernardine of Siena",
- "rank": 3,
- "colour": "w"
- },
- "2026-05-27": {
+ "2027-05-19": {
+ "id": "tempora:Pasc7-3:1:r",
"tempora": "",
"title": "Ember Wednesday of Pentecost",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-29": {
+ "2027-05-20": {
+ "id": "tempora:Pasc7-4:1:r",
"tempora": "",
- "title": "Ember Friday of Pentecost",
+ "title": "Thursday after Pentecost",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-05-16": {
- "tempora": "Saturday after the Ascension",
- "title": "IV Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "2027-05-21": {
+ "id": "tempora:Pasc7-5:1:r",
+ "tempora": "",
+ "title": "Ember Friday of Pentecost",
+ "rank": 1,
+ "colours": [
+ "r"
+ ]
},
- "2026-05-30": {
+ "2027-05-22": {
+ "id": "tempora:Pasc7-6:1:r",
"tempora": "",
"title": "Ember Saturday of Pentecost",
"rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-06-03": {
- "tempora": "Feria IV after I Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2027-05-23": {
+ "id": "tempora:Pent01-0r:1:w",
+ "tempora": "",
+ "title": "Trinity Sunday",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2026-06-08": {
- "tempora": "Feria II after II Sunday after Pentecost",
+ "2027-05-24": {
+ "id": "tempora:Pent01-0a:2:g",
+ "tempora": "Feria II after I Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-05": {
- "tempora": "Feria V after I Sunday after Pentecost",
- "title": "St. Boniface",
+ "2027-05-25": {
+ "id": "sancti:05-25:3:w",
+ "tempora": "Feria III after I Sunday after Pentecost",
+ "title": "St. Gregory VII",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-07": {
- "tempora": "",
- "title": "II Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
+ "2027-05-26": {
+ "id": "sancti:05-26:3:w",
+ "tempora": "Feria IV after I Sunday after Pentecost",
+ "title": "St. Philip Neri",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-05-31": {
+ "2027-05-27": {
+ "id": "tempora:Pent01-4:1:w",
"tempora": "",
- "title": "Trinity Sunday",
+ "title": "Corpus Christi",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-11": {
- "tempora": "Feria V after II Sunday after Pentecost",
- "title": "St. Barnabas",
+ "2027-05-28": {
+ "id": "sancti:05-28:3:w",
+ "tempora": "Feria V after I Sunday after Pentecost",
+ "title": "St. Augustine of Canterbury",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-01": {
- "tempora": "Feria II after I Sunday after Pentecost",
+ "2027-05-29": {
+ "id": "sancti:05-29:3:w",
+ "tempora": "Saturday after I Sunday after Pentecost",
+ "title": "St. Mary Magdalene de Pazzi",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-05-30": {
+ "id": "tempora:Pent02-0r:2:g",
+ "tempora": "",
+ "title": "II Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-05-31": {
+ "id": "sancti:05-31:2:w",
+ "tempora": "Feria II after II Sunday after Pentecost",
+ "title": "Queenship of the Blessed Virgin Mary",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-06-01": {
+ "id": "sancti:06-01:3:w",
+ "tempora": "Feria III after II Sunday after Pentecost",
"title": "St. Angela Merici",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-10": {
+ "2027-06-02": {
+ "id": "tempora:Pent02-0r:2:g",
"tempora": "Feria IV after II Sunday after Pentecost",
- "title": "St. Margaret of Scotland",
- "rank": 3,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-06-04": {
+ "2027-06-03": {
+ "id": "tempora:Pent02-0r:2:g",
+ "tempora": "Feria V after II Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-06-04": {
+ "id": "tempora:Pent02-5:1:w",
"tempora": "",
- "title": "Corpus Christi",
+ "title": "Sacred Heart of Jesus",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-14": {
+ "2027-06-05": {
+ "id": "sancti:06-05:3:r",
+ "tempora": "Saturday after II Sunday after Pentecost",
+ "title": "St. Boniface",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-06-06": {
+ "id": "tempora:Pent03-0r:2:g",
"tempora": "",
"title": "III Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-16": {
+ "2027-06-07": {
+ "id": "tempora:Pent03-0r:2:g",
+ "tempora": "Feria II after III Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-06-08": {
+ "id": "tempora:Pent03-0r:2:g",
"tempora": "Feria III after III Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-02": {
- "tempora": "Feria III after I Sunday after Pentecost",
+ "2027-06-09": {
+ "id": "tempora:Pent03-0r:2:g",
+ "tempora": "Feria IV after III Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-12": {
- "tempora": "",
- "title": "Sacred Heart of Jesus",
- "rank": 1,
- "colour": "w"
+ "2027-06-10": {
+ "id": "sancti:06-10:3:w",
+ "tempora": "Feria V after III Sunday after Pentecost",
+ "title": "St. Margaret of Scotland",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-06-21": {
+ "2027-06-11": {
+ "id": "sancti:06-11:3:r",
+ "tempora": "Feria VI after III Sunday after Pentecost",
+ "title": "St. Barnabas",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-06-12": {
+ "id": "sancti:06-12:3:r",
+ "tempora": "Saturday after III Sunday after Pentecost",
+ "title": "St. John of San Fecundo",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-06-13": {
+ "id": "tempora:Pent04-0:2:g",
"tempora": "",
"title": "IV Sunday after Pentecost",
"rank": 2,
- "colour": "g"
- },
- "2026-06-09": {
- "tempora": "Feria III after II Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-13": {
- "tempora": "Saturday after II Sunday after Pentecost",
- "title": "St. Anthony of Padua",
+ "2027-06-14": {
+ "id": "sancti:06-14:3:w",
+ "tempora": "Feria II after IV Sunday after Pentecost",
+ "title": "St. Basil the Great",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-23": {
+ "2027-06-15": {
+ "id": "tempora:Pent04-0:2:g",
"tempora": "Feria III after IV Sunday after Pentecost",
- "title": "Vigil of the Nativity of St. John the Baptist",
- "rank": 2,
- "colour": "v"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-06-15": {
- "tempora": "Feria II after III Sunday after Pentecost",
+ "2027-06-16": {
+ "id": "tempora:Pent04-0:2:g",
+ "tempora": "Feria IV after IV Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-06": {
- "tempora": "Saturday after I Sunday after Pentecost",
- "title": "St. Norbert",
+ "2027-06-17": {
+ "id": "sancti:06-17r:3:w",
+ "tempora": "Feria V after IV Sunday after Pentecost",
+ "title": "St. Gregory Barbarigo",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-22": {
- "tempora": "Feria II after IV Sunday after Pentecost",
- "title": "St. Paulinus of Nola",
+ "2027-06-18": {
+ "id": "sancti:06-18:3:r",
+ "tempora": "Feria VI after IV Sunday after Pentecost",
+ "title": "St. Ephrem of Syria",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-06-19": {
- "tempora": "Feria VI after III Sunday after Pentecost",
+ "2027-06-19": {
+ "id": "sancti:06-19:3:r",
+ "tempora": "Saturday after IV Sunday after Pentecost",
"title": "St. Julia of Falconieri",
"rank": 3,
- "colour": "r"
- },
- "2026-07-01": {
- "tempora": "Feria IV after V Sunday after Pentecost",
- "title": "The Precious Blood of Our Lord Jesus Christ",
- "rank": 1,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-07-02": {
- "tempora": "Feria V after V Sunday after Pentecost",
- "title": "Visitation of the Blessed Virgin Mary",
+ "2027-06-20": {
+ "id": "tempora:Pent05-0:2:g",
+ "tempora": "",
+ "title": "V Sunday after Pentecost",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2026-07-03": {
- "tempora": "Feria VI after V Sunday after Pentecost",
- "title": "St. Irenaeus",
+ "2027-06-21": {
+ "id": "sancti:06-21:3:w",
+ "tempora": "Feria II after V Sunday after Pentecost",
+ "title": "St. Aloysius Gongzaga",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-24": {
- "tempora": "Feria IV after IV Sunday after Pentecost",
+ "2027-06-22": {
+ "id": "sancti:06-22:3:w",
+ "tempora": "Feria III after V Sunday after Pentecost",
+ "title": "St. Paulinus of Nola",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-06-23": {
+ "id": "sancti:06-23:2:v",
+ "tempora": "Feria IV after V Sunday after Pentecost",
+ "title": "Vigil of the Nativity of St. John the Baptist",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2027-06-24": {
+ "id": "sancti:06-24:1:w",
+ "tempora": "Feria V after V Sunday after Pentecost",
"title": "Nativity of St. John the Baptist",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-07-04": {
+ "2027-06-25": {
+ "id": "sancti:06-25:3:w",
+ "tempora": "Feria VI after V Sunday after Pentecost",
+ "title": "St. William",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-06-26": {
+ "id": "sancti:06-26:3:r",
"tempora": "Saturday after V Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "title": "Sts. John & Paul",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2026-07-05": {
+ "2027-06-27": {
+ "id": "tempora:Pent06-0:2:g",
"tempora": "",
"title": "VI Sunday after Pentecost",
"rank": 2,
- "colour": "g"
- },
- "2026-07-07": {
- "tempora": "Feria III after VI Sunday after Pentecost",
- "title": "Sts. Cyril & Methodius",
- "rank": 3,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2026-07-06": {
+ "2027-06-28": {
+ "id": "sancti:06-28r:2:v",
"tempora": "Feria II after VI Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "Vigil of Sts. Peter & Paul",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2026-07-08": {
+ "2027-06-29": {
+ "id": "sancti:06-29:1:r",
+ "tempora": "Feria III after VI Sunday after Pentecost",
+ "title": "Sts. Peter & Paul",
+ "rank": 1,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-06-30": {
+ "id": "sancti:06-30:3:r",
"tempora": "Feria IV after VI Sunday after Pentecost",
- "title": "St. Elizabeth of Portugal",
+ "title": "In Commemoratione Sancti Pauli Apostoli",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-07-09": {
+ "2027-07-01": {
+ "id": "sancti:07-01:1:r",
"tempora": "Feria V after VI Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "The Precious Blood of Our Lord Jesus Christ",
+ "rank": 1,
+ "colours": [
+ "r"
+ ]
},
- "2026-07-10": {
+ "2027-07-02": {
+ "id": "sancti:07-02:2:w",
"tempora": "Feria VI after VI Sunday after Pentecost",
- "title": "Seven Holy Brothers and Sts. Rufina & Secunda",
- "rank": 3,
- "colour": "r"
- },
- "2026-06-26": {
- "tempora": "Feria VI after IV Sunday after Pentecost",
- "title": "Sts. John & Paul",
- "rank": 3,
- "colour": "r"
+ "title": "Visitation of the Blessed Virgin Mary",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2026-07-11": {
+ "2027-07-03": {
+ "id": "sancti:07-03r:3:r",
"tempora": "Saturday after VI Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "title": "St. Irenaeus",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2026-07-12": {
+ "2027-07-04": {
+ "id": "tempora:Pent07-0:2:g",
"tempora": "",
"title": "VII Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-07-13": {
+ "2027-07-05": {
+ "id": "sancti:07-05:3:w",
"tempora": "Feria II after VII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Anthony Mary Zaccariah",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-07-14": {
+ "2027-07-06": {
+ "id": "tempora:Pent07-0:2:g",
"tempora": "Feria III after VII Sunday after Pentecost",
- "title": "St. Bonaventure",
- "rank": 3,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-06-18": {
- "tempora": "Feria V after III Sunday after Pentecost",
- "title": "St. Ephrem of Syria",
+ "2027-07-07": {
+ "id": "sancti:07-07:3:w",
+ "tempora": "Feria IV after VII Sunday after Pentecost",
+ "title": "Sts. Cyril & Methodius",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-07-15": {
- "tempora": "Feria IV after VII Sunday after Pentecost",
- "title": "St. Henry the Emperor",
+ "2027-07-08": {
+ "id": "sancti:07-08:3:w",
+ "tempora": "Feria V after VII Sunday after Pentecost",
+ "title": "St. Elizabeth of Portugal",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-07-17": {
+ "2027-07-09": {
+ "id": "tempora:Pent07-0:2:g",
"tempora": "Feria VI after VII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
- },
- "2026-07-16": {
- "tempora": "Feria V after VII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-07-18": {
+ "2027-07-10": {
+ "id": "sancti:07-10:3:r",
"tempora": "Saturday after VII Sunday after Pentecost",
- "title": "Camillus de Lellis",
+ "title": "Seven Holy Brothers and Sts. Rufina & Secunda",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-07-19": {
+ "2027-07-11": {
+ "id": "tempora:Pent08-0:2:g",
"tempora": "",
"title": "VIII Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-29": {
- "tempora": "Feria II after V Sunday after Pentecost",
- "title": "Sts. Peter & Paul",
- "rank": 1,
- "colour": "r"
- },
- "2026-07-22": {
- "tempora": "Feria IV after VIII Sunday after Pentecost",
- "title": "St. Mary Magdalene",
+ "2027-07-12": {
+ "id": "sancti:07-12:3:r",
+ "tempora": "Feria II after VIII Sunday after Pentecost",
+ "title": "St. John Gualbert",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-07-21": {
+ "2027-07-13": {
+ "id": "tempora:Pent08-0:2:g",
"tempora": "Feria III after VIII Sunday after Pentecost",
- "title": "St. Laurence of Brindisi",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-07-14": {
+ "id": "sancti:07-14:3:w",
+ "tempora": "Feria IV after VIII Sunday after Pentecost",
+ "title": "St. Bonaventure",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-07-23": {
+ "2027-07-15": {
+ "id": "sancti:07-15:3:w",
"tempora": "Feria V after VIII Sunday after Pentecost",
- "title": "St. Apollinaris",
+ "title": "St. Henry the Emperor",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-07-24": {
+ "2027-07-16": {
+ "id": "tempora:Pent08-0:2:g",
"tempora": "Feria VI after VIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-07-25": {
+ "2027-07-17": {
+ "id": "commune:C10t:4:w",
"tempora": "Saturday after VIII Sunday after Pentecost",
- "title": "St. James the Greater",
- "rank": 2,
- "colour": "r"
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2026-07-26": {
+ "2027-07-18": {
+ "id": "tempora:Pent09-0:2:g",
"tempora": "",
"title": "IX Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-07-27": {
+ "2027-07-19": {
+ "id": "sancti:07-19:3:w",
"tempora": "Feria II after IX Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Vincent de Paul",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-07-28": {
+ "2027-07-20": {
+ "id": "sancti:07-20:3:r",
"tempora": "Feria III after IX Sunday after Pentecost",
- "title": "Sts. Nazarius & Celsus, St. Victor I & St. Innocent I",
+ "title": "St. Jerome Emiliani",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-07-29": {
+ "2027-07-21": {
+ "id": "sancti:07-21r:3:w",
"tempora": "Feria IV after IX Sunday after Pentecost",
- "title": "St. Martha",
+ "title": "St. Laurence of Brindisi",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-07-30": {
+ "2027-07-22": {
+ "id": "sancti:07-22:3:w",
"tempora": "Feria V after IX Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Mary Magdalene",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-07-31": {
+ "2027-07-23": {
+ "id": "sancti:07-23:3:w",
"tempora": "Feria VI after IX Sunday after Pentecost",
- "title": "St. Ignatius Loyola",
+ "title": "St. Apollinaris",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-01": {
+ "2027-07-24": {
+ "id": "commune:C10t:4:w",
"tempora": "Saturday after IX Sunday after Pentecost",
"title": "V Mass of the B. V. M. – Salve, Sancta Parens",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-02": {
+ "2027-07-25": {
+ "id": "tempora:Pent10-0:2:g",
"tempora": "",
"title": "X Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-08-03": {
+ "2027-07-26": {
+ "id": "sancti:07-26:2:w",
"tempora": "Feria II after X Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Anne, Mother of the Blessed Virgin",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2026-08-04": {
+ "2027-07-27": {
+ "id": "tempora:Pent10-0:2:g",
"tempora": "Feria III after X Sunday after Pentecost",
- "title": "St. Dominic",
- "rank": 3,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-08-05": {
+ "2027-07-28": {
+ "id": "sancti:07-28:3:r",
"tempora": "Feria IV after X Sunday after Pentecost",
- "title": "Dedication of the Basilica of St. Mary Major",
+ "title": "Sts. Nazarius & Celsus, St. Victor I & St. Innocent I",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-08-06": {
+ "2027-07-29": {
+ "id": "sancti:07-29:3:r",
"tempora": "Feria V after X Sunday after Pentecost",
- "title": "Transfiguration of Our Lord",
- "rank": 2,
- "colour": "w"
- },
- "2026-06-17": {
- "tempora": "Feria IV after III Sunday after Pentecost",
- "title": "St. Gregory Barbarigo",
+ "title": "St. Martha",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-08-07": {
+ "2027-07-30": {
+ "id": "tempora:Pent10-0:2:g",
"tempora": "Feria VI after X Sunday after Pentecost",
- "title": "St. Cajetan",
- "rank": 3,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-08-08": {
+ "2027-07-31": {
+ "id": "sancti:07-31:3:w",
"tempora": "Saturday after X Sunday after Pentecost",
- "title": "St. John Mary Vianney",
+ "title": "St. Ignatius Loyola",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-10": {
- "tempora": "Feria II after XI Sunday after Pentecost",
- "title": "St. Lawrence",
+ "2027-08-01": {
+ "id": "tempora:Pent11-0:2:g",
+ "tempora": "",
+ "title": "XI Sunday after Pentecost",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "g"
+ ]
},
- "2026-08-09": {
- "tempora": "XI Sunday after Pentecost",
- "title": "Vigil of St. Lawrence",
+ "2027-08-02": {
+ "id": "sancti:08-02:3:r",
+ "tempora": "Feria II after XI Sunday after Pentecost",
+ "title": "St. Alphonsus Liguori",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-08-11": {
+ "2027-08-03": {
+ "id": "tempora:Pent11-0:2:g",
"tempora": "Feria III after XI Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-08-12": {
+ "2027-08-04": {
+ "id": "sancti:08-04:3:w",
"tempora": "Feria IV after XI Sunday after Pentecost",
- "title": "St. Clare",
+ "title": "St. Dominic",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-13": {
+ "2027-08-05": {
+ "id": "sancti:08-05:3:w",
"tempora": "Feria V after XI Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
- },
- "2026-08-16": {
- "tempora": "",
- "title": "XII Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
+ "title": "Dedication of the Basilica of St. Mary Major",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-08-14": {
+ "2027-08-06": {
+ "id": "sancti:08-06:2:w",
"tempora": "Feria VI after XI Sunday after Pentecost",
- "title": "Vigil of the Assumption",
+ "title": "Transfiguration of Our Lord",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-15": {
+ "2027-08-07": {
+ "id": "sancti:08-07:3:w",
"tempora": "Saturday after XI Sunday after Pentecost",
- "title": "Assumption of the Blessed Virgin Mary",
- "rank": 1,
- "colour": "w"
+ "title": "St. Cajetan",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-08-17": {
+ "2027-08-08": {
+ "id": "tempora:Pent12-0:2:g",
+ "tempora": "",
+ "title": "XII Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-08-09": {
+ "id": "sancti:08-09t:3:r",
"tempora": "Feria II after XII Sunday after Pentecost",
- "title": "St. Hyacinth",
+ "title": "Vigil of St. Lawrence",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-08-18": {
+ "2027-08-10": {
+ "id": "sancti:08-10:2:r",
"tempora": "Feria III after XII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Lawrence",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
},
- "2026-08-19": {
+ "2027-08-11": {
+ "id": "tempora:Pent12-0:2:g",
"tempora": "Feria IV after XII Sunday after Pentecost",
- "title": "St. John Eudes",
- "rank": 3,
- "colour": "w"
- },
- "2026-06-25": {
- "tempora": "Feria V after IV Sunday after Pentecost",
- "title": "St. William",
- "rank": 3,
- "colour": "w"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-08-20": {
+ "2027-08-12": {
+ "id": "sancti:08-12:3:w",
"tempora": "Feria V after XII Sunday after Pentecost",
- "title": "St. Bernard of Clairvaux",
+ "title": "St. Clare",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-21": {
+ "2027-08-13": {
+ "id": "tempora:Pent12-0:2:g",
"tempora": "Feria VI after XII Sunday after Pentecost",
- "title": "St. Jane Frances de Chantal",
- "rank": 3,
- "colour": "w"
- },
- "2026-08-23": {
- "tempora": "",
- "title": "XIII Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-08-22": {
+ "2027-08-14": {
+ "id": "sancti:08-14:2:w",
"tempora": "Saturday after XII Sunday after Pentecost",
- "title": "Immaculate Heart of Mary",
+ "title": "Vigil of the Assumption",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-24": {
+ "2027-08-15": {
+ "id": "sancti:08-15:1:w",
+ "tempora": "XIII Sunday after Pentecost",
+ "title": "Assumption of the Blessed Virgin Mary",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-08-16": {
+ "id": "sancti:08-16:2:w",
"tempora": "Feria II after XIII Sunday after Pentecost",
- "title": "St. Bartholomew",
+ "title": "St. Joachim, Father of the Blessed Virgin",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-25": {
+ "2027-08-17": {
+ "id": "sancti:08-17:3:w",
"tempora": "Feria III after XIII Sunday after Pentecost",
- "title": "St. Louis IX",
+ "title": "St. Hyacinth",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-26": {
+ "2027-08-18": {
+ "id": "tempora:Pent13-0:2:g",
"tempora": "Feria IV after XIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-08-27": {
+ "2027-08-19": {
+ "id": "sancti:08-19:3:w",
"tempora": "Feria V after XIII Sunday after Pentecost",
- "title": "St. Joseph Calasance",
+ "title": "St. John Eudes",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-28": {
+ "2027-08-20": {
+ "id": "sancti:08-20:3:w",
"tempora": "Feria VI after XIII Sunday after Pentecost",
- "title": "St. Augustine",
+ "title": "St. Bernard of Clairvaux",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-29": {
+ "2027-08-21": {
+ "id": "sancti:08-21:3:w",
"tempora": "Saturday after XIII Sunday after Pentecost",
- "title": "Beheading of St. John the Baptist",
+ "title": "St. Jane Frances de Chantal",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-08-30": {
+ "2027-08-22": {
+ "id": "tempora:Pent14-0:2:g",
"tempora": "",
"title": "XIV Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-08-31": {
+ "2027-08-23": {
+ "id": "sancti:08-23:3:w",
"tempora": "Feria II after XIV Sunday after Pentecost",
- "title": "St. Raymond Nonnatus",
+ "title": "St. Philip Benizi",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-09-01": {
+ "2027-08-24": {
+ "id": "sancti:08-24:2:r",
"tempora": "Feria III after XIV Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
- },
- "2026-06-28": {
- "tempora": "",
- "title": "V Sunday after Pentecost",
+ "title": "St. Bartholomew",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "r"
+ ]
},
- "2026-09-07": {
- "tempora": "Feria II after XV Sunday after Pentecost",
+ "2027-08-25": {
+ "id": "sancti:08-25:3:w",
+ "tempora": "Feria IV after XIV Sunday after Pentecost",
+ "title": "St. Louis IX",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-08-26": {
+ "id": "tempora:Pent14-0:2:g",
+ "tempora": "Feria V after XIV Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-09-04": {
+ "2027-08-27": {
+ "id": "sancti:08-27:3:w",
"tempora": "Feria VI after XIV Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Joseph Calasance",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-09-06": {
+ "2027-08-28": {
+ "id": "sancti:08-28:3:r",
+ "tempora": "Saturday after XIV Sunday after Pentecost",
+ "title": "St. Augustine",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-08-29": {
+ "id": "tempora:Pent15-0:2:g",
"tempora": "",
"title": "XV Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-30": {
- "tempora": "Feria III after V Sunday after Pentecost",
- "title": "In Commemoratione Sancti Pauli Apostoli",
+ "2027-08-30": {
+ "id": "sancti:08-30:3:r",
+ "tempora": "Feria II after XV Sunday after Pentecost",
+ "title": "St. Rose of Lima",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-09-03": {
- "tempora": "Feria V after XIV Sunday after Pentecost",
- "title": "St. Pius X",
+ "2027-08-31": {
+ "id": "sancti:08-31:3:w",
+ "tempora": "Feria III after XV Sunday after Pentecost",
+ "title": "St. Raymond Nonnatus",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-09-13": {
- "tempora": "",
- "title": "XVI Sunday after Pentecost",
- "rank": 2,
- "colour": "g"
- },
- "2026-09-14": {
- "tempora": "Feria II after XVI Sunday after Pentecost",
- "title": "Exaltation of the Holy Cross",
- "rank": 2,
- "colour": "r"
+ "2027-09-01": {
+ "id": "tempora:Pent15-0:2:g",
+ "tempora": "Feria IV after XV Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-09-10": {
+ "2027-09-02": {
+ "id": "sancti:09-02:3:w",
"tempora": "Feria V after XV Sunday after Pentecost",
- "title": "St. Nicholas of Tolentino",
+ "title": "St. Stephen of Hungary",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-09-02": {
- "tempora": "Feria IV after XIV Sunday after Pentecost",
- "title": "St. Stephen of Hungary",
+ "2027-09-03": {
+ "id": "sancti:09-03:3:w",
+ "tempora": "Feria VI after XV Sunday after Pentecost",
+ "title": "St. Pius X",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-06-27": {
- "tempora": "Saturday after IV Sunday after Pentecost",
+ "2027-09-04": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after XV Sunday after Pentecost",
"title": "V Mass of the B. V. M. – Salve, Sancta Parens",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-07-20": {
- "tempora": "Feria II after VIII Sunday after Pentecost",
- "title": "St. Jerome Emiliani",
- "rank": 3,
- "colour": "r"
- },
- "2026-09-08": {
- "tempora": "Feria III after XV Sunday after Pentecost",
- "title": "Nativity of the Blessed Virgin Mary",
+ "2027-09-05": {
+ "id": "tempora:Pent16-0:2:g",
+ "tempora": "",
+ "title": "XVI Sunday after Pentecost",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2026-06-20": {
- "tempora": "Saturday after III Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "2027-09-06": {
+ "id": "tempora:Pent16-0:2:g",
+ "tempora": "Feria II after XVI Sunday after Pentecost",
+ "title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2026-09-09": {
- "tempora": "Feria IV after XV Sunday after Pentecost",
+ "2027-09-07": {
+ "id": "tempora:Pent16-0:2:g",
+ "tempora": "Feria III after XVI Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-09-11": {
- "tempora": "Feria VI after XV Sunday after Pentecost",
+ "2027-09-08": {
+ "id": "sancti:09-08:2:w",
+ "tempora": "Feria IV after XVI Sunday after Pentecost",
+ "title": "Nativity of the Blessed Virgin Mary",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-09-09": {
+ "id": "tempora:Pent16-0:2:g",
+ "tempora": "Feria V after XVI Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-09-12": {
- "tempora": "Saturday after XV Sunday after Pentecost",
- "title": "Most Holy Name of Mary",
+ "2027-09-10": {
+ "id": "sancti:09-10:3:w",
+ "tempora": "Feria VI after XVI Sunday after Pentecost",
+ "title": "St. Nicholas of Tolentino",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-09-19": {
+ "2027-09-11": {
+ "id": "commune:C10t:4:w",
"tempora": "Saturday after XVI Sunday after Pentecost",
- "title": "St. Januarius & Companions",
- "rank": 3,
- "colour": "r"
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2026-09-21": {
+ "2027-09-12": {
+ "id": "tempora:Pent17-0:2:g",
+ "tempora": "",
+ "title": "XVII Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-09-13": {
+ "id": "tempora:Pent17-0:2:g",
"tempora": "Feria II after XVII Sunday after Pentecost",
- "title": "St. Matthew",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-09-14": {
+ "id": "sancti:09-14:2:r",
+ "tempora": "Feria III after XVII Sunday after Pentecost",
+ "title": "Exaltation of the Holy Cross",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-09-20": {
- "tempora": "",
- "title": "XVII Sunday after Pentecost",
+ "2027-09-15": {
+ "id": "sancti:09-15:2:w",
+ "tempora": "Feria IV after XVII Sunday after Pentecost",
+ "title": "Seven Sorrows of the Blessed Virgin Mary",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2026-09-05": {
- "tempora": "Saturday after XIV Sunday after Pentecost",
- "title": "St. Lawrence Justinian",
+ "2027-09-16": {
+ "id": "sancti:09-16:3:r",
+ "tempora": "Feria V after XVII Sunday after Pentecost",
+ "title": "Sts. Cornelius & Cyprian",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-09-18": {
- "tempora": "Feria VI after XVI Sunday after Pentecost",
- "title": "St. Joseph of Cupertino",
- "rank": 3,
- "colour": "w"
+ "2027-09-17": {
+ "id": "tempora:Pent17-0:2:g",
+ "tempora": "Feria VI after XVII Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-09-28": {
- "tempora": "Feria II after XVIII Sunday after Pentecost",
- "title": "St. Wenceslaus",
+ "2027-09-18": {
+ "id": "sancti:09-18:3:w",
+ "tempora": "Saturday after XVII Sunday after Pentecost",
+ "title": "St. Joseph of Cupertino",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-09-27": {
+ "2027-09-19": {
+ "id": "tempora:Pent18-0:2:g",
"tempora": "",
"title": "XVIII Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-09-25": {
- "tempora": "",
- "title": "Ember Friday of September",
- "rank": 2,
- "colour": "v"
+ "2027-09-20": {
+ "id": "tempora:Pent18-0:2:g",
+ "tempora": "Feria II after XVIII Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-09-29": {
+ "2027-09-21": {
+ "id": "sancti:09-21:2:r",
"tempora": "Feria III after XVIII Sunday after Pentecost",
- "title": "Dedication of St. Michael the Archangel",
- "rank": 1,
- "colour": "w"
- },
- "2026-09-15": {
- "tempora": "Feria III after XVI Sunday after Pentecost",
- "title": "Seven Sorrows of the Blessed Virgin Mary",
+ "title": "St. Matthew",
"rank": 2,
- "colour": "w"
- },
- "2026-09-17": {
- "tempora": "Feria V after XVI Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "colours": [
+ "r"
+ ]
},
- "2026-09-23": {
+ "2027-09-22": {
+ "id": "tempora:093-3:2:v",
"tempora": "",
"title": "Ember Wednesday of September",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-10-02": {
- "tempora": "Feria VI after XVIII Sunday after Pentecost",
- "title": "Holy Guardian Angels",
- "rank": 3,
- "colour": "w"
- },
- "2026-10-03": {
- "tempora": "Saturday after XVIII Sunday after Pentecost",
- "title": "St. Theresa of the Infant Jesus",
+ "2027-09-23": {
+ "id": "sancti:09-23:3:r",
+ "tempora": "Feria V after XVIII Sunday after Pentecost",
+ "title": "St. Linus",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-10-04": {
+ "2027-09-24": {
+ "id": "tempora:093-5:2:v",
"tempora": "",
- "title": "XIX Sunday after Pentecost",
+ "title": "Ember Friday of September",
"rank": 2,
- "colour": "g"
- },
- "2026-09-30": {
- "tempora": "Feria IV after XVIII Sunday after Pentecost",
- "title": "St. Jerome",
- "rank": 3,
- "colour": "w"
- },
- "2026-10-05": {
- "tempora": "Feria II after XIX Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "colours": [
+ "v"
+ ]
},
- "2026-09-24": {
- "tempora": "Feria V after XVII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2027-09-25": {
+ "id": "tempora:093-6:2:v",
+ "tempora": "",
+ "title": "Ember Saturday of September",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2026-10-07": {
- "tempora": "Feria IV after XIX Sunday after Pentecost",
- "title": "Our Lady of the Rosary",
+ "2027-09-26": {
+ "id": "tempora:Pent19-0:2:g",
+ "tempora": "",
+ "title": "XIX Sunday after Pentecost",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2026-10-09": {
- "tempora": "Feria VI after XIX Sunday after Pentecost",
- "title": "St. John Leonardi",
+ "2027-09-27": {
+ "id": "sancti:09-27:3:r",
+ "tempora": "Feria II after XIX Sunday after Pentecost",
+ "title": "Sts. Cosmas & Damian",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-10-06": {
+ "2027-09-28": {
+ "id": "sancti:09-28:3:r",
"tempora": "Feria III after XIX Sunday after Pentecost",
- "title": "St. Bruno",
+ "title": "St. Wenceslaus",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-10-10": {
- "tempora": "Saturday after XIX Sunday after Pentecost",
- "title": "St. Francis Borgia",
+ "2027-09-29": {
+ "id": "sancti:09-29:1:w",
+ "tempora": "Feria IV after XIX Sunday after Pentecost",
+ "title": "Dedication of St. Michael the Archangel",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-09-30": {
+ "id": "sancti:09-30:3:w",
+ "tempora": "Feria V after XIX Sunday after Pentecost",
+ "title": "St. Jerome",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-12": {
- "tempora": "Feria II after XX Sunday after Pentecost",
+ "2027-10-01": {
+ "id": "tempora:Pent19-0:2:g",
+ "tempora": "Feria VI after XIX Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-10-08": {
- "tempora": "Feria V after XIX Sunday after Pentecost",
- "title": "St. Bridget of Sweden",
+ "2027-10-02": {
+ "id": "sancti:10-02:3:w",
+ "tempora": "Saturday after XIX Sunday after Pentecost",
+ "title": "Holy Guardian Angels",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-11": {
+ "2027-10-03": {
+ "id": "tempora:Pent20-0:2:g",
"tempora": "",
"title": "XX Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-09-26": {
- "tempora": "",
- "title": "Ember Saturday of September",
- "rank": 2,
- "colour": "v"
- },
- "2026-10-16": {
- "tempora": "Feria VI after XX Sunday after Pentecost",
- "title": "St. Hedwig",
- "rank": 3,
- "colour": "w"
- },
- "2026-10-19": {
- "tempora": "Feria II after XXI Sunday after Pentecost",
- "title": "St. Peter of Alcantara",
+ "2027-10-04": {
+ "id": "sancti:10-04:3:w",
+ "tempora": "Feria II after XX Sunday after Pentecost",
+ "title": "St. Francis of Assisi",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-17": {
- "tempora": "Saturday after XX Sunday after Pentecost",
- "title": "St. Margaret Mary Alacoque",
- "rank": 3,
- "colour": "w"
+ "2027-10-05": {
+ "id": "tempora:Pent20-0:2:g",
+ "tempora": "Feria III after XX Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-10-14": {
+ "2027-10-06": {
+ "id": "sancti:10-06:3:w",
"tempora": "Feria IV after XX Sunday after Pentecost",
- "title": "St. Callistus I",
- "rank": 3,
- "colour": "r"
- },
- "2026-09-22": {
- "tempora": "Feria III after XVII Sunday after Pentecost",
- "title": "St. Thomas of Villanova",
+ "title": "St. Bruno",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-15": {
+ "2027-10-07": {
+ "id": "sancti:10-07:2:w",
"tempora": "Feria V after XX Sunday after Pentecost",
- "title": "St. Teresa of Avila",
- "rank": 3,
- "colour": "w"
+ "title": "Our Lady of the Rosary",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2026-09-16": {
- "tempora": "Feria IV after XVI Sunday after Pentecost",
- "title": "Sts. Cornelius & Cyprian",
+ "2027-10-08": {
+ "id": "sancti:10-08:3:w",
+ "tempora": "Feria VI after XX Sunday after Pentecost",
+ "title": "St. Bridget of Sweden",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-20": {
- "tempora": "Feria III after XXI Sunday after Pentecost",
- "title": "St. John Cantius",
+ "2027-10-09": {
+ "id": "sancti:10-09:3:w",
+ "tempora": "Saturday after XX Sunday after Pentecost",
+ "title": "St. John Leonardi",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-18": {
+ "2027-10-10": {
+ "id": "tempora:Pent21-0:2:g",
"tempora": "",
"title": "XXI Sunday after Pentecost",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-10-27": {
- "tempora": "Feria III after XXII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2027-10-11": {
+ "id": "sancti:10-11:2:w",
+ "tempora": "Feria II after XXI Sunday after Pentecost",
+ "title": "Maternity of the Blessed Virgin Mary",
+ "rank": 2,
+ "colours": [
+ "w"
+ ]
},
- "2026-10-01": {
- "tempora": "Feria V after XVIII Sunday after Pentecost",
+ "2027-10-12": {
+ "id": "tempora:Pent21-0:2:g",
+ "tempora": "Feria III after XXI Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-10-23": {
- "tempora": "Feria VI after XXI Sunday after Pentecost",
- "title": "St. Anthony Mary Claret",
+ "2027-10-13": {
+ "id": "sancti:10-13:3:w",
+ "tempora": "Feria IV after XXI Sunday after Pentecost",
+ "title": "St. Edward",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-22": {
+ "2027-10-14": {
+ "id": "sancti:10-14:3:r",
"tempora": "Feria V after XXI Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "title": "St. Callistus I",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2026-10-24": {
+ "2027-10-15": {
+ "id": "sancti:10-15:3:w",
+ "tempora": "Feria VI after XXI Sunday after Pentecost",
+ "title": "St. Teresa of Avila",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-10-16": {
+ "id": "sancti:10-16:3:w",
"tempora": "Saturday after XXI Sunday after Pentecost",
- "title": "St. Raphael the Archangel",
+ "title": "St. Hedwig",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-25": {
- "tempora": "XXII Sunday after Pentecost",
- "title": "Christ the King",
- "rank": 1,
- "colour": "w"
+ "2027-10-17": {
+ "id": "tempora:Pent22-0:2:g",
+ "tempora": "",
+ "title": "XXII Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2026-10-28": {
- "tempora": "Feria IV after XXII Sunday after Pentecost",
- "title": "Sts. Simon & Jude",
+ "2027-10-18": {
+ "id": "sancti:10-18:2:r",
+ "tempora": "Feria II after XXII Sunday after Pentecost",
+ "title": "St. Luke the Evangelist",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-10-13": {
- "tempora": "Feria III after XX Sunday after Pentecost",
- "title": "St. Edward",
+ "2027-10-19": {
+ "id": "sancti:10-19:3:w",
+ "tempora": "Feria III after XXII Sunday after Pentecost",
+ "title": "St. Peter of Alcantara",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-30": {
+ "2027-10-20": {
+ "id": "sancti:10-20:3:w",
+ "tempora": "Feria IV after XXII Sunday after Pentecost",
+ "title": "St. John Cantius",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-10-21": {
+ "id": "tempora:Pent22-0:2:g",
+ "tempora": "Feria V after XXII Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-10-22": {
+ "id": "tempora:Pent22-0:2:g",
"tempora": "Feria VI after XXII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-10-29": {
- "tempora": "Feria V after XXII Sunday after Pentecost",
+ "2027-10-23": {
+ "id": "sancti:10-23r:3:w",
+ "tempora": "Saturday after XXII Sunday after Pentecost",
+ "title": "St. Anthony Mary Claret",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-10-24": {
+ "id": "tempora:Pent23-0:2:g",
+ "tempora": "",
+ "title": "XXIII Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-10-25": {
+ "id": "tempora:Pent23-0:2:g",
+ "tempora": "Feria II after XXIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-11-03": {
+ "2027-10-26": {
+ "id": "tempora:Pent23-0:2:g",
"tempora": "Feria III after XXIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-11-05": {
+ "2027-10-27": {
+ "id": "tempora:Pent23-0:2:g",
+ "tempora": "Feria IV after XXIII Sunday after Pentecost",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
+ },
+ "2027-10-28": {
+ "id": "sancti:10-28:2:r",
"tempora": "Feria V after XXIII Sunday after Pentecost",
+ "title": "Sts. Simon & Jude",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-10-29": {
+ "id": "tempora:Pent23-0:2:g",
+ "tempora": "Feria VI after XXIII Sunday after Pentecost",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-11-01": {
- "tempora": "XXIII Sunday after Pentecost",
+ "2027-10-30": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after XXIII Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-10-31": {
+ "id": "sancti:10-DU:1:w",
+ "tempora": "IV Sunday after Epiphany",
+ "title": "Christ the King",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-11-01": {
+ "id": "sancti:11-01:1:w",
+ "tempora": "Feria II after IV Sunday after Epiphany",
"title": "All Saints",
"rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-10-26": {
- "tempora": "Feria II after XXII Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2027-11-02": {
+ "id": "sancti:11-02m1:1:b",
+ "tempora": "Feria III after IV Sunday after Epiphany",
+ "title": "Commemoration of All Souls",
+ "rank": 1,
+ "colours": [
+ "b"
+ ]
},
- "2026-11-06": {
- "tempora": "Feria VI after XXIII Sunday after Pentecost",
+ "2027-11-03": {
+ "id": "tempora:Epi4-0:2:g",
+ "tempora": "Feria IV after IV Sunday after Epiphany",
"title": "Feria",
"rank": 4,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-11-14": {
- "tempora": "Saturday after V Sunday after Epiphany",
- "title": "St. Josaphat",
+ "2027-11-04": {
+ "id": "sancti:11-04:3:w",
+ "tempora": "Feria V after IV Sunday after Epiphany",
+ "title": "St. Charles Borromeo",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-12": {
- "tempora": "Feria V after V Sunday after Epiphany",
- "title": "St. Martin I",
- "rank": 3,
- "colour": "r"
+ "2027-11-05": {
+ "id": "tempora:Epi4-0:2:g",
+ "tempora": "Feria VI after IV Sunday after Epiphany",
+ "title": "Feria",
+ "rank": 4,
+ "colours": [
+ "g"
+ ]
},
- "2026-11-08": {
+ "2027-11-06": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after IV Sunday after Epiphany",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-11-07": {
+ "id": "tempora:Epi5-0:2:g",
"tempora": "",
"title": "V Sunday after Epiphany",
"rank": 2,
- "colour": "g"
- },
- "2026-11-16": {
- "tempora": "Feria II after VI Sunday after Epiphany",
- "title": "St. Gertrude the Great",
- "rank": 3,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2026-10-31": {
- "tempora": "Saturday after XXII Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "2027-11-08": {
+ "id": "tempora:Epi5-0:2:g",
+ "tempora": "Feria II after V Sunday after Epiphany",
+ "title": "Feria",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "g"
+ ]
},
- "2026-11-15": {
- "tempora": "",
- "title": "VI Sunday after Epiphany",
+ "2027-11-09": {
+ "id": "sancti:11-09:2:w",
+ "tempora": "Feria III after V Sunday after Epiphany",
+ "title": "Dedication of the Archbasilica of Our Holy Savior",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-11": {
+ "2027-11-10": {
+ "id": "sancti:11-10:3:w",
"tempora": "Feria IV after V Sunday after Epiphany",
+ "title": "St. Andrew Avellino",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-11-11": {
+ "id": "sancti:11-11:3:w",
+ "tempora": "Feria V after V Sunday after Epiphany",
"title": "St. Martin of Tours",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-13": {
+ "2027-11-12": {
+ "id": "sancti:11-12:3:r",
"tempora": "Feria VI after V Sunday after Epiphany",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
- },
- "2026-11-04": {
- "tempora": "Feria IV after XXIII Sunday after Pentecost",
- "title": "St. Charles Borromeo",
+ "title": "St. Martin I",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-10-21": {
- "tempora": "Feria IV after XXI Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
- },
- "2026-11-07": {
- "tempora": "Saturday after XXIII Sunday after Pentecost",
+ "2027-11-13": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after V Sunday after Epiphany",
"title": "V Mass of the B. V. M. – Salve, Sancta Parens",
"rank": 4,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-22": {
+ "2027-11-14": {
+ "id": "tempora:Epi6-0:2:g",
"tempora": "",
- "title": "XXIV Sunday after Pentecost",
+ "title": "VI Sunday after Epiphany",
"rank": 2,
- "colour": "g"
+ "colours": [
+ "g"
+ ]
},
- "2026-11-10": {
- "tempora": "Feria III after V Sunday after Epiphany",
- "title": "St. Andrew Avellino",
+ "2027-11-15": {
+ "id": "sancti:11-15:3:w",
+ "tempora": "Feria II after VI Sunday after Epiphany",
+ "title": "St. Albert the Great",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-20": {
- "tempora": "Feria VI after VI Sunday after Epiphany",
- "title": "St. Felix of Valois",
+ "2027-11-16": {
+ "id": "sancti:11-16:3:w",
+ "tempora": "Feria III after VI Sunday after Epiphany",
+ "title": "St. Gertrude the Great",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-19": {
+ "2027-11-17": {
+ "id": "sancti:11-17:3:w",
+ "tempora": "Feria IV after VI Sunday after Epiphany",
+ "title": "St. Gregory the Wonderworker",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-11-18": {
+ "id": "sancti:11-18r:3:w",
"tempora": "Feria V after VI Sunday after Epiphany",
+ "title": "Dedication of the Basilicas of Sts. Peter & Paul",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
+ },
+ "2027-11-19": {
+ "id": "sancti:11-19:3:w",
+ "tempora": "Feria VI after VI Sunday after Epiphany",
"title": "St. Elizabeth of Hungary",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-17": {
- "tempora": "Feria III after VI Sunday after Epiphany",
- "title": "St. Gregory the Wonderworker",
+ "2027-11-20": {
+ "id": "sancti:11-20:3:w",
+ "tempora": "Saturday after VI Sunday after Epiphany",
+ "title": "St. Felix of Valois",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-27": {
- "tempora": "Feria VI after XXIV Sunday after Pentecost",
- "title": "Feria",
- "rank": 4,
- "colour": "g"
+ "2027-11-21": {
+ "id": "tempora:Pent24-0:2:g",
+ "tempora": "",
+ "title": "XXIV Sunday after Pentecost",
+ "rank": 2,
+ "colours": [
+ "g"
+ ]
},
- "2026-11-23": {
+ "2027-11-22": {
+ "id": "sancti:11-22:3:r",
"tempora": "Feria II after XXIV Sunday after Pentecost",
- "title": "St. Clement I",
+ "title": "St. Cecilia",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "r"
+ ]
},
- "2026-11-09": {
- "tempora": "Feria II after V Sunday after Epiphany",
- "title": "Dedication of the Archbasilica of Our Holy Savior",
- "rank": 2,
- "colour": "w"
- },
- "2026-11-30": {
- "tempora": "Feria II after I Sunday of Advent",
- "title": "St. Andrew",
- "rank": 2,
- "colour": "r"
+ "2027-11-23": {
+ "id": "sancti:11-23:3:r",
+ "tempora": "Feria III after XXIV Sunday after Pentecost",
+ "title": "St. Clement I",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
},
- "2026-11-29": {
- "tempora": "",
- "title": "I Sunday of Advent",
- "rank": 1,
- "colour": "v"
+ "2027-11-24": {
+ "id": "sancti:11-24:3:w",
+ "tempora": "Feria IV after XXIV Sunday after Pentecost",
+ "title": "St. John of the Cross",
+ "rank": 3,
+ "colours": [
+ "w"
+ ]
},
- "2026-12-01": {
- "tempora": "",
- "title": "Feria III after I Sunday of Advent",
+ "2027-11-25": {
+ "id": "sancti:11-25:3:r",
+ "tempora": "Feria V after XXIV Sunday after Pentecost",
+ "title": "St. Catherine of Alexandria",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "r"
+ ]
},
- "2026-11-21": {
- "tempora": "Saturday after VI Sunday after Epiphany",
- "title": "Presentation of the Blessed Virgin Mary",
+ "2027-11-26": {
+ "id": "sancti:11-26:3:w",
+ "tempora": "Feria VI after XXIV Sunday after Pentecost",
+ "title": "St. Sylvester",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-11-02": {
- "tempora": "Feria II after XXIII Sunday after Pentecost",
- "title": "Commemoration of All Souls",
- "rank": 1,
- "colour": "b"
+ "2027-11-27": {
+ "id": "commune:C10t:4:w",
+ "tempora": "Saturday after XXIV Sunday after Pentecost",
+ "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
+ "rank": 4,
+ "colours": [
+ "w"
+ ]
},
- "2026-12-06": {
+ "2027-11-28": {
+ "id": "tempora:Adv1-0:1:v",
"tempora": "",
- "title": "II Sunday of Advent",
+ "title": "I Sunday of Advent",
"rank": 1,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-11-25": {
- "tempora": "Feria IV after XXIV Sunday after Pentecost",
- "title": "St. Catherine of Alexandria",
+ "2027-11-29": {
+ "id": "tempora:Adv1-0:1:v",
+ "tempora": "",
+ "title": "Feria II after I Sunday of Advent",
"rank": 3,
- "colour": "r"
+ "colours": [
+ "v"
+ ]
},
- "2026-11-24": {
- "tempora": "Feria III after XXIV Sunday after Pentecost",
- "title": "St. John of the Cross",
+ "2027-11-30": {
+ "id": "sancti:11-30:2:r",
+ "tempora": "Feria III after I Sunday of Advent",
+ "title": "St. Andrew",
+ "rank": 2,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-12-01": {
+ "id": "tempora:Adv1-0:1:v",
+ "tempora": "",
+ "title": "Feria IV after I Sunday of Advent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-12-02": {
- "tempora": "Feria IV after I Sunday of Advent",
+ "2027-12-02": {
+ "id": "sancti:12-02:3:r",
+ "tempora": "Feria V after I Sunday of Advent",
"title": "St. Vivian",
"rank": 3,
- "colour": "r"
- },
- "2026-11-28": {
- "tempora": "Saturday after XXIV Sunday after Pentecost",
- "title": "V Mass of the B. V. M. – Salve, Sancta Parens",
- "rank": 4,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-11-18": {
- "tempora": "Feria IV after VI Sunday after Epiphany",
- "title": "Dedication of the Basilicas of Sts. Peter & Paul",
+ "2027-12-03": {
+ "id": "sancti:12-03:3:w",
+ "tempora": "Feria VI after I Sunday of Advent",
+ "title": "St. Francis Xavier",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-03": {
- "tempora": "Feria V after I Sunday of Advent",
- "title": "St. Francis Xavier",
+ "2027-12-04": {
+ "id": "sancti:12-04:3:w",
+ "tempora": "Sabbato after I Sunday of Advent",
+ "title": "St. Peter Chrysologus",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-09": {
+ "2027-12-05": {
+ "id": "tempora:Adv2-0:1:v",
"tempora": "",
- "title": "Feria IV after II Sunday of Advent",
+ "title": "II Sunday of Advent",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
+ },
+ "2027-12-06": {
+ "id": "sancti:12-06:3:w",
+ "tempora": "Feria II after II Sunday of Advent",
+ "title": "St. Nicholas",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-08": {
+ "2027-12-07": {
+ "id": "sancti:12-07:3:w",
"tempora": "Feria III after II Sunday of Advent",
- "title": "Immaculate Conception of the Blessed Virgin Mary",
- "rank": 1,
- "colour": "w"
- },
- "2026-12-12": {
- "tempora": "",
- "title": "Sabbato after II Sunday of Advent",
+ "title": "St. Ambrose",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-13": {
- "tempora": "",
- "title": "III Sunday of Advent",
+ "2027-12-08": {
+ "id": "sancti:12-08:1:w",
+ "tempora": "Feria IV after II Sunday of Advent",
+ "title": "Immaculate Conception of the Blessed Virgin Mary",
"rank": 1,
- "colour": "pv"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-05": {
+ "2027-12-09": {
+ "id": "tempora:Adv2-0:1:v",
"tempora": "",
- "title": "Sabbato after I Sunday of Advent",
- "rank": 3,
- "colour": "v"
- },
- "2026-12-04": {
- "tempora": "Feria VI after I Sunday of Advent",
- "title": "St. Peter Chrysologus",
+ "title": "Feria V after II Sunday of Advent",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-12-14": {
+ "2027-12-10": {
+ "id": "tempora:Adv2-0:1:v",
"tempora": "",
- "title": "Feria II after III Sunday of Advent",
+ "title": "Feria VI after II Sunday of Advent",
"rank": 3,
- "colour": "pv"
+ "colours": [
+ "v"
+ ]
},
- "2026-12-11": {
- "tempora": "Feria VI after II Sunday of Advent",
+ "2027-12-11": {
+ "id": "sancti:12-11:3:w",
+ "tempora": "Sabbato after II Sunday of Advent",
"title": "St. Damasus I",
"rank": 3,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-17": {
+ "2027-12-12": {
+ "id": "tempora:Adv3-0:1:pv",
"tempora": "",
- "title": "Feria V after III Sunday of Advent",
- "rank": 2,
- "colour": "pv"
+ "title": "III Sunday of Advent",
+ "rank": 1,
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2026-12-15": {
+ "2027-12-13": {
+ "id": "sancti:12-13r:3:r",
+ "tempora": "Feria II after III Sunday of Advent",
+ "title": "St. Lucy",
+ "rank": 3,
+ "colours": [
+ "r"
+ ]
+ },
+ "2027-12-14": {
+ "id": "tempora:Adv3-0:1:pv",
"tempora": "",
"title": "Feria III after III Sunday of Advent",
"rank": 3,
- "colour": "pv"
+ "colours": [
+ "p",
+ "v"
+ ]
},
- "2026-12-10": {
+ "2027-12-15": {
+ "id": "tempora:Adv3-3:2:v",
"tempora": "",
- "title": "Feria V after II Sunday of Advent",
+ "title": "Ember Wednesday of Advent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
+ },
+ "2027-12-16": {
+ "id": "sancti:12-16:3:r",
+ "tempora": "Feria V after III Sunday of Advent",
+ "title": "St. Eusebius",
"rank": 3,
- "colour": "v"
+ "colours": [
+ "r"
+ ]
},
- "2026-12-18": {
+ "2027-12-17": {
+ "id": "tempora:Adv3-5:2:v",
"tempora": "",
"title": "Ember Friday of Advent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-11-26": {
- "tempora": "Feria V after XXIV Sunday after Pentecost",
- "title": "St. Sylvester",
- "rank": 3,
- "colour": "w"
+ "2027-12-18": {
+ "id": "tempora:Adv3-6:2:v",
+ "tempora": "",
+ "title": "Ember Saturday of Advent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2026-12-20": {
+ "2027-12-19": {
+ "id": "tempora:Adv4-0:1:v",
"tempora": "",
"title": "IV Sunday of Advent",
"rank": 1,
- "colour": "v"
- },
- "2026-12-07": {
- "tempora": "Feria II after II Sunday of Advent",
- "title": "St. Ambrose",
- "rank": 3,
- "colour": "w"
+ "colours": [
+ "v"
+ ]
},
- "2026-12-24": {
+ "2027-12-20": {
+ "id": "tempora:Adv4-0:1:v",
"tempora": "",
- "title": "Vigil of Christmas",
- "rank": 1,
- "colour": "v"
+ "title": "Feria II after IV Sunday of Advent",
+ "rank": 2,
+ "colours": [
+ "v"
+ ]
},
- "2026-12-22": {
- "tempora": "",
- "title": "Feria III after IV Sunday of Advent",
+ "2027-12-21": {
+ "id": "sancti:12-21:2:r",
+ "tempora": "Feria III after IV Sunday of Advent",
+ "title": "St. Thomas",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "r"
+ ]
},
- "2026-12-23": {
+ "2027-12-22": {
+ "id": "tempora:Adv4-0:1:v",
"tempora": "",
"title": "Feria IV after IV Sunday of Advent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-12-16": {
+ "2027-12-23": {
+ "id": "tempora:Adv4-0:1:v",
"tempora": "",
- "title": "Ember Wednesday of Advent",
+ "title": "Feria V after IV Sunday of Advent",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "v"
+ ]
},
- "2026-12-21": {
- "tempora": "Feria II after IV Sunday of Advent",
- "title": "St. Thomas",
- "rank": 2,
- "colour": "r"
+ "2027-12-24": {
+ "id": "sancti:12-24:1:v",
+ "tempora": "",
+ "title": "Vigil of Christmas",
+ "rank": 1,
+ "colours": [
+ "v"
+ ]
},
- "2026-12-30": {
+ "2027-12-25": {
+ "id": "sancti:12-25m1:1:w",
"tempora": "",
- "title": "Feria in the Octave of Christmas",
- "rank": 2,
- "colour": "w"
+ "title": "The Nativity of Our Lord",
+ "rank": 1,
+ "colours": [
+ "w"
+ ]
},
- "2026-12-26": {
+ "2027-12-26": {
+ "id": "tempora:Nat1-0:2:w",
"tempora": "",
- "title": "St. Stephen",
+ "title": "Sunday in the Octave of Christmas",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-28": {
+ "2027-12-27": {
+ "id": "sancti:12-27:2:w",
"tempora": "",
- "title": "Holy Innocents",
+ "title": "St. John the Evangelist",
"rank": 2,
- "colour": "r"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-29": {
+ "2027-12-28": {
+ "id": "sancti:12-28:2:r",
"tempora": "",
- "title": "Feria in the Octave of Christmas",
+ "title": "Holy Innocents",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "r"
+ ]
},
- "2026-12-31": {
+ "2027-12-29": {
+ "id": "tempora:Nat1-1:2:w",
"tempora": "",
"title": "Feria in the Octave of Christmas",
"rank": 2,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-19": {
+ "2027-12-30": {
+ "id": "tempora:Nat1-1:2:w",
"tempora": "",
- "title": "Ember Saturday of Advent",
+ "title": "Feria in the Octave of Christmas",
"rank": 2,
- "colour": "v"
+ "colours": [
+ "w"
+ ]
},
- "2026-12-27": {
+ "2027-12-31": {
+ "id": "tempora:Nat1-1:2:w",
"tempora": "",
- "title": "Sunday in the Octave of Christmas",
+ "title": "Feria in the Octave of Christmas",
"rank": 2,
- "colour": "w"
- },
- "2026-12-25": {
- "tempora": "",
- "title": "The Nativity of Our Lord",
- "rank": 1,
- "colour": "w"
+ "colours": [
+ "w"
+ ]
}
}
diff --git a/internal/calendar/types.go b/internal/calendar/types.go
index 298e49d..6d828f1 100644
--- a/internal/calendar/types.go
+++ b/internal/calendar/types.go
@@ -17,6 +17,18 @@ const (
RankFeast Rank = "feast" //
RankSolemnity Rank = "solemnity" //
+ // RankSunday is a DISPLAY-ONLY rank: in the 1969 Universal Norms' Table of
+ // Liturgical Days, Sunday is its own category, never a solemnity (Sundays
+ // of Christmas Time/Ordinary Time are band 6, below feasts of the Lord;
+ // Sundays of Advent/Lent/Easter are band 2, above solemnities -- a flat
+ // Rank cannot express both bands, and this constant does not try to).
+ // Nothing in this package ever assigns it to Celebration.Rank, and it MUST
+ // NOT be added to ofRankOrder below: ofRankOrder's default case (0,
+ // ferial-level) would make every Sunday lose every precedence contest.
+ // Callers that want it derive it themselves from the already-computed,
+ // unchanged Celebration (see internal/readings/offline.go's displayRank).
+ RankSunday Rank = "sunday"
+
// Extraordinary Form ranks (1960 Code of Rubrics): I-IV class + commemoration.
RankClass1 Rank = "class-1"
RankClass2 Rank = "class-2"
diff --git a/internal/i18n/golden_test.go b/internal/i18n/golden_test.go
index 5a29097..d06d63b 100644
--- a/internal/i18n/golden_test.go
+++ b/internal/i18n/golden_test.go
@@ -22,6 +22,15 @@ var enUI = UI{
"drugie_czytanie": "2nd reading",
"aklamacja": "Acclamation",
"ewangelia": "Gospel",
+ "epistola": "Lesson",
+ "evangelium": "Gospel",
+ },
+ Rank: map[string]string{
+ "solemnity": "solemnity", "feast": "feast", "memorial": "memorial",
+ "optional": "optional memorial", "ferial": "feria", "sunday": "Sunday",
+ "class-1": "I class", "class-2": "II class",
+ "class-3": "III class", "class-4": "IV class",
+ "commemoration": "commemoration",
},
FooterKeys: "tab/⇧tab version ←/→ day d date j/k scroll space/b page g/G top/bottom q quit",
Loading: "loading…",
@@ -110,6 +119,16 @@ var plUI = UI{
"drugie_czytanie": "2. czytanie",
"aklamacja": "Aklamacja",
"ewangelia": "Ewangelia",
+ "epistola": "Lekcja",
+ "evangelium": "Ewangelia",
+ },
+ Rank: map[string]string{
+ "solemnity": "uroczystość", "feast": "święto",
+ "memorial": "wspomnienie obowiązkowe", "optional": "wspomnienie dowolne",
+ "ferial": "dzień powszedni", "sunday": "niedziela",
+ "class-1": "I klasy", "class-2": "II klasy",
+ "class-3": "III klasy", "class-4": "IV klasy",
+ "commemoration": "komemoracja",
},
FooterKeys: "tab/⇧tab wersja ←/→ dzień d data j/k przewiń spacja/b strona g/G góra/dół q wyjście",
Loading: "ładowanie…",
diff --git a/internal/i18n/i18n.go b/internal/i18n/i18n.go
index fece4a8..6785705 100644
--- a/internal/i18n/i18n.go
+++ b/internal/i18n/i18n.go
@@ -15,14 +15,24 @@ type UI struct {
// version code (wuj, vul, grb, drb).
Version map[string]string
- // PartLabel names each modern-lectionary section's heading label word,
- // keyed by liturgy.Section.PartID (pierwsze_czytanie, psalm,
+ // PartLabel names each lectionary section's heading label word, keyed by
+ // liturgy.Section.PartID -- the modern IDs (pierwsze_czytanie, psalm,
// drugie_czytanie, aklamacja, ewangelia). The pl entries are the exact
// prefixes niedziela.pl's scraped headings carry, used by
// render.LocalizeHeading to recognise and swap the label word while
// keeping the citation untouched.
PartLabel map[string]string
+ // Rank names each liturgical rank, keyed by the string form of
+ // calendar.Rank. Two vocabularies share the map: the Ordinary Form's
+ // ferial/optional/memorial/feast/solemnity and the 1962 form's
+ // class-1..class-4/commemoration. The English values are hand-synced to
+ // agree with internal/cli/liturgy.go's own rankLabel() switch, but the
+ // CLI does not read this map -- it renders rank independently, and the
+ // two are kept in sync by hand. The actual consumers of this map are the
+ // dlectio app's day banner and calendar rows.
+ Rank map[string]string
+
// TUI keybar and status messages. NoReadingsFor and ErrorPrefix each
// carry their own single trailing space (the TUI concatenates a date or
// error string directly onto them, no separator added at the call site).
diff --git a/internal/i18n/lang/en.ini b/internal/i18n/lang/en.ini
index 8444a4d..4abd5d1 100644
--- a/internal/i18n/lang/en.ini
+++ b/internal/i18n/lang/en.ini
@@ -4,9 +4,22 @@ version.vul = Vulgate (Latin)
version.wuj = Wujek (Polish)
part_label.aklamacja = Acclamation
part_label.drugie_czytanie = 2nd reading
+part_label.epistola = Lesson
+part_label.evangelium = Gospel
part_label.ewangelia = Gospel
part_label.pierwsze_czytanie = 1st reading
part_label.psalm = Psalm
+rank.class-1 = I class
+rank.class-2 = II class
+rank.class-3 = III class
+rank.class-4 = IV class
+rank.commemoration = commemoration
+rank.feast = feast
+rank.ferial = feria
+rank.memorial = memorial
+rank.optional = optional memorial
+rank.solemnity = solemnity
+rank.sunday = Sunday
footer_keys = tab/⇧tab version ←/→ day d date j/k scroll space/b page g/G top/bottom q quit
loading = loading…
no_readings_for = "no readings for "
diff --git a/internal/i18n/lang/pl.ini b/internal/i18n/lang/pl.ini
index 305c4c9..4ae4ebf 100644
--- a/internal/i18n/lang/pl.ini
+++ b/internal/i18n/lang/pl.ini
@@ -4,9 +4,22 @@ version.vul = Wulgata (lac.)
version.wuj = Wujek (pol.)
part_label.aklamacja = Aklamacja
part_label.drugie_czytanie = 2. czytanie
+part_label.epistola = Lekcja
+part_label.evangelium = Ewangelia
part_label.ewangelia = Ewangelia
part_label.pierwsze_czytanie = 1. czytanie
part_label.psalm = Psalm
+rank.class-1 = I klasy
+rank.class-2 = II klasy
+rank.class-3 = III klasy
+rank.class-4 = IV klasy
+rank.commemoration = komemoracja
+rank.feast = święto
+rank.ferial = dzień powszedni
+rank.memorial = wspomnienie obowiązkowe
+rank.optional = wspomnienie dowolne
+rank.solemnity = uroczystość
+rank.sunday = niedziela
footer_keys = tab/⇧tab wersja ←/→ dzień d data j/k przewiń spacja/b strona g/G góra/dół q wyjście
loading = ładowanie…
no_readings_for = "brak czytań na "
diff --git a/internal/i18n/vocab_test.go b/internal/i18n/vocab_test.go
new file mode 100644
index 0000000..d0fd603
--- /dev/null
+++ b/internal/i18n/vocab_test.go
@@ -0,0 +1,98 @@
+package i18n
+
+import (
+ "testing"
+
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+// Every rank the calendar can emit must have a word in every shipped
+// language. This is the guard against adding a constant later and silently
+// rendering a raw key like "class-2" in the UI.
+func TestVocabularyCoversEveryConstant(t *testing.T) {
+ ranks := []calendar.Rank{
+ calendar.RankFerial, calendar.RankOptional, calendar.RankMemorial,
+ calendar.RankFeast, calendar.RankSolemnity, calendar.RankSunday,
+ calendar.RankClass1, calendar.RankClass2, calendar.RankClass3,
+ calendar.RankClass4, calendar.RankCommemoration,
+ }
+ for _, lang := range []string{"en", "pl"} {
+ ui := Get(lang)
+ for _, r := range ranks {
+ if ui.Rank[string(r)] == "" {
+ t.Errorf("%s: no rank word for %q", lang, r)
+ }
+ }
+ }
+}
+
+// The English rank words must not change: lectio's CLI has printed these since
+// before the table existed, and English users' output should be untouched.
+func TestEnglishRankWordingUnchanged(t *testing.T) {
+ want := map[string]string{
+ "solemnity": "solemnity", "feast": "feast", "memorial": "memorial",
+ "optional": "optional memorial", "ferial": "feria", "sunday": "Sunday",
+ "class-1": "I class", "class-2": "II class",
+ "class-3": "III class", "class-4": "IV class",
+ "commemoration": "commemoration",
+ }
+ ui := Get("en")
+ for k, v := range want {
+ if got := ui.Rank[k]; got != v {
+ t.Errorf("rank[%q] = %q, want %q", k, got, v)
+ }
+ }
+}
+
+// The Polish rank words must be exactly this spelling, diacritics included:
+// TestVocabularyCoversEveryConstant only checks non-emptiness, so a typo
+// (e.g. "uroczystosc" for "uroczystość", or a wrong diacritic) would pass
+// silently without this pin. Polish correctness is the whole point of this
+// project.
+func TestPolishRankWordingUnchanged(t *testing.T) {
+ want := map[string]string{
+ "solemnity": "uroczystość", "feast": "święto",
+ "memorial": "wspomnienie obowiązkowe", "optional": "wspomnienie dowolne",
+ "ferial": "dzień powszedni", "sunday": "niedziela",
+ "class-1": "I klasy", "class-2": "II klasy",
+ "class-3": "III klasy", "class-4": "IV klasy",
+ "commemoration": "komemoracja",
+ }
+ ui := Get("pl")
+ for k, v := range want {
+ if got := ui.Rank[k]; got != v {
+ t.Errorf("rank[%q] = %q, want %q", k, got, v)
+ }
+ }
+}
+
+// The Polish part labels must be exactly this spelling, diacritics included,
+// and no others. This asserts against plUI directly, NOT i18n.Get("pl"):
+// Get overlays the embedded English baseline first and then the Polish file
+// on top (lang.go's Get), so a label present in English and missing only
+// from plUI.PartLabel would resolve through Get("pl") to the identical
+// English word rather than "" -- invisible to
+// internal/readings/partids_test.go's TestEveryPartIDHasLabels, which only
+// checks Get(lang) for non-emptiness. plUI is the unmerged golden source
+// with no fallback, so a missing entry is genuinely absent there, which is
+// the only way to catch this. The exact key-count check guards against a
+// future addition to plUI.PartLabel slipping in unpinned.
+func TestPolishPartLabelWordingUnchanged(t *testing.T) {
+ want := map[string]string{
+ "pierwsze_czytanie": "1. czytanie",
+ "psalm": "Psalm",
+ "drugie_czytanie": "2. czytanie",
+ "aklamacja": "Aklamacja",
+ "ewangelia": "Ewangelia",
+ "epistola": "Lekcja",
+ "evangelium": "Ewangelia",
+ }
+ if len(plUI.PartLabel) != len(want) {
+ t.Fatalf("plUI.PartLabel has %d keys, want %d: %v", len(plUI.PartLabel), len(want), plUI.PartLabel)
+ }
+ for k, v := range want {
+ if got, ok := plUI.PartLabel[k]; !ok || got != v {
+ t.Errorf("plUI.PartLabel[%q] = %q (present=%v), want %q", k, got, ok, v)
+ }
+ }
+}
diff --git a/internal/liturgy/section.go b/internal/liturgy/section.go
index dcca7b5..baf2b9d 100644
--- a/internal/liturgy/section.go
+++ b/internal/liturgy/section.go
@@ -33,4 +33,13 @@ type DayInfo struct {
// Colour is the normalized liturgical colour: "white", "green",
// "violet", "red", "rose", or "" when unknown/unmapped.
Colour string
+ // Rank is the normalized liturgical rank of the observed celebration:
+ // the Ordinary Form's "ferial", "optional", "memorial", "feast",
+ // "solemnity", or the 1962 form's "class-1".."class-4",
+ // "commemoration". The engine always supplies a rank: an unset or
+ // unrecognised rank on a sanctoral celebration defaults to "ferial"
+ // (calendar.buildCelebration), and every temporal-day constructor sets
+ // one explicitly. Consumers must not treat an empty Rank as meaning "no
+ // celebration".
+ Rank string
}
diff --git a/internal/readings/offline.go b/internal/readings/offline.go
index d0b8942..b2c16df 100644
--- a/internal/readings/offline.go
+++ b/internal/readings/offline.go
@@ -8,6 +8,7 @@ import (
"github.com/lukaszkasprzak/lectio/internal/caldata"
"github.com/lukaszkasprzak/lectio/internal/calendar"
"github.com/lukaszkasprzak/lectio/internal/config"
+ "github.com/lukaszkasprzak/lectio/internal/i18n"
"github.com/lukaszkasprzak/lectio/internal/liturgy"
"github.com/lukaszkasprzak/lectio/internal/naming"
)
@@ -60,24 +61,68 @@ var ofPart = map[string]struct{ id, heading string }{
"gospel": {"ewangelia", "Ewangelia"},
}
-// efPartHeading gives the traditional (1962) section's heading per UI language;
-// the EF has only an epistle/lesson and a gospel. Unlike the OF headings these
-// are not translated downstream, so they are set in the target language here.
+// efPartHeading gives the traditional (1962) section's ID and heading per UI
+// language; the EF has only an epistle/lesson and a gospel. The label words are
+// i18n data, like the modern ones. Unlike the OF headings these are not
+// translated downstream (render.LocalizeHeading only handles modern IDs), so
+// they are resolved in the target language here.
func efPartHeading(part, lang string) (id, heading string) {
- pl := lang == "pl"
switch part {
case "first":
- if pl {
- return "epistola", "Lekcja"
- }
- return "epistola", "Lesson"
+ id = "epistola"
case "gospel":
- if pl {
- return "evangelium", "Ewangelia"
- }
- return "evangelium", "Gospel"
+ id = "evangelium"
+ default:
+ return "", ""
+ }
+ heading = i18n.Get(lang).PartLabel[id]
+ if heading == "" {
+ heading = id
+ }
+ return id, heading
+}
+
+// ofPartOrder and efPartOrder are the display orders of each lectionary's
+// sections. They are the single source of truth for which part IDs exist.
+//
+// ofPartOrder's five modern IDs must remain the same *set* as
+// internal/render/render.go's modernPartOrder, which lists them in a
+// different, deliberate order (prefix-match determinism for
+// render.LocalizeHeading, unrelated to display order). Nothing enforces
+// that agreement mechanically -- internal/readings and internal/render do
+// not import each other (adding a cross-package test would create a new
+// dependency edge that does not exist today) -- so if a sixth modern part
+// is ever added here, add it to modernPartOrder too, by hand.
+var (
+ ofPartOrder = []string{"pierwsze_czytanie", "psalm", "drugie_czytanie", "aklamacja", "ewangelia"}
+ efPartOrder = []string{"epistola", "evangelium"}
+)
+
+// ofEmittedPartOrder is the subset of ofPartOrder the offline engine can
+// actually produce, in display order. It excludes "aklamacja":
+// internal/caldata/caldata.go:42 parses only "first", "psalm", "second" and
+// "gospel" out of the lectionary data, so no computed OF reading ever carries
+// Part == "acclamation" and ofPart's "aklamacja" mapping above is never
+// reached. ofPartOrder stays the full five-ID set on purpose -- it also
+// drives render.LocalizeHeading's *label* matching, where a scraped heading
+// can still read "Aklamacja" even though this engine's own readings never
+// produce that section -- so PartIDs, which promises IDs an app can filter
+// on, needs this narrower list rather than reusing or shrinking ofPartOrder.
+var ofEmittedPartOrder = []string{"pierwsze_czytanie", "psalm", "drugie_czytanie", "ewangelia"}
+
+// PartIDs returns the part IDs the given lectionary can emit, in display order.
+// lect takes config.Config.Lectionary's values: "new" or "traditional". An
+// unknown lectionary returns nil. Callers that build per-part UI (the dlectio
+// app's reading filters) must derive their list from this rather than
+// hardcoding IDs.
+func PartIDs(lect string) []string {
+ switch lect {
+ case "new":
+ return append([]string(nil), ofEmittedPartOrder...)
+ case "traditional":
+ return append([]string(nil), efPartOrder...)
}
- return "", ""
+ return nil
}
// sectionsFor turns computed readings into render-ready sections, tagging each
@@ -109,14 +154,62 @@ func sectionsFor(rs []calendar.Reading, form, lang, siglaLang string, tbl *bible
return out
}
-// dayInfo builds the header (celebration name, liturgical colour) for the
-// computed day. Season is left empty: the celebration name already carries the
-// temporal identity for temporal days, and the header is a nice-to-have.
+// dayInfo builds the header (celebration name, liturgical colour, rank) for
+// the computed day. Season is left empty: the celebration name already
+// carries the temporal identity for temporal days, and the header is a
+// nice-to-have.
func dayInfo(cfg config.Config, day calendar.LiturgicalDay) liturgy.DayInfo {
return liturgy.DayInfo{
Name: celebrationName(cfg, day.Observed),
Colour: string(day.Colour),
+ Rank: displayRank(cfg, day),
+ }
+}
+
+// displayRank is the Ordinary Form's REPORTED rank for the observed
+// celebration -- it never changes calendar.Celebration.Rank (the engine's own
+// field, which drives precedence via ofRankOrder); it only relabels what is
+// handed to a caller here, after the engine has already finished computing.
+//
+// In the 1969 Universal Norms' Table of Liturgical Days, Sunday is its own
+// category, not a solemnity. The calendar engine's temporal.go builds every
+// "Nth Sunday of <season>" day (Ordinary Time, Advent, Lent, the Easter
+// season, Christmas time, and Palm Sunday) with sundayDay(), which sets
+// Rank=solemnity but leaves Class at its zero value (calendar.ClassNone) --
+// solemnity is a placeholder there, not a real classification. A genuinely
+// NAMED solemnity of the Lord that happens to fall on a Sunday (Easter Sunday
+// itself, Pentecost, Ascension/Corpus Christi when transferred, Trinity,
+// Christ the King, Christmas, Epiphany) is built by solemn(), which does set
+// Class=ClassLord, and must keep reporting "solemnity"; a feast of the Lord
+// (Holy Family, Baptism of the Lord) is Rank=feast already and is untouched.
+//
+// Investigated first: temporal.go's own Sunday bool (sundayDay's
+// `Sunday: !priv`) looked like the natural signal, but it does not reach
+// calendar.LiturgicalDay at all (LiturgicalDay carries no such field, only
+// the aggregate ObservedBand), and even if plumbed through it would be the
+// wrong signal here -- by its own doc comment it marks only the band-6
+// Christmas-time/Ordinary-time Sundays, not the band-2 Advent/Lent/Easter
+// Sundays this change must ALSO relabel (the 1st Sunday of Advent from the
+// original bug report is band 2, Privileged=true, Sunday=false). Using
+// Celebration.Class instead of that flag, or of temporalDay.Privileged,
+// covers exactly the sundayDay()-built set in both bands without adding any
+// new field: RankSolemnity + Layer=="temporal" + Class!=ClassLord occurs only
+// from sundayDay(), and (checked against every call site in temporal.go) only
+// ever on an actual Sunday, so the weekday check below is defensive, not
+// load-bearing.
+//
+// The Extraordinary Form (1962) has no such category and is untouched: this
+// only ever fires when the modern form is selected.
+func displayRank(cfg config.Config, day calendar.LiturgicalDay) string {
+ obs := day.Observed
+ if cfg.Selection().Form != "old" &&
+ day.Weekday == time.Sunday &&
+ obs.Layer == "temporal" &&
+ obs.Rank == calendar.RankSolemnity &&
+ obs.Class != calendar.ClassLord {
+ return string(calendar.RankSunday)
}
+ return string(obs.Rank)
}
// celebrationName is the observed celebration's name in the UI language,
diff --git a/internal/readings/partids_test.go b/internal/readings/partids_test.go
new file mode 100644
index 0000000..fdbd816
--- /dev/null
+++ b/internal/readings/partids_test.go
@@ -0,0 +1,75 @@
+package readings
+
+import (
+ "testing"
+
+ "github.com/lukaszkasprzak/lectio/internal/config"
+ "github.com/lukaszkasprzak/lectio/internal/i18n"
+)
+
+// The day header must carry the rank, not just name and colour: the app's
+// calendar rows and the CLI's day line both display it.
+func TestDayInfoCarriesRank(t *testing.T) {
+ cases := []struct {
+ date, lect, wantRank string
+ }{
+ // 2026-08-01 is the memorial of St Alphonsus Liguori in the OF.
+ {"2026-08-01", "new", "memorial"},
+ // 2026-12-25 is a solemnity.
+ {"2026-12-25", "new", "solemnity"},
+ }
+ for _, c := range cases {
+ cfg := config.Config{UILanguage: "en", Lectionary: c.lect, All: true}
+ _, info, err := Load(cfg, Options{Date: c.date, All: true})
+ if err != nil {
+ t.Fatalf("%s: load: %v", c.date, err)
+ }
+ if info.Rank != c.wantRank {
+ t.Errorf("%s: rank = %q, want %q", c.date, info.Rank, c.wantRank)
+ }
+ }
+}
+
+// PartIDs must list exactly the IDs the engine can emit, in display order.
+// The app derives its "show readings" checkboxes from this; when it hardcoded
+// them instead, seven of the nine 1962 IDs were wrong and the epistle's
+// checkbox did nothing.
+func TestPartIDs(t *testing.T) {
+ wantNew := []string{"pierwsze_czytanie", "psalm", "drugie_czytanie", "ewangelia"}
+ wantOld := []string{"epistola", "evangelium"}
+ if got := PartIDs("new"); !equalSlice(got, wantNew) {
+ t.Errorf("PartIDs(new) = %v, want %v", got, wantNew)
+ }
+ if got := PartIDs("traditional"); !equalSlice(got, wantOld) {
+ t.Errorf("PartIDs(traditional) = %v, want %v", got, wantOld)
+ }
+ if got := PartIDs("nonsense"); len(got) != 0 {
+ t.Errorf("PartIDs(nonsense) = %v, want empty", got)
+ }
+}
+
+// Every ID PartIDs lists must have a label in every shipped language,
+// otherwise a checkbox would render a raw ID like "epistola".
+func TestEveryPartIDHasLabels(t *testing.T) {
+ for _, lect := range []string{"new", "traditional"} {
+ for _, id := range PartIDs(lect) {
+ for _, lang := range []string{"en", "pl"} {
+ if i18n.Get(lang).PartLabel[id] == "" {
+ t.Errorf("%s/%s: no label for %q", lect, lang, id)
+ }
+ }
+ }
+ }
+}
+
+func equalSlice(a, b []string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i := range a {
+ if a[i] != b[i] {
+ return false
+ }
+ }
+ return true
+}
diff --git a/internal/readings/readings_test.go b/internal/readings/readings_test.go
index 143b43e..1674d85 100644
--- a/internal/readings/readings_test.go
+++ b/internal/readings/readings_test.go
@@ -99,6 +99,43 @@ func TestLoadModern(t *testing.T) {
}
}
+// TestSundayRankIsDisplayOnly guards the Ordinary Form's Sunday display rank.
+// Sunday is its own category in the 1969 Universal Norms' Table of Liturgical
+// Days -- never a solemnity -- so an ordinary "Nth Sunday of <season>" temporal
+// day (Ordinary Time, Advent, Lent, the Easter season, Christmas time; this
+// also covers Palm Sunday) must report "sunday". A NAMED solemnity of the Lord
+// that happens to fall on a Sunday (Easter Sunday itself, Pentecost) is a
+// genuine solemnity and must keep reporting "solemnity"; a feast of the Lord
+// (Holy Family) must keep reporting "feast". This is display-only: it must
+// never touch the engine's internal precedence, so the 1962 form (which has no
+// such Sunday category -- Sundays report their class exactly as before) is
+// asserted unchanged too. Dates are the ones from the original bug report,
+// plus the Easter/Pentecost/Holy Family dates they imply for the same
+// liturgical year.
+func TestSundayRankIsDisplayOnly(t *testing.T) {
+ cases := []struct {
+ date, lect, wantRank, why string
+ }{
+ {"2026-08-09", "new", "sunday", "19th Sunday in Ordinary Time"},
+ {"2026-11-29", "new", "sunday", "1st Sunday of Advent"},
+ {"2027-03-28", "new", "solemnity", "Easter Sunday itself"},
+ {"2027-05-16", "new", "solemnity", "Pentecost"},
+ {"2026-12-27", "new", "feast", "Holy Family"},
+ {"2026-08-09", "traditional", "class-2", "1962 Sunday after Pentecost"},
+ {"2026-11-29", "traditional", "class-1", "1962 1st Sunday of Advent"},
+ }
+ for _, c := range cases {
+ cfg := config.Config{Lectionary: c.lect}
+ _, info, err := Load(cfg, Options{Date: c.date, All: true})
+ if err != nil {
+ t.Fatalf("%s (%s, %s): Load: %v", c.date, c.lect, c.why, err)
+ }
+ if info.Rank != c.wantRank {
+ t.Errorf("%s (%s, %s): Rank = %q, want %q (name=%q)", c.date, c.lect, c.why, info.Rank, c.wantRank, info.Name)
+ }
+ }
+}
+
// TestLoadTraditional computes the Extraordinary Form day offline: it never
// needs the network, and yields the EF epistle+gospel with a header name.
func TestLoadTraditional(t *testing.T) {
diff --git a/internal/render/render.go b/internal/render/render.go
index c744aac..3437396 100644
--- a/internal/render/render.go
+++ b/internal/render/render.go
@@ -29,6 +29,14 @@ func versionLabel(version, lang string) string {
// than ranging over i18n.UI.PartLabel (a map, so Go randomises its
// iteration order). None of the five labels is a prefix of another, so the
// order never changes which one matches, only determinism.
+//
+// Its *set* of five IDs must match internal/readings/offline.go's
+// ofPartOrder, which lists the same modern IDs in a different, deliberate
+// order (display order, unrelated to this package's prefix-match need).
+// Nothing enforces that agreement mechanically -- internal/render and
+// internal/readings do not import each other (adding a cross-package test
+// would create a new dependency edge that does not exist today) -- so if a
+// sixth modern part is ever added to ofPartOrder, add it here too, by hand.
var modernPartOrder = []string{"pierwsze_czytanie", "drugie_czytanie", "psalm", "aklamacja", "ewangelia"}
// LocalizeHeading swaps a modern (niedziela.pl) section heading's leading
diff --git a/mobile/mobile.go b/mobile/mobile.go
new file mode 100644
index 0000000..7e4c109
--- /dev/null
+++ b/mobile/mobile.go
@@ -0,0 +1,229 @@
+// Package mobile is lectio's engine facade for the dlectio Android app. It is
+// bound to a native Android library with gomobile:
+//
+// gomobile bind -target=android/arm64,android/amd64 -androidapi 26 \
+// -javapkg=xyz.labunix.dlectio.engine -tags fullbible \
+// -o dlectio-engine.aar github.com/lukaszkasprzak/lectio/mobile
+//
+// -javapkg gives the bound Mobile class its xyz.labunix.dlectio.engine.mobile
+// package (the app imports it from there); the two -target ABIs match the
+// app's arm64-v8a and x86_64 device/emulator targets; -androidapi 26 matches
+// the app's minSdk. Omitting any of the three still produces an .aar, just
+// one the app cannot import or that lacks an ABI it needs.
+//
+// The app calls Day() with a plain date/form/version and receives one JSON
+// document with the day's identity and rendered Mass readings. All liturgical
+// computation and text resolution happens here, in the exact validated lectio
+// engine — the app does none of it. Keep every exported signature to types
+// gomobile can marshal -- strings and ints only -- with JSON as the payload
+// format for everything structured. A Go int parameter (e.g. Days' count)
+// surfaces on the Kotlin side as a long, not an Int.
+package mobile
+
+import (
+ "encoding/json"
+ "strings"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/config"
+ "github.com/lukaszkasprzak/lectio/internal/i18n"
+ "github.com/lukaszkasprzak/lectio/internal/readings"
+ "github.com/lukaszkasprzak/lectio/internal/render"
+)
+
+// reading is one Mass reading, rendered in the requested corpus.
+type reading struct {
+ Ord int `json:"ord"`
+ Part string `json:"part"` // machine key, e.g. "ewangelium" / "psalm"
+ Heading string `json:"heading"` // human label in the interface language, e.g. "Gospel"
+ Citation string `json:"citation"`
+ Text string `json:"text"`
+}
+
+// dayDoc is the JSON returned by Day.
+type dayDoc struct {
+ Date string `json:"date"`
+ Form string `json:"form"`
+ Lang string `json:"lang"`
+ Version string `json:"version"`
+ Name string `json:"name"`
+ Season string `json:"season"`
+ Colour string `json:"colour"`
+ Rank string `json:"rank"`
+ RankLabel string `json:"rank_label"`
+ Readings []reading `json:"readings"`
+ Error string `json:"error,omitempty"`
+}
+
+// lectByForm maps the app's form code to lectio's lectionary value.
+func lectByForm(form string) string {
+ if form == "ef" {
+ return "traditional"
+ }
+ return "new"
+}
+
+// Day computes one day's identity and Mass readings and returns them as JSON.
+//
+// date - "YYYY-MM-DD"
+// form - "of" (Ordinary) or "ef" (1962)
+// version - corpus: "drb" | "wuj" | "vul" | "grb"
+// lang - interface language for the celebration name: "en" | "pl"
+//
+// It never panics: any engine error is reported in the JSON "error" field so the
+// JNI boundary always returns a well-formed string.
+func Day(date, form, version, lang string) string {
+ lect := lectByForm(form)
+ out := dayDoc{Date: date, Form: form, Lang: lang, Version: version}
+
+ // Day identity (name/season/colour) in the interface language.
+ cfgID := config.Config{UILanguage: lang, Lectionary: lect, All: true}
+ if _, info, err := readings.Load(cfgID, readings.Options{Date: date, All: true}); err == nil {
+ out.Name = info.Name
+ out.Season = info.Season
+ out.Colour = info.Colour
+ out.Rank = info.Rank
+ out.RankLabel = i18n.Get(lang).Rank[info.Rank]
+ } else {
+ out.Error = err.Error()
+ }
+
+ // Readings, resolved and rendered in the requested corpus. The interface
+ // language localizes the structural labels (Heading) and citation dialect;
+ // the corpus (version) alone decides the scripture text.
+ cfgR := config.Config{UILanguage: lang, Lectionary: lect, ReadingVersion: version, All: true}
+ secs, _, err := readings.Load(cfgR, readings.Options{Date: date, All: true})
+ if err != nil {
+ if out.Error == "" {
+ out.Error = err.Error()
+ }
+ } else {
+ for i, sec := range secs {
+ // GatherVersion's lang localizes only the label/error wording, never
+ // the verse text (which the corpus alone decides), so the interface
+ // language is correct here. HeadingWithRef gives the same localized
+ // "label (citation)" heading lectio shows in its cli/tui/web.
+ _, blocks := render.GatherVersion(version, sec, lect, lang)
+ out.Readings = append(out.Readings, reading{
+ Ord: i,
+ Part: sec.PartID,
+ Heading: render.HeadingWithRef(sec, lang),
+ Citation: sec.Citation,
+ Text: strings.Join(blocks, "\n"),
+ })
+ }
+ }
+
+ b, err := json.Marshal(out)
+ if err != nil {
+ // Should be impossible with these types; degrade gracefully.
+ return `{"error":"marshal failed"}`
+ }
+ return string(b)
+}
+
+// summaryPart is one section of a day, citation only -- no rendered text.
+type summaryPart struct {
+ Part string `json:"part"`
+ Citation string `json:"citation"`
+}
+
+// daySummary is one day's identity and its reading citations. It is Day's
+// cheaper projection: the calendar view needs the day's shape, not its text.
+type daySummary struct {
+ Date string `json:"date"`
+ Name string `json:"name"`
+ Rank string `json:"rank"`
+ RankLabel string `json:"rank_label"`
+ Colour string `json:"colour"`
+ Parts []summaryPart `json:"parts"`
+ Error string `json:"error,omitempty"`
+}
+
+// Days returns count consecutive days from start as a JSON array, each with its
+// identity and reading citations but no reading text. It is the calendar view's
+// source.
+//
+// start - "YYYY-MM-DD", taken literally: aligning to a week is the caller's job
+// count - 1..366; anything else yields "[]"
+// form - "of" or "ef"
+// lang - interface language for the name, rank word and citation dialect
+//
+// No version parameter: the corpus decides only scripture text, which this never
+// renders, while the citation's sigla dialect follows lang.
+//
+// A day that fails to compute carries its own "error" and does not abort the
+// rest, so one bad date cannot blank a whole week. Malformed input yields "[]"
+// rather than an error string, so the caller always parses an array.
+func Days(start string, count int, form, lang string) string {
+ if count < 1 || count > 366 {
+ return "[]"
+ }
+ d, err := time.Parse("2006-01-02", start)
+ if err != nil {
+ return "[]"
+ }
+ lect := lectByForm(form)
+ ui := i18n.Get(lang)
+ cfg := config.Config{UILanguage: lang, Lectionary: lect, All: true}
+
+ out := make([]daySummary, 0, count)
+ for i := 0; i < count; i++ {
+ date := d.AddDate(0, 0, i).Format("2006-01-02")
+ row := daySummary{Date: date, Parts: []summaryPart{}}
+ secs, info, err := readings.Load(cfg, readings.Options{Date: date, All: true})
+ if err != nil {
+ row.Error = err.Error()
+ } else {
+ row.Name = info.Name
+ row.Colour = info.Colour
+ row.Rank = info.Rank
+ row.RankLabel = ui.Rank[info.Rank]
+ for _, sec := range secs {
+ row.Parts = append(row.Parts, summaryPart{Part: sec.PartID, Citation: sec.Citation})
+ }
+ }
+ out = append(out, row)
+ }
+
+ b, err := json.Marshal(out)
+ if err != nil {
+ return "[]"
+ }
+ return string(b)
+}
+
+// partLabel is one section's machine ID and its human label.
+type partLabel struct {
+ Part string `json:"part"`
+ Label string `json:"label"`
+}
+
+// PartLabels returns the form's section IDs and their labels in lang, in display
+// order, as a JSON array. The app builds its reading filters from this instead
+// of hardcoding part IDs -- which is how it came to ship seven 1962 checkboxes
+// the engine never emits.
+//
+// An array, not an object: JSON object key order is not guaranteed and the app
+// renders these in order.
+func PartLabels(form, lang string) string {
+ ids := readings.PartIDs(lectByForm(form))
+ ui := i18n.Get(lang)
+ out := make([]partLabel, 0, len(ids))
+ for _, id := range ids {
+ label := ui.PartLabel[id]
+ if label == "" {
+ label = id
+ }
+ out = append(out, partLabel{Part: id, Label: label})
+ }
+ b, err := json.Marshal(out)
+ if err != nil {
+ return "[]"
+ }
+ return string(b)
+}
+
+// Ping is a trivial JNI smoke test: it returns "pong" so the app can confirm the
+// native engine loaded before issuing a real query.
+func Ping() string { return "pong" }
diff --git a/mobile/mobile_test.go b/mobile/mobile_test.go
new file mode 100644
index 0000000..85de500
--- /dev/null
+++ b/mobile/mobile_test.go
@@ -0,0 +1,211 @@
+package mobile
+
+import (
+ "encoding/json"
+ "strings"
+ "testing"
+)
+
+func decodeDay(t *testing.T, s string) map[string]any {
+ t.Helper()
+ var m map[string]any
+ if err := json.Unmarshal([]byte(s), &m); err != nil {
+ t.Fatalf("Day returned invalid JSON: %v\n%s", err, s)
+ }
+ return m
+}
+
+func TestDayCarriesLocalisedRank(t *testing.T) {
+ pl := decodeDay(t, Day("2026-08-01", "of", "vul", "pl"))
+ if pl["rank"] != "memorial" {
+ t.Errorf("pl rank = %v, want memorial", pl["rank"])
+ }
+ if pl["rank_label"] != "wspomnienie obowiązkowe" {
+ t.Errorf("pl rank_label = %v", pl["rank_label"])
+ }
+ en := decodeDay(t, Day("2026-08-01", "of", "vul", "en"))
+ if en["rank_label"] != "memorial" {
+ t.Errorf("en rank_label = %v", en["rank_label"])
+ }
+}
+
+// The app shows the colour as a swatch and never as a word, so the raw key must
+// be present and no localised label may be. This test exists so the app cannot
+// quietly start depending on a colour word.
+func TestDayHasColourKeyButNoColourLabel(t *testing.T) {
+ m := decodeDay(t, Day("2026-08-01", "of", "vul", "pl"))
+ if m["colour"] != "white" {
+ t.Errorf("colour = %v, want white", m["colour"])
+ }
+ if _, present := m["colour_label"]; present {
+ t.Error("colour_label must not be returned: the app draws a swatch")
+ }
+}
+
+func decodeDays(t *testing.T, s string) []map[string]any {
+ t.Helper()
+ var a []map[string]any
+ if err := json.Unmarshal([]byte(s), &a); err != nil {
+ t.Fatalf("Days returned invalid JSON: %v\n%s", err, s)
+ }
+ return a
+}
+
+func TestDaysReturnsConsecutiveDates(t *testing.T) {
+ a := decodeDays(t, Days("2026-07-27", 7, "of", "pl"))
+ if len(a) != 7 {
+ t.Fatalf("got %d elements, want 7", len(a))
+ }
+ want := []string{"2026-07-27", "2026-07-28", "2026-07-29", "2026-07-30",
+ "2026-07-31", "2026-08-01", "2026-08-02"}
+ for i, w := range want {
+ if a[i]["date"] != w {
+ t.Errorf("element %d date = %v, want %v", i, a[i]["date"], w)
+ }
+ }
+}
+
+// Days must agree with Day about the same date -- it is a cheaper projection of
+// the same computation, not a second implementation.
+func TestDaysAgreesWithDay(t *testing.T) {
+ one := decodeDay(t, Day("2026-08-01", "of", "vul", "pl"))
+ week := decodeDays(t, Days("2026-08-01", 1, "of", "pl"))
+ if len(week) != 1 {
+ t.Fatalf("got %d elements, want 1", len(week))
+ }
+ row := week[0]
+ for _, k := range []string{"name", "colour", "rank", "rank_label"} {
+ if row[k] != one[k] {
+ t.Errorf("%s: Days = %v, Day = %v", k, row[k], one[k])
+ }
+ }
+ parts := row["parts"].([]any)
+ readings := one["readings"].([]any)
+ if len(parts) != len(readings) {
+ t.Fatalf("parts = %d, readings = %d", len(parts), len(readings))
+ }
+ for i := range parts {
+ p := parts[i].(map[string]any)
+ r := readings[i].(map[string]any)
+ if p["part"] != r["part"] || p["citation"] != r["citation"] {
+ t.Errorf("element %d: Days %v/%v vs Day %v/%v",
+ i, p["part"], p["citation"], r["part"], r["citation"])
+ }
+ }
+}
+
+// No reading text may cross the boundary: that is the whole point of Days.
+func TestDaysCarriesNoReadingText(t *testing.T) {
+ s := Days("2026-08-01", 7, "of", "pl")
+ if strings.Contains(s, `"text"`) {
+ t.Error("Days must not return reading text")
+ }
+ if strings.Contains(s, "colour_label") {
+ t.Error("Days must not return a colour label")
+ }
+}
+
+func TestDaysRejectsBadInput(t *testing.T) {
+ for _, c := range []struct {
+ start string
+ count int
+ }{
+ {"not-a-date", 7}, {"2026-08-01", 0}, {"2026-08-01", -1}, {"2026-08-01", 400},
+ {"2026-08-01", 367}, // one past the documented upper bound
+ } {
+ if got := Days(c.start, c.count, "of", "pl"); got != "[]" {
+ t.Errorf("Days(%q, %d) = %s, want []", c.start, c.count, got)
+ }
+ }
+}
+
+// count's documented range is 1..366; 367 (above) is the first invalid value
+// and 366 (here) is the last valid one -- the boundary an off-by-one would
+// actually live on.
+func TestDaysAcceptsUpperBoundCount(t *testing.T) {
+ a := decodeDays(t, Days("2026-01-01", 366, "of", "en"))
+ if len(a) != 366 {
+ t.Errorf("got %d elements, want 366", len(a))
+ }
+}
+
+func BenchmarkDaysWeek(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Days("2026-07-27", 7, "of", "pl")
+ }
+}
+
+// This is the assertion that would have caught the app's dead 1962 checkboxes:
+// it listed nine part IDs where the engine emits two.
+func TestPartLabelsMatchesWhatTheEngineEmits(t *testing.T) {
+ var ef []map[string]any
+ if err := json.Unmarshal([]byte(PartLabels("ef", "pl")), &ef); err != nil {
+ t.Fatalf("invalid JSON: %v", err)
+ }
+ if len(ef) != 2 {
+ t.Fatalf("ef: got %d labels, want 2: %v", len(ef), ef)
+ }
+ if ef[0]["part"] != "epistola" || ef[0]["label"] != "Lekcja" {
+ t.Errorf("ef[0] = %v, want epistola/Lekcja", ef[0])
+ }
+ if ef[1]["part"] != "evangelium" || ef[1]["label"] != "Ewangelia" {
+ t.Errorf("ef[1] = %v, want evangelium/Ewangelia", ef[1])
+ }
+
+ // OF: derive the expected set from what Days actually emits over a full
+ // year (2026 -- a full Sunday cycle, so second readings appear too),
+ // rather than hand-copying the production list, which asserts a
+ // declaration against itself and can never fail for this class of bug
+ // (that is exactly how the app came to render a checkbox -- aklamacja --
+ // that filters an ID the engine never emits).
+ observed := map[string]bool{}
+ s := Days("2026-01-01", 365, "of", "pl")
+ var days []map[string]any
+ if err := json.Unmarshal([]byte(s), &days); err != nil {
+ t.Fatalf("Days returned invalid JSON: %v", err)
+ }
+ for _, d := range days {
+ parts, _ := d["parts"].([]any)
+ for _, p := range parts {
+ part, _ := p.(map[string]any)
+ if id, ok := part["part"].(string); ok {
+ observed[id] = true
+ }
+ }
+ }
+ if len(observed) == 0 {
+ t.Fatal("swept zero part IDs from Days over 2026 -- the sweep is broken, not necessarily the engine")
+ }
+
+ var of []map[string]any
+ if err := json.Unmarshal([]byte(PartLabels("of", "pl")), &of); err != nil {
+ t.Fatalf("invalid JSON: %v", err)
+ }
+ got := map[string]bool{}
+ for _, e := range of {
+ got[e["part"].(string)] = true
+ }
+ if len(got) != len(of) {
+ t.Fatalf("PartLabels(of) lists a part ID more than once: %v", of)
+ }
+ for id := range observed {
+ if !got[id] {
+ t.Errorf("Days emits part %q somewhere in 2026 but PartLabels(of) does not list it: %v", id, of)
+ }
+ }
+ for id := range got {
+ if !observed[id] {
+ t.Errorf("PartLabels(of) lists part %q but Days never emits it anywhere in 2026: %v", id, of)
+ }
+ }
+}
+
+func TestPartLabelsUnknownForm(t *testing.T) {
+ // An unknown form is treated as the modern one, matching lectByForm: the
+ // fallback must be byte-identical to "of", not merely non-empty.
+ got := PartLabels("nonsense", "en")
+ want := PartLabels("of", "en")
+ if got != want {
+ t.Errorf("PartLabels(\"nonsense\", \"en\") = %s, want %s (same as \"of\")", got, want)
+ }
+}
diff --git a/scripts/build-oracle-ef.sh b/scripts/build-oracle-ef.sh
index 2864551..32cb385 100755
--- a/scripts/build-oracle-ef.sh
+++ b/scripts/build-oracle-ef.sh
@@ -1,38 +1,42 @@
#!/usr/bin/env bash
-# build-oracle-ef.sh — generate the EF (1962) regression oracle by fetching
-# missalemeum's per-date proper API (the same source lectio's trad scraper uses,
-# built on Divinum Officium data) for 2025-2026.
+# build-oracle-ef.sh — build the EF (1962) regression oracle from the committed
+# missalemeum snapshot (sources/snapshot.tar.gz, missalemeum/en/YYYY-MM-DD.json,
+# 2026-01-01 .. 2027-12-31, 730 days), not a live fetch, so the oracle is
+# reproducible offline and pinned to a known-good snapshot.
#
-# NOT run by `go test`. Requires network + curl + jq + GNU date. From repo root:
+# Three things about this data that cost hours to learn (see oracle_ef_test.go):
+# 1. info.id looks like "sancti:MM-DD:rank:colour", but its embedded rank is
+# the rank of the PROPERS USED that day, not the day's own rank (e.g.
+# 2026-01-02 is a class-4 feria whose id is "sancti:01-01:1:w" because it
+# reuses the Circumcision's propers). Extracted here for provenance only
+# -- never parse rank/colour out of it. Use info.rank/info.colors.
+# 2. info.colors is an array; 14 of 730 days carry two values (Gaudete/
+# Laetare "pv", Palm Sunday "rv", Good Friday "bv", Holy Saturday "vw").
+# The Go test compares by membership, not equality.
+# 3. A two-colour value on a weekday can be a proper-reuse artifact (a feria
+# inside Gaudete/Laetare week reusing the Sunday's own propers) rather
+# than a claim about that weekday's own colour.
+#
+# From repo root:
# scripts/build-oracle-ef.sh
# Writes internal/calendar/testdata/oracle-ef.json:
-# { "YYYY-MM-DD": {"tempora": "...", "title": "...", "rank": N, "colour": "w"} }
-# tempora is "" when missalemeum returns null (then the temporal identity is in
-# title); the Go test derives the season from tempora-or-title.
+# { "YYYY-MM-DD": {"id":"...", "tempora":"...", "title":"...", "rank":N, "colours":["w",...]} }
set -euo pipefail
out=internal/calendar/testdata/oracle-ef.json
-mkdir -p "$(dirname "$out")"
work=$(mktemp -d)
+trap 'rm -rf "$work"' EXIT
-for y in 2025 2026; do
- d="$y-01-01"
- while [ "$(date -d "$d" +%Y)" = "$y" ]; do
- echo "$d"
- d=$(date -d "$d +1 day" +%F)
- done
-done > "$work/dates"
+tar xzf sources/snapshot.tar.gz -C "$work" missalemeum/en
-fetch() {
- local d="$1"
- local j
- j=$(curl -sf --max-time 25 "https://www.missalemeum.com/en/api/v5/proper/$d" || true)
- printf '%s' "$j" | jq -c --arg d "$d" \
- '{($d): (.[0].info | {id:(.id // ""), tempora:(.tempora // ""), title:.title, rank:.rank, colour:(.colors|join(""))})}' \
- 2>/dev/null || true
-}
-export -f fetch
+jq -s '
+ map({(.[0].info.date): {
+ id: (.[0].info.id // ""),
+ tempora: (.[0].info.tempora // ""),
+ title: .[0].info.title,
+ rank: .[0].info.rank,
+ colours: .[0].info.colors
+ }}) | add
+' "$work"/missalemeum/en/*.json > "$out"
-xargs -P 12 -I{} bash -c 'fetch "$@"' _ {} < "$work/dates" | jq -s 'add' > "$out"
echo "wrote $out ($(jq 'length' "$out") days)" >&2
-rm -rf "$work"
diff --git a/scripts/gen-sanctoral-ef.go b/scripts/gen-sanctoral-ef.go
index e3ad18e..0a43869 100644
--- a/scripts/gen-sanctoral-ef.go
+++ b/scripts/gen-sanctoral-ef.go
@@ -10,8 +10,10 @@
// (Lectio) and Gospel (Evangelium) citations, plus any sancti co-celebrations
// listed as commemorations. Several reference years are tried per date so a
// saint whose date is a Sunday (or under a higher feast) in one year is still
-// captured, observed with its own readings, from another. Existing Latin names
-// in the file are preserved (missalemeum has no Latin titles).
+// captured, observed with its own readings, from another. Existing
+// non-English names (Polish, Latin, or any other language already present
+// in the file) are preserved verbatim -- missalemeum supplies English
+// titles only.
//
// One-time; requires network. Run from the repo root:
//
@@ -113,21 +115,83 @@ func rankOf(n int) calendar.Rank {
// classOf marks the feasts of the Lord (class lord). A II class feast of the
// Lord takes the place of a Sunday of the same class (1960 occurrence rules) —
-// e.g. the Purification (Presentation, Feb 2), the Exaltation of the Holy Cross
-// (Sep 14), the Dedication of the Lateran (Nov 9). Saint/BVM feasts of the same
-// class are only commemorated on a Sunday, so they need no marker.
+// e.g. the Exaltation of the Holy Cross (Sep 14), the Dedication of the
+// Archbasilica (Nov 9), the Commemoration of the Baptism (Jan 13). Saint/BVM
+// feasts of the same class are only commemorated on a Sunday, so they need no
+// marker.
+//
+// Two substring checks were narrowed, and one is new, found by checking the
+// calendarium's own verbatim titles (missale-romanum-1962.pdf) against what
+// this function produced -- but "purification" is a THIRD, DELIBERATELY
+// DIFFERENT case, kept matching rather than narrowed, and the reason is
+// itself worth recording:
+//
+// - "purification" IS kept as a lord match, even though the calendarium's
+// own title is "IN PURIFICATIONE B. MARIAE VIRG." (a feast of the BLESSED
+// VIRGIN by name) -- a DECISION AGAINST that primary text, made on
+// occurrence-behaviour evidence, not a reading of it, and recorded as
+// such rather than dressed up as textually clean.
+//
+// The tag's actual job is not naming/colour categorisation: it exists
+// solely to drive the occurrence rule that separates RG 91 entry 14
+// ("Festa Domini II classis" -- takes an occurring II-class Sunday's
+// place outright) from entry 16 ("Festa II classis Ecclesiae universae,
+// quae non [sunt Domini]" -- merely commemorated on one), entry 15
+// ("Dominicae II classis") sitting between the two. Checked live against
+// missalemeum (this generator's own data source): the Purification
+// takes a II-class Sunday's place OUTRIGHT, commemorations EMPTY
+// (2014-02-02, 2020-02-02, 2025-02-02, 2031-02-02, 2042-02-02, all
+// fetched independently) -- entry 14's pattern, not entry 16's (control:
+// the Nativity of the BVM, an undisputed ordinary Marian feast, on a
+// Sunday -- 2019-09-08 -- shows the SUNDAY observed, the feast merely
+// commemorated, entry 16's own pattern). RG 112(b), "Officium, Missa aut
+// commemoratio de dominica excludit commemorationem... de festo vel
+// mysterio Domini, et vicissim" (a Sunday's office and a feast/mystery
+// OF THE LORD mutually exclude each other as commemorations), backs the
+// empty commemoration list independently of RG 91's table position.
+//
+// Genuine primary-text counter-evidence exists and is not discarded:
+// RG 120(b), "Adhibetur color albus... b) B. Mariae Virg., etiam in
+// benedictione et processione candelarum die 2 februarii" -- 2 February
+// is filed under the WHITE-colour rule's "B. Mariae Virg." heading,
+// kept separate there from 120(a)'s own "Domini" heading. Both textual
+// tests available (the calendarium's title, RG 120's own taxonomy)
+// point BVM; the occurrence-behaviour evidence points Domini. This is a
+// live, acknowledged disagreement with the primary text on the
+// strength of oracle evidence about what the day actually DOES, not a
+// claim that the text is wrong or ambiguous. See
+// internal/calendar/precedence_ef_repro_test.go's
+// TestPurificationBeatsFebruarySunday for the fixture this rests on --
+// committed, not merely asserted, since the deciding years (2 February
+// on a Sunday) fall outside this repo's own 2026-2027 oracle snapshot
+// window.
+//
+// - "holy name" alone is ambiguous: it matches BOTH "Holy Name of Jesus"
+// (a feast of the Lord) and "Most Holy Name of Mary"/"Holy Name of Mary"
+// (a feast of the BVM, calendarium: "Sanctissimi Nominis Mariae") --
+// wrongly matching the latter too. Excluded whenever the title also
+// names Mary. Unlike the Purification, this one is NOT contested: its
+// occurrence behaviour matches the ordinary-BVM pattern too.
+//
+// - "baptism" is a new case: "Commemoration of the Baptism of the Lord" (13
+// January, calendarium: "IN COMMEMORATIONE BAPTISMATIS D. N. I. C.") did
+// not match any existing case -- the HasSuffix check below requires "of
+// OUR Lord", but this title's own wording is "of THE Lord" -- so it was
+// missing the marker entirely.
func classOf(en string) string {
l := strings.ToLower(en)
switch {
case strings.Contains(l, "holy cross"), // Exaltation / Finding of the Holy Cross
strings.Contains(l, "transfiguration"),
- strings.Contains(l, "purification"), // the Presentation of the Lord
+ strings.Contains(l, "purification"), // the Presentation of the Lord -- occurrence-behaviour evidence, see doc comment
strings.Contains(l, "precious blood"),
- strings.Contains(l, "holy name"),
+ strings.Contains(l, "baptism"),
+ strings.Contains(l, "holy name") && !strings.Contains(l, "mary"),
strings.Contains(l, "dedication of the archbasilica"),
strings.Contains(l, "of our holy savior"),
strings.Contains(l, "of our lord jesus"),
- strings.HasSuffix(l, "of our lord"):
+ strings.HasSuffix(l, "of our lord"),
+ strings.HasSuffix(l, "of the lord"):
return "lord"
}
return ""
@@ -169,6 +233,19 @@ type mmDay struct {
} `json:"sections"`
}
+// fetchOnce fetches one date's proper. KNOWN LIMITATION: missalemeum
+// returns one array element PER MASS on a date with more than one (25
+// December: three, ids ...m1/...m2/...m3) and this reads only `data[0]`,
+// the first -- so a commemoration attached specifically to a second or
+// third Mass (the calendarium's own "In secunda Missa: Commemoratio..."
+// pattern, e.g. St Anastasia on Christmas Day) is structurally invisible
+// to every caller of this function, not just harvestDate's own use of it.
+// See the PRIMARY-SOURCE NOTE above harvestDate for the fuller account --
+// in Anastasia's specific case this is moot (missalemeum's own
+// "commemorations" list is empty on all three of the date's records, not
+// just the first, so fixing this would not by itself recover her), but the
+// limitation is real and would matter for any date whose SECOND or third
+// Mass genuinely does carry a commemoration missalemeum's API records.
func fetchOnce(date string) (*mmDay, error) {
req, _ := http.NewRequest("GET", "https://www.missalemeum.com/en/api/v5/proper/"+date, nil)
req.Header.Set("User-Agent", ua)
@@ -200,9 +277,16 @@ func fetch(date string) (*mmDay, error) {
}
type entry struct {
- slug, date, colour, class, en, la, first, gospel string
- rank calendar.Rank
- observed bool // has readings / reliable rank
+ slug, date, colour, class, en, first, gospel string
+ rank calendar.Rank
+ observed bool // has readings / reliable rank
+ // otherNames holds every "name.<lang>" field already present in the
+ // existing file for this slug, EXCLUDING "name.en" (English always
+ // comes fresh from missalemeum, in `en` above). Keyed by the full
+ // field name (e.g. "name.pl") so main() can emit it verbatim without
+ // hardcoding a language list -- see main()'s own preservedNames
+ // comment for why a hardcoded list is exactly the bug this fixes.
+ otherNames map[string]string
}
// idParts splits "sancti:MM-DD[sfx]:RANK:COLOUR" into rank word and colour word.
@@ -215,6 +299,32 @@ func idParts(id string) (calendar.Rank, string) {
return rankOf(n), colourOf(p[3])
}
+// idHomeDate extracts the "MM-DD" home date embedded in a missalemeum
+// info.id ("sancti:MM-DD[sfx]:RANK:COLOUR" -- sfx is an internal
+// disambiguator missalemeum sometimes appends, e.g. "01-28t", "11-09cc",
+// "11-02m1"; the first 5 characters are always the date). Returns "" if id
+// is too short to contain one.
+//
+// This is the fix for a class of bug the slug-collision fix above (in
+// main()) exposed rather than caused: a MOVABLE-transfer feast displayed on
+// whatever civil date it actually landed on in a given reference year (St
+// Joseph, 19 March, impeded by a Sunday of Lent and shown on the 20th; the
+// Annunciation deferred past Holy Week; All Souls moved to the Monday when 2
+// November is a Sunday; even St Matthias' fixed 24 February shown on the
+// 25th in a leap year) still carries its OWN proper date in info.id, not the
+// civil date queried. Comparing the two lets harvestDate recognise "this is
+// not really this date's own office" and skip it, instead of harvesting a
+// phantom fixed-date entry at the transferred civil date -- confirmed
+// live (`curl .../api/v5/proper/2023-03-20`): id "sancti:03-19:1:w" while
+// the date queried is 2023-03-20.
+func idHomeDate(id string) string {
+ p := strings.SplitN(id, ":", 2)
+ if len(p) < 2 || len(p[1]) < 5 {
+ return ""
+ }
+ return p[1][:5]
+}
+
func readingsFrom(d *mmDay) (first, gospel string) {
for _, s := range d.Sections {
if len(s.Body) == 0 || len(s.Body[0]) == 0 {
@@ -234,40 +344,246 @@ func readingsFrom(d *mmDay) (first, gospel string) {
return
}
+// PRIMARY-SOURCE NOTE (found and corrected during review): of the three
+// local scans this generator's citations are checked against
+// (docs/research/*.pdf in the sibling colitur repo), ONE --
+// "1962-06-23,…LT.pdf", the Archivum Liturgicum ELECTRONIC TRANSCRIPTION --
+// silently drops vigil commemorations that the other two, PHOTOGRAPHIC
+// scans of the actual 1962 Missale Romanum, both carry. Confirmed on four
+// entries: 7 August (Donatus), 9 August (Romanus), 14 August (Eusebius),
+// 25 December (Anastasia) -- all present in both photographic scans'
+// calendarium AND their own Proprium Sanctorum text ("Et fit
+// commemoratio S. Romani Mar-", "...S. Eusebii Con-", etc.), all silently
+// absent from the transcription. A `knownSpuriousComm` exclusion list once
+// stood here, built by checking ONLY the transcription and concluding two
+// of these four ("Romanus", "Eusebius" on 14 August) were spurious -- WRONG,
+// on evidence that itself was incomplete, not on a genuine absence. Treat
+// the photographic scans as the primary source and the electronic
+// transcription as a convenience index only; where they disagree, the scan
+// wins. (14 August's "St. Eusebius, Conf." and 16 December's "St. Eusebius,
+// Ep. et Mart." are two different people, both genuinely in the calendarium
+// -- see slugOverride below, not a reason to drop either.)
+//
+// Of the four confirming examples above, three (Donatus, Romanus, Eusebius)
+// are fixed by this generator as of this note. **25 December's Anastasia is
+// NOT, and cannot be from this data source alone** -- a different, NOT
+// generator-fixable limitation, recorded so the next person does not
+// mistake her continued absence for an oversight of the fix above (she was
+// also missing at the branch point, so this is not a regression either):
+// the photographic scan places her "In secunda Missa: Commemoratio S.
+// Anastasiae Mart." -- specifically the SECOND of Christmas Day's three
+// Masses (missalemeum's own "2026-12-25" query independently confirmed to
+// return exactly three records, ids ...m1/...m2/...m3). Two compounding
+// problems, not one: fetchOnce (below) reads only `data[0]`, the FIRST
+// Mass, so this harvester cannot structurally see a commemoration attached
+// to a date's second or third Mass at all, for ANY date, not just this
+// one -- but ALSO, checked directly (not merely inferred from the
+// symptom), missalemeum's OWN "commemorations" list is empty on all THREE
+// of the 25 December records, not just the first -- so even a fetchOnce
+// rewritten to merge all of a date's Masses would still not recover her:
+// missalemeum's own data lacks her here, the same "transcription-shaped"
+// gap as Donatus/Romanus/Eusebius, just in the live API rather than the
+// static PDF this time. Not fixed: no source this generator reads carries
+// her.
+
+// slugOverride gives a proper, distinct slug to a small number of
+// commemorations whose title slugifies IDENTICALLY to an unrelated feast on
+// a different fixed date. Confirmed against the calendarium: 28 January's
+// "St. Agnes" is the traditional SECOND commemoration of the 21 January
+// feast (the same saint, repeated, not a coincidence); 14 May's "St.
+// Boniface" is a different early martyr from 5 June's Boniface of Mainz, an
+// entirely different person whose title happens to abbreviate to the same
+// English string; 14 August's "St. Eusebius" (a Confessor, calendarium "S.
+// Eusebii Conf.") is likewise a different person from 16 December's "St.
+// Eusebius" (a Bishop and Martyr, calendarium "S. Eusebii Ep. et Mart."),
+// both genuinely commemorated, missalemeum giving both the same bare
+// English title. Keyed "MM-DD/original-slug" -> replacement slug.
+var slugOverride = map[string]string{
+ "01-28/agnes": "agnes-secundo",
+ "08-14/eusebius": "eusebius-confessor",
+ "05-14/boniface": "boniface-martyr",
+}
+
+// refYearExplainsAbsence is a RANK-BLIND SAMPLING HEURISTIC, not a rubric
+// evaluator: it does not know, and cannot know, the true class of the saint
+// it is being asked about -- only whether the TEMPORAL day alone (no
+// sanctoral data at all) on this one reference date looks privileged enough
+// that a saint failing to win there is unsurprising. It reports true for: a
+// Sunday or a named I/II-class feast, an Ember day, the late-Advent or
+// Christmas-octave privilege (I or II class), or a Lent/Passiontide feria
+// (III class, privileged per RG 109(e)).
+//
+// Two known imprecisions, recorded rather than silently accepted:
+//
+// - The Lent/Passiontide branch is privilege over an EQUAL-OR-LOWER-class
+// candidate only -- RG 109(e) does not let a mere III-class feria beat a
+// I- or II-class feast. Nothing in this codebase's actual data currently
+// exercises that gap (every saint this heuristic has ever been asked
+// about that is demoted throughout Lent is independently III class or
+// lower, per the calendarium), but the function does not itself enforce
+// it, so a future entry could reach it.
+// - It is a proxy for "why was this saint never observed", not a citation.
+// 17-31 December is excluded from ever trusting the id-derived rank
+// below, REGARDLESS of what this heuristic would otherwise say --
+// covering BOTH of round 1's own late-Advent/Christmas-octave rank
+// promotions, on two DIFFERENT strengths of evidence, recorded
+// separately because they are not the same case:
+// -- 26-31 December (RG 68(d)/(e), a positive citation): "die 29
+// decembris, fit commemoratio S. Thomae Episcopi et Mart.; die 31
+// decembris, fit commemoratio S. Silvestri I Papae et Conf." -- the
+// calendarium names each a bare "Commemoratio" with NO class of its
+// own, even though the DAY they fall on is II class (within the
+// Nativity Octave). Two REAL entries (Thomas Becket, Silvester) are
+// live here; see the call site's own comment.
+// -- 17-23 December (RG 91 entry 18, a PRECAUTIONARY exclusion, not a
+// positive citation): round 1 also promoted these late-Advent
+// ferias from class-3 to class-2 (same lineage, same coupling
+// shape as 26-31 December's own promotion). No live entry tests
+// this range today -- the sole sanctoral entry there, `thomas`
+// (21 December), is an OBSERVED class-2 feast reached via the
+// harvestDate `obs` path, not this function's id-rank-trust path
+// at all -- but unlike 26-31 December, there is no RG citation
+// stating that a saint commemorated here has NO independent class;
+// RG 91 entry 18 only ranks the FERIA, and (per defect 2's own
+// finding) a genuine class-2 FEAST commemorated here would
+// actually WIN against it (entry 16 above entry 18), so "the day
+// is class-2" does not reliably explain a class-2 saint's absence
+// the way it does for 26-31 December's own two named cases.
+// Excluded anyway, on the side of the KNOWN-safe default
+// (RankCommemoration) rather than risk repeating the identical
+// failure shape the day this range's own first real entry arrives.
+//
+// This is also a warning about a structural hazard, not just a boundary
+// fix: this function calls calendar.Compute, i.e. it reads the ENGINE's OWN
+// computed temporal ranks to decide what DATA to generate. A change to
+// temporal_ef.go's ranking (e.g. round 1's own 17-23/26-31 December
+// promotions, RG 91 entry 18 / RG 67-68) can silently flip this function's
+// verdict and rewrite generated data with no code change to this file at
+// all -- confirmed by direct measurement, not just reasoned about: running
+// this function's body against the branch-point engine versus the current
+// one over the six reference years flips the verdict on 50 dates across
+// exactly these 13 MM-DD values (12-17 through 12-23, 12-26 through
+// 12-31), no others. Any future temporal_ef.go rank change should re-check
+// this function's own boundary cases, not just its own tests.
+//
+// harvestDate keeps a saint's commemoration id rank ONLY if every reference
+// year in which it was seen returned true here -- a single false (an
+// unprivileged day, or the December exclusion) is enough to fall back to
+// RankCommemoration, the safe default (see St Blaise, whose own
+// commemoration id claims rank 4 and is still correctly overridden to
+// RankCommemoration, proving the id's rank is a candidate, not a verdict --
+// see the call site's own comment).
+func refYearExplainsAbsence(date time.Time) bool {
+ if date.Month() == time.December && date.Day() >= 17 && date.Day() <= 31 {
+ // RG 68(d)/(e) (26-31 Dec) and RG 91 entry 18 (17-23 Dec,
+ // precautionary): "die 29 decembris, fit commemoratio S. Thomae
+ // Episcopi et Mart.; die 31 decembris, fit commemoratio S.
+ // Silvestri I Papae et Conf." -- both named as a bare
+ // "Commemoratio", no class. See this function's own doc comment.
+ return false
+ }
+ sel := calendar.DefaultSelection()
+ sel.Form = "old"
+ day := calendar.Compute(date, sel, nil)
+ if day.Observed.Rank == calendar.RankClass1 || day.Observed.Rank == calendar.RankClass2 {
+ return true
+ }
+ return day.Season == calendar.Lent || day.Season == calendar.Passiontide
+}
+
+// commTracker accumulates one commemoration slug's data across reference
+// years: the entry itself (first sighting's title/colour/id-rank), and
+// whether EVERY year it was seen in was "explained" by refYearExplainsAbsence.
+type commTracker struct {
+ entry entry
+ allExplained bool
+}
+
// harvestDate returns the observed sanctoral office for a fixed MM-DD (nil if
// the date is always a feria/temporal) plus any sancti commemorations seen.
+//
+// Every one of the `years` reference years is scanned for BOTH the observed
+// office and commemorations -- neither loop exits early on the first hit.
+// Two real bugs lived in an earlier version that DID exit early:
+//
+// 1. Returning as soon as the FIRST reference year showed an observed
+// office discarded every commemoration that only showed up in a LATER
+// year (e.g. 9 November: 2025, the first year tried, happens to be the
+// one year of six with no "St. Theodore" commemoration alongside the
+// Dedication of the Archbasilica; a `return` there drops Theodore for
+// good).
+// 2. The tempFeastSkip/Christ-the-King check used to `return nil, comms`
+// outright -- correct for tempFeastSkip's three permanently-fixed dates
+// (every year behaves the same, so nothing is lost), but wrong for
+// Christ the King, which occupies a given MM-DD only in the one
+// reference year it happens to be the last Sunday of October (2025 for
+// 26 October, in this generator's own reference years): returning
+// immediately there discarded "St. Evaristus", visible only in the OTHER
+// five years. `continue` fixes both: the loop keeps trying every
+// remaining year regardless of what any single year showed.
func harvestDate(mmdd string) (*entry, []entry) {
- var comms []entry
- seenComm := map[string]bool{}
+ commTrack := map[string]*commTracker{}
+ var obs *entry
for _, y := range years {
date := fmt.Sprintf("%04d-%s", y, mmdd)
- if _, err := time.Parse("2006-01-02", date); err != nil {
+ refDate, err := time.Parse("2006-01-02", date)
+ if err != nil {
continue // e.g. 02-29 in a common year
}
d, err := fetch(date)
if err != nil {
continue
}
+ explained := refYearExplainsAbsence(refDate)
for _, c := range d.Info.Commemorations {
if !strings.HasPrefix(c.ID, "sancti:") || !isSaintTitle(c.Title) {
continue
}
+ if home := idHomeDate(c.ID); home != "" && home != mmdd {
+ continue // a transferred feast's commemoration, not a genuine one for THIS date
+ }
slug := slugify(c.Title)
- if slug == "" || seenComm[slug] {
+ if slug == "" {
continue
}
- seenComm[slug] = true
- _, col := idParts(c.ID)
- // A saint never OBSERVED in any harvest year — only ever commemorated —
- // is a commemoration in the 1962 universal calendar: it does NOT have
- // its own Mass and yields to the ferial office (which is celebrated with
- // the saint commemorated). Rank it RankCommemoration, not the id's class
- // (missalemeum reuses class-3/4 for these), so a genuine IV class feast
- // still outranks the feria while a commemoration does not. If the same
- // slug is observed in another year, that observed entry supersedes this.
- comms = append(comms, entry{slug: slug, date: mmdd, colour: col, en: c.Title, rank: calendar.RankCommemoration})
+ if t, ok := commTrack[slug]; ok {
+ if !explained {
+ t.allExplained = false
+ }
+ continue // title/colour/id-rank already captured from the first sighting
+ }
+ // The COMMEMORATION object's own id is a BETTER rank signal than
+ // the DAY's own info.id (which names the rank of whatever
+ // propers are reused that day, not the commemorated saint's) --
+ // but "better" is not "always correct": St Blaise's own
+ // commemoration id is "sancti:02-03:4:r" (rank 4), and he is
+ // still, correctly, ruled RankCommemoration below, because he
+ // has no independent Mass at all in the 1960 books, not because
+ // his id's rank is wrong. The id's rank is a CANDIDATE value,
+ // trusted only when refYearExplainsAbsence's heuristic finds no
+ // counter-evidence across every reference year this slug is
+ // seen -- see that function's own doc comment for what the
+ // heuristic actually checks and its known limits.
+ rank, col := idParts(c.ID)
+ commTrack[slug] = &commTracker{
+ entry: entry{slug: slug, date: mmdd, colour: col, en: c.Title, rank: rank},
+ allExplained: explained,
+ }
+ }
+ if obs != nil {
+ continue // already have an observed office; keep scanning other years for MORE commemorations
}
if strings.HasPrefix(d.Info.ID, "sancti:") && isSaintTitle(d.Info.Title) {
+ if home := idHomeDate(d.Info.ID); home != "" && home != mmdd {
+ // A movable-transfer feast displayed on today's civil date in
+ // THIS particular reference year (St Joseph pushed to the
+ // 20th; the Annunciation deferred past Holy Week; All Souls
+ // moved to the Monday; St Matthias shown on the 25th in a
+ // leap year) -- not a genuine fixed office for mmdd itself.
+ // See idHomeDate's own doc comment for the live-verified
+ // evidence. Try the next reference year instead.
+ continue
+ }
// A few Lord's feasts live in missalemeum's sancti namespace but the
// EF temporal engine already computes them (Nativity, Circumcision,
// Epiphany); exclude them so they aren't duplicated in the sanctoral.
@@ -278,26 +594,62 @@ func harvestDate(mmdd string) (*entry, []entry) {
// Christ the King is movable (last Sunday of October) and computed
// by the temporal engine; missalemeum files it under sancti, so it
// would otherwise leak into the sanctoral at a spurious fixed date.
- return nil, comms
+ // `continue`, not `return`: this disqualifies only THIS year's
+ // observed-office candidacy, not the whole date (see doc comment).
+ continue
}
first, gospel := readingsFrom(d)
col := colourOf(strings.Join(d.Info.Colors, ""))
- return &entry{
+ obs = &entry{
slug: slugify(d.Info.Title), date: mmdd, colour: col, class: classOf(d.Info.Title),
en: d.Info.Title, first: first, gospel: gospel,
rank: rankOf(d.Info.Rank), observed: true,
- }, comms
+ }
+ }
+ }
+ comms := make([]entry, 0, len(commTrack))
+ for _, t := range commTrack {
+ e := t.entry
+ if !t.allExplained {
+ // At least one reference year showed this saint demoted even on
+ // an ordinary, unprivileged day -- genuinely commemoration-only
+ // (see refYearExplainsAbsence), not merely unlucky sampling.
+ e.rank = calendar.RankCommemoration
}
+ comms = append(comms, e)
}
- return nil, comms
+ return obs, comms
}
func main() {
- // Existing Latin names to preserve (missalemeum has no Latin titles).
- la := map[string]string{}
+ // preservedNames holds every "name.<lang>" field already present in the
+ // existing file, keyed by slug then by the full field name -- EVERY
+ // language missalemeum does not itself supply (it has English titles
+ // only), not a hardcoded whitelist of one or two. A whitelist is
+ // exactly the bug this replaces: an earlier version of this generator
+ // preserved only name.la, and -- unnoticed, because the bootstrapped
+ // file has in fact never carried a name.la value at all, so that
+ // mechanism was silently inert from the start -- name.pl had no
+ // preservation mechanism whatsoever. A regeneration deleted all 322
+ // Polish names outright (measured: name.pl 322 -> 0), reaching
+ // mobile.Day(date, "ef", version, "pl") -- a shipped dlectio entry
+ // point -- on the app's next build, with `naming.CelebrationName`'s own
+ // name[lang] -> name.en fallback silently substituting English and no
+ // error anywhere. Generalising to every "name.*" key except name.en
+ // (English is always freshly regenerated from missalemeum, the whole
+ // point of this tool) means a third, fourth, or Nth language added to
+ // the file later survives a regeneration without this function ever
+ // needing to change again.
+ preservedNames := map[string]map[string]string{}
for slug, rc := range caldata.Tridentine().Cels {
- if v := rc.Fields["name.la"]; v != "" {
- la[slug] = v
+ for k, v := range rc.Fields {
+ if v == "" || !strings.HasPrefix(k, "name.") || k == "name.en" {
+ continue
+ }
+ if preservedNames[slug] == nil {
+ preservedNames[slug] = map[string]string{}
+ }
+ preservedNames[slug][k] = v
}
}
@@ -331,11 +683,34 @@ func main() {
entries := map[string]entry{}
add := func(e entry) {
if cur, ok := entries[e.slug]; ok {
- if cur.observed && !e.observed {
- return // don't let a commemoration downgrade an observed office
- }
- if cur.observed && e.observed {
- return // first observed year wins
+ if cur.date == e.date {
+ // Same fixed date: this is the SAME feast, seen again in
+ // another reference year or pass -- the existing dedup rules
+ // apply (an observed office is never downgraded by a
+ // commemoration; the first observed year wins).
+ if cur.observed && !e.observed {
+ return
+ }
+ if cur.observed && e.observed {
+ return
+ }
+ } else {
+ // A DIFFERENT fixed date slugified to the identical string
+ // (e.g. "St. Boniface" on both 14 May and 5 June, or "St.
+ // Agnes" on both 21 and 28 January) -- two distinct
+ // celebrations, not the same one recurring. The map is keyed
+ // by slug, so silently keeping the first and dropping the
+ // second here is exactly how St Agnes secundo (28 Jan), St
+ // Boniface Martyr (14 May), and their like went missing
+ // before this fix. Disambiguate instead of dropping.
+ if ov, ok := slugOverride[e.date+"/"+e.slug]; ok {
+ e.slug = ov
+ } else {
+ e.slug = e.slug + "-" + strings.ReplaceAll(e.date, "-", "")
+ }
+ if _, stillCollides := entries[e.slug]; stillCollides {
+ return // extremely unlikely second collision; drop rather than clobber
+ }
}
}
entries[e.slug] = e
@@ -347,17 +722,13 @@ func main() {
}
for _, r := range results { // commemorations after, so observed offices win
for _, c := range r.comms {
- if _, ok := entries[c.slug]; !ok {
- add(c)
- }
+ add(c)
}
}
es := make([]entry, 0, len(entries))
for _, e := range entries {
- if l := la[e.slug]; l != "" {
- e.la = l
- }
+ e.otherNames = preservedNames[e.slug]
es = append(es, e)
}
sort.Slice(es, func(i, j int) bool {
@@ -374,7 +745,9 @@ func main() {
b.WriteString("; by internal/calendar (temporalEF) and is NOT listed here.\n")
b.WriteString("; Ranks use the 1960 Code of Rubrics: class-1..class-4.\n")
b.WriteString("; Generated by scripts/gen-sanctoral-ef.go from missalemeum (Divinum Officium 1962\n")
- b.WriteString("; data). Latin names are hand-curated where present. See NOTICE.\n\n")
+ b.WriteString("; data). name.en is always regenerated fresh from missalemeum; every other\n")
+ b.WriteString("; name.<lang> (missalemeum supplies English only) is preserved verbatim from\n")
+ b.WriteString("; whatever this file already carried before regeneration. See NOTICE.\n\n")
b.WriteString("[layer]\nid = tridentine\nname = General Roman Calendar of 1962\ntype = universal\n")
for _, e := range es {
fmt.Fprintf(&b, "\n[%s]\ndate = %s\nrank = %s\ncolour = %s\n", e.slug, e.date, e.rank, e.colour)
@@ -382,8 +755,13 @@ func main() {
fmt.Fprintf(&b, "class = %s\n", e.class)
}
fmt.Fprintf(&b, "name.en = %s\n", e.en)
- if e.la != "" {
- fmt.Fprintf(&b, "name.la = %s\n", e.la)
+ otherKeys := make([]string, 0, len(e.otherNames))
+ for k := range e.otherNames {
+ otherKeys = append(otherKeys, k)
+ }
+ sort.Strings(otherKeys) // deterministic output regardless of map iteration order
+ for _, k := range otherKeys {
+ fmt.Fprintf(&b, "%s = %s\n", k, e.otherNames[k])
}
if e.first != "" {
fmt.Fprintf(&b, "reading.first = %s\n", e.first)