summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 23:14:13 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 23:14:13 +0200
commitd201028d71ad3f26d5ec0b9c12268475e0b70cfd (patch)
treead89916625d650ce8e3439ce18341a51447a0010
parenta2fba71d8cccc765c4a987df99fa780f5467514d (diff)
downloadcolitur-d201028d71ad3f26d5ec0b9c12268475e0b70cfd.tar.gz
colitur-d201028d71ad3f26d5ec0b9c12268475e0b70cfd.zip
feat(of): transcribe the 2002 General Roman Calendar
Extracted from the Latin typical edition, which is the authority; lectio's roman-calendar.ini is a cross-check only and every divergence is adjudicated against the Missal in the provenance header. A blank grade column means Memoria ad libitum, per the calendar's own footnote, rather than a missing value. 206 entries; 1 January, 6 January and 25 December are deliberately excluded because Temporal_of.named already computes them, and shipping them here too would create a duplicate candidate for the same day. Colour is derived from IGMR n. 346, cited per class in the extractor; the lectio cross-check surfaces one rank divergence (22 July, Mary Magdalene -- lectio reflects the 2016 post-2002 elevation decree, out of scope here) and 17 colour divergences, all adjudicated in the provenance header.
-rw-r--r--data/of/calendar-2002.sexp1294
-rw-r--r--test/dune1
-rw-r--r--test/test_calendar_of_data.ml207
-rw-r--r--test/test_colitur.ml3
-rw-r--r--tools/extract_of_calendar.py677
5 files changed, 2181 insertions, 1 deletions
diff --git a/data/of/calendar-2002.sexp b/data/of/calendar-2002.sexp
new file mode 100644
index 0000000..4972f0d
--- /dev/null
+++ b/data/of/calendar-2002.sexp
@@ -0,0 +1,1294 @@
+; data/of/calendar-2002.sexp -- OF (post-1970) General Roman Calendar,
+; transcribed from the Missale Romanum, editio typica tertia (2002). The
+; 2002 Missal is the AUTHORITY; this file is never edited afterwards --
+; spec sec4.1. lectio's roman-calendar.ini is a cross-check only, never a
+; source (see tools/extract_of_calendar.py's own header for the full
+; source-discipline statement and every classification citation).
+;
+; Generator: tools/extract_of_calendar.py -- do not hand-edit; re-run against the same
+; PDF (this file is pinned, so a re-run should reproduce it byte-for-byte)
+; and diff before committing.
+;
+; Source: docs/research/of/missale-romanum-2002.pdf
+; SHA-256: 0b458944824d2ee92854b9664f83e553af0342e0b15a9dc14b00e846405523d7
+; Extracted lines (pdftotext -layout, 0-based): 3992-4461
+; Extraction date (UTC): 2026-08-25
+;
+; 206 entries. 1 entry needed letter-spacing repair
+; (collapse_letterspacing's own mechanical pass, then hand-verified -- see
+; the module docstring's EXTRACTION ARTIFACT note).
+;
+; Excluded, deliberately (see module docstring for the full reasoning):
+; 01-01: of-mary-mother-of-god (Temporal_of.named, m=1 dd=1)
+; 01-06: of-epiphany (Temporal_of.named, m=1 dd=6)
+; 12-25: of-nativity (Temporal_of.named, m=12 dd=25)
+; Movable universal solemnities (Baptism of the Lord, Holy Family,
+; Trinity, Corpus Christi, Christ the King -- Temporal_of code; the
+; Sacred Heart of Jesus and the Immaculate Heart of Mary -- present in
+; the 2002 table but covered by NEITHER Temporal_of NOR this file, a
+; genuine gap this task found and reports but does not fix).
+;
+; lectio cross-check: 206 dates paired; rank agrees on 205/206.
+; 03-07 COLOUR colitur=Red lectio=Violet ['Ss. Perpetuæ et Felicitatis, martyrum']
+; 03-08 COLOUR colitur=White lectio=Violet ['S. Ioannis a Deo, religiosi']
+; 03-09 COLOUR colitur=White lectio=Violet ['S. Franciscæ Romanæ, religiosæ']
+; 03-17 COLOUR colitur=White lectio=Violet ['S. Patricii, episcopi']
+; 03-18 COLOUR colitur=White lectio=Violet ['S. Cyrilli Hierosolymitani, episcopi et Ecclesiæ doctoris']
+; 03-23 COLOUR colitur=White lectio=Violet ['S. Turibii de Mogrovejo, episcopi']
+; 04-02 COLOUR colitur=White lectio=Violet ['S. Francisci de Paola, eremitæ']
+; 04-04 COLOUR colitur=White lectio=Violet ['S. Isidori, episcopi et Ecclesiæ doctoris']
+; 04-05 COLOUR colitur=White lectio=Violet ['S. Vincentii Ferrer, presbyteri']
+; 07-22 RANK colitur=Memoria_obligatoria lectio=Festum ['S. Mariæ Magdalenæ' vs 'S. Mariae Magdalenae']
+; 07-29 COLOUR colitur=White lectio=Red ['S. Marthæ']
+; 11-02 COLOUR colitur=Violet lectio=White ['IN COMMEMORATIONE OMNIUM FIDELIUM DEFUNCTORUM']
+; 11-03 COLOUR colitur=White lectio=Red ['S. Martini de Porres, religiosi']
+; 11-11 COLOUR colitur=White lectio=Red ['S. Martini Turonensis, episcopi']
+; 12-21 COLOUR colitur=White lectio=Violet ['S. Petri Canisii, presbyteri et Ecclesiæ doctoris']
+; 12-23 COLOUR colitur=White lectio=Violet ['S. Ioannis de Ke¸ty, presbyteri']
+; 12-29 COLOUR colitur=Red lectio=White ['S. Thomæ Becket, episcopi et martyris']
+; 09-23 LECTIO-ONLY 'S. Pii de Pietrelcina, presbyteri [*]' rank=memorial slug=pio-of-pietrelcina-padre-pio-priest
+; 10-11 LECTIO-ONLY 'S. Ioannis XXIII, papae' rank=optional slug=john-xxiii-pope
+; 10-22 LECTIO-ONLY 'S. Ioannis Pauli II, papae' rank=optional slug=john-paul-ii-pope
+; 12-09 LECTIO-ONLY 'S. Ioannis Didaci Cuahtlatoatzin [*]' rank=optional slug=juan-diego
+; 12-12 LECTIO-ONLY 'Beatae Mariae Virginis de Guadalupe [*]' rank=optional slug=our-lady-of-guadalupe
+
+((id of-universal) (name "OF (2002) General Roman Calendar")
+ (entries
+ (
+ ((date (Fixed (month 4) (day 23)))
+ (cel
+ ((slug adalbert-bishop-and-martyr)
+ (names ((en "Saint Adalbert, bishop and martyr") (la "S. Adalberti, episcopi et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 5)))
+ (cel
+ ((slug agatha-virgin-and-martyr)
+ (names ((en "Saint Agatha, virgin and martyr") (la "S. Agathae, virginis et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 21)))
+ (cel
+ ((slug agnes-virgin-and-martyr)
+ (names ((en "Saint Agnes, virgin and martyr") (la "S. Agnetis, virginis et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 15)))
+ (cel
+ ((slug albert-the-great-bishop-and-doctor)
+ (names ((en "Saint Albert the Great, bishop and doctor") (la "S. Alberti Magni, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 1)))
+ (cel
+ ((slug all-saints)
+ (names ((en "All Saints") (la "OMNIUM SANCTORUM")))
+ (rank Sollemnitas) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 2)))
+ (cel
+ ((slug all-souls)
+ (names ((en "All Souls") (la "IN COMMEMORATIONE OMNIUM FIDELIUM DEFUNCTORUM")))
+ (rank Sollemnitas) (status Feast) (colour Violet)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 21)))
+ (cel
+ ((slug aloysius-gonzaga-religious)
+ (names ((en "Saint Aloysius Gonzaga, religious") (la "S. Aloisii Gonzaga, religiosi")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 1)))
+ (cel
+ ((slug alphonsus-maria-de-liguori-bishop-and-doctor-of-the-church)
+ (names ((en "Saint Alphonsus Maria de Liguori, bishop and doctor of the Church") (la "S. Alfonsi Mariae de’ Liguori, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 7)))
+ (cel
+ ((slug ambrose-bishop-and-doctor)
+ (names ((en "Saint Ambrose, bishop and doctor") (la "S. Ambrosii, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 24)))
+ (cel
+ ((slug andrew-dung-lac-and-his-companions-martyrs)
+ (names ((en "Saint Andrew Dung Lac and his companions, martyrs") (la "Ss. Andreae Du˜ ng La.c, presbyteri, et sociorum, martyrum")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 20)))
+ (cel
+ ((slug andrew-kim-taegon-priest-and-paul-chong-hasang-and-companions-martyrs)
+ (names ((en "Saint Andrew Kim Taegon, priest, and Paul Chong Hasang and companions, martyrs") (la "Ss. Andreae Kim Tae-go¡ n, presbyteri, Pauli Cho¡ ng Ha-sang et sociorum, martyrum")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 30)))
+ (cel
+ ((slug andrew-the-apostle)
+ (names ((en "Saint Andrew the Apostle") (la "S. Andreae, apostoli")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 27)))
+ (cel
+ ((slug angela-merici-virgin)
+ (names ((en "Saint Angela Merici, virgin") (la "S. Angelae Merici, virginis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 25)))
+ (cel
+ ((slug annunciation-of-the-lord)
+ (names ((en "Annunciation of the Lord") (la "IN ANNUNTIATIONE DOMINI")))
+ (rank Sollemnitas) (status Feast) (colour White)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 21)))
+ (cel
+ ((slug anselm-of-canterbury-bishop-and-doctor-of-the-church)
+ (names ((en "Saint Anselm of Canterbury, bishop and doctor of the Church") (la "S. Anselmi, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 3)))
+ (cel
+ ((slug ansgar-bishop)
+ (names ((en "Saint Ansgar, bishop") (la "S. Ansgarii, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 24)))
+ (cel
+ ((slug anthony-mary-claret-bishop)
+ (names ((en "Saint Anthony Mary Claret, bishop") (la "S. Antonii Mariae Claret, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 17)))
+ (cel
+ ((slug anthony-of-egypt-abbot)
+ (names ((en "Saint Anthony of Egypt, abbot") (la "S. Antonii, abbatis")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 13)))
+ (cel
+ ((slug anthony-of-padua-priest-and-doctor)
+ (names ((en "Saint Anthony of Padua, priest and doctor") (la "S. Antonii de Padova, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 5)))
+ (cel
+ ((slug anthony-zaccaria-priest)
+ (names ((en "Saint Anthony Zaccaria, priest") (la "S. Antonii Mariae Zaccaria, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 20)))
+ (cel
+ ((slug apollinaris-bishop-and-martyr)
+ (names ((en "Saint Apollinaris, bishop and martyr") (la "S. Apollinaris, episcopi et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 15)))
+ (cel
+ ((slug assumption-of-the-blessed-virgin-mary)
+ (names ((en "Assumption of the Blessed Virgin Mary") (la "IN ASSUMPTIONE BEATAE MARIAE VIRGINIS")))
+ (rank Sollemnitas) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 2)))
+ (cel
+ ((slug athanasius-bishop-and-doctor)
+ (names ((en "Saint Athanasius, bishop and doctor") (la "S. Athanasii, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 27)))
+ (cel
+ ((slug augustine-austin-of-canterbury-bishop)
+ (names ((en "Saint Augustine (Austin) of Canterbury, bishop") (la "S. Augustini Cantuariensis, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 28)))
+ (cel
+ ((slug augustine-of-hippo-bishop-and-doctor-of-the-church)
+ (names ((en "Saint Augustine of Hippo, bishop and doctor of the Church") (la "S. Augustini, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 9)))
+ (cel
+ ((slug augustine-zhao-rong-and-companions-martyrs)
+ (names ((en "Saint Augustine Zhao Rong and companions, martyrs") (la "Ss. Augustini Zhao Rong, presbyteri, et sociorum, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 11)))
+ (cel
+ ((slug barnabas-the-apostle)
+ (names ((en "Saint Barnabas the Apostle") (la "S. Barnabae, apostoli")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 24)))
+ (cel
+ ((slug bartholomew-the-apostle)
+ (names ((en "Saint Bartholomew the Apostle") (la "S. Bartholomaei, apostoli")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 25)))
+ (cel
+ ((slug bede-the-venerable-priest-and-doctor)
+ (names ((en "Saint Bede the Venerable, priest and doctor") (la "S. Bedae Venerabilis, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 11)))
+ (cel
+ ((slug benedict-abbot)
+ (names ((en "Saint Benedict, abbot") (la "S. Benedicti, abbatis")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 20)))
+ (cel
+ ((slug bernard-of-clairvaux-abbot-and-doctor-of-the-church)
+ (names ((en "Saint Bernard of Clairvaux, abbot and doctor of the Church") (la "S. Bernardi, abbatis et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 20)))
+ (cel
+ ((slug bernardine-of-siena-priest)
+ (names ((en "Saint Bernardine of Siena, priest") (la "S. Bernardini Senensis, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 23)))
+ (cel
+ ((slug birgitta-religious)
+ (names ((en "Saint Birgitta, religious") (la "S. Birgittae, religiosae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 24)))
+ (cel
+ ((slug birth-of-saint-john-the-baptist)
+ (names ((en "Birth of Saint John the Baptist") (la "IN NATIVITATE S. IOANNIS BAPTISTAE")))
+ (rank Sollemnitas) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 8)))
+ (cel
+ ((slug birth-of-the-blessed-virgin-mary)
+ (names ((en "Birth of the Blessed Virgin Mary") (la "In Nativitate B. Mariae Virginis")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 3)))
+ (cel
+ ((slug blase-bishop-and-martyr)
+ (names ((en "Saint Blase, bishop and martyr") (la "S. Blasii, episcopi et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 15)))
+ (cel
+ ((slug bonaventure-bishop-and-doctor)
+ (names ((en "Saint Bonaventure, bishop and doctor") (la "S. Bonaventurae, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 5)))
+ (cel
+ ((slug boniface-bishop-and-martyr)
+ (names ((en "Saint Boniface, bishop and martyr") (la "S. Bonifatii, episcopi et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 6)))
+ (cel
+ ((slug bruno-priest)
+ (names ((en "Saint Bruno, priest") (la "S. Brunonis, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 7)))
+ (cel
+ ((slug cajetan-priest)
+ (names ((en "Saint Cajetan, priest") (la "S. Caietani, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 14)))
+ (cel
+ ((slug callistus-i-pope-and-martyr)
+ (names ((en "Saint Callistus I, pope and martyr") (la "S. Callisti I, papae et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 14)))
+ (cel
+ ((slug camillus-de-lellis-priest)
+ (names ((en "Saint Camillus de Lellis, priest") (la "S. Camilli de Lellis, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 4)))
+ (cel
+ ((slug casimir)
+ (names ((en "Saint Casimir") (la "S. Casimiri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 25)))
+ (cel
+ ((slug catherine-of-alexandria)
+ (names ((en "Saint Catherine of Alexandria") (la "S. Catharinae Alexandrinae, virginis et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 29)))
+ (cel
+ ((slug catherine-of-siena-virgin-and-doctor-of-the-church)
+ (names ((en "Saint Catherine of Siena, virgin and doctor of the Church") (la "S. Catharinae Senensis, virginis et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 22)))
+ (cel
+ ((slug cecilia-virgin-and-martyr)
+ (names ((en "Saint Cecilia, virgin and martyr") (la "S. Caeciliae, virginis et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 22)))
+ (cel
+ ((slug chair-of-saint-peter-apostle)
+ (names ((en "Chair of Saint Peter, apostle") (la "Cathedrae S. Petri, apostoli")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 4)))
+ (cel
+ ((slug charles-borromeo-bishop)
+ (names ((en "Saint Charles Borromeo, bishop") (la "S. Caroli Borromeo, episcopi")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 21)))
+ (cel
+ ((slug christopher-magallanes-and-companions-martyrs)
+ (names ((en "Saint Christopher Magallanes and companions, martyrs") (la "Ss. Christophori Magallanes, presbyteri, et sociorum, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 11)))
+ (cel
+ ((slug clare-virgin)
+ (names ((en "Saint Clare, virgin") (la "S. Clarae, virginis")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 23)))
+ (cel
+ ((slug clement-i-pope-and-martyr)
+ (names ((en "Saint Clement I, pope and martyr") (la "S. Clementis I, papae et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 23)))
+ (cel
+ ((slug columban-religious)
+ (names ((en "Saint Columban, religious") (la "S. Columbani, abbatis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 27)))
+ (cel
+ ((slug cyril-of-alexandria-bishop-and-doctor)
+ (names ((en "Saint Cyril of Alexandria, bishop and doctor") (la "S. Cyrilli Alexandrini, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 18)))
+ (cel
+ ((slug cyril-of-jerusalem-bishop-and-doctor)
+ (names ((en "Saint Cyril of Jerusalem, bishop and doctor") (la "S. Cyrilli Hierosolymitani, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 11)))
+ (cel
+ ((slug damasus-i-pope)
+ (names ((en "Saint Damasus I, pope") (la "S. Damasi I, papae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 5)))
+ (cel
+ ((slug dedication-of-the-basilica-of-saint-mary-major)
+ (names ((en "Dedication of the Basilica of Saint Mary Major") (la "In Dedicatione basilicae S. Mariae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 18)))
+ (cel
+ ((slug dedication-of-the-basilicas-of-saints-peter-and-paul-apostles)
+ (names ((en "Dedication of the basilicas of Saints Peter and Paul, Apostles") (la "In Dedicatione basilicarum Ss. Petri et Pauli, apostolorum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 9)))
+ (cel
+ ((slug dedication-of-the-lateran-basilica)
+ (names ((en "Dedication of the Lateran basilica") (la "In Dedicatione basilicae Lateranensis")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 9)))
+ (cel
+ ((slug denis-and-companions-martyrs)
+ (names ((en "Saint Denis and companions, martyrs") (la "Ss. Dionysii, episcopi, et sociorum, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 8)))
+ (cel
+ ((slug dominic-priest)
+ (names ((en "Saint Dominic, priest") (la "S. Dominici, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 17)))
+ (cel
+ ((slug elizabeth-of-hungary-religious)
+ (names ((en "Saint Elizabeth of Hungary, religious") (la "S. Elisabeth Hungariae, religiosae")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 4)))
+ (cel
+ ((slug elizabeth-of-portugal)
+ (names ((en "Saint Elizabeth of Portugal") (la "S. Elisabeth Lusitaniae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 9)))
+ (cel
+ ((slug ephrem-deacon-and-doctor)
+ (names ((en "Saint Ephrem, deacon and doctor") (la "S. Ephraem, diaconi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 2)))
+ (cel
+ ((slug eusebius-of-vercelli-bishop)
+ (names ((en "Saint Eusebius of Vercelli, bishop") (la "S. Eusebii Vercellensis, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 20)))
+ (cel
+ ((slug fabian-pope-and-martyr)
+ (names ((en "Saint Fabian, pope and martyr") (la "S. Fabiani, papae et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 24)))
+ (cel
+ ((slug fidelis-of-sigmaringen-priest-and-martyr)
+ (names ((en "Saint Fidelis of Sigmaringen, priest and martyr") (la "S. Fidelis de Sigmaringen, presbyteri et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 30)))
+ (cel
+ ((slug first-martyrs-of-the-church-of-rome)
+ (names ((en "First Martyrs of the Church of Rome") (la "Ss. Protomartyrum S. Romanae Ecclesiae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 9)))
+ (cel
+ ((slug frances-of-rome-religious)
+ (names ((en "Saint Frances of Rome, religious") (la "S. Franciscae Romanae, religiosae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 24)))
+ (cel
+ ((slug francis-de-sales-bishop-and-doctor)
+ (names ((en "Saint Francis de Sales, bishop and doctor") (la "S. Francisci de Sales, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 4)))
+ (cel
+ ((slug francis-of-assisi)
+ (names ((en "Saint Francis of Assisi") (la "S. Francisci Assisiensis")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 2)))
+ (cel
+ ((slug francis-of-paola-hermit)
+ (names ((en "Saint Francis of Paola, hermit") (la "S. Francisci de Paola, eremitae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 3)))
+ (cel
+ ((slug francis-xavier-priest)
+ (names ((en "Saint Francis Xavier, priest") (la "S. Francisci Xavier, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 23)))
+ (cel
+ ((slug george-martyr)
+ (names ((en "Saint George, martyr") (la "S. Georgii, martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 16)))
+ (cel
+ ((slug gertrude-the-great-virgin)
+ (names ((en "Saint Gertrude the Great, virgin") (la "S. Gertrudis, virginis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 3)))
+ (cel
+ ((slug gregory-the-great-pope-and-doctor)
+ (names ((en "Saint Gregory the Great, pope and doctor") (la "S. Gregorii Magni, papae et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 25)))
+ (cel
+ ((slug gregory-vii-pope)
+ (names ((en "Saint Gregory VII, pope") (la "S. Gregorii VII, papae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 2)))
+ (cel
+ ((slug guardian-angels)
+ (names ((en "Guardian Angels") (la "Ss. Angelorum Custodum")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 16)))
+ (cel
+ ((slug hedwig-religious)
+ (names ((en "Saint Hedwig, religious") (la "S. Hedvigis, religiosae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 13)))
+ (cel
+ ((slug henry)
+ (names ((en "Saint Henry") (la "S. Henrici")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 13)))
+ (cel
+ ((slug hilary-of-poitiers-bishop-and-doctor)
+ (names ((en "Saint Hilary of Poitiers, bishop and doctor") (la "S. Hilarii, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 28)))
+ (cel
+ ((slug holy-innocents-martyrs)
+ (names ((en "Holy Innocents, martyrs") (la "Ss. Innocentium, martyrum")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 12)))
+ (cel
+ ((slug holy-name-of-the-blessed-virgin-mary)
+ (names ((en "Holy Name of the Blessed Virgin Mary") (la "Ss.mi Nominis Mariae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 17)))
+ (cel
+ ((slug ignatius-of-antioch-bishop-and-martyr)
+ (names ((en "Saint Ignatius of Antioch, bishop and martyr") (la "S. Ignatii Antiocheni, episcopi et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 31)))
+ (cel
+ ((slug ignatius-of-loyola-priest)
+ (names ((en "Saint Ignatius of Loyola, priest") (la "S. Ignatii de Loyola, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 8)))
+ (cel
+ ((slug immaculate-conception-of-the-blessed-virgin-mary)
+ (names ((en "Immaculate Conception of the Blessed Virgin Mary") (la "IN CONCEPTIONE IMMACULATA BEATAE MARIAE VIRGINIS")))
+ (rank Sollemnitas) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 28)))
+ (cel
+ ((slug irenaeus-bishop-and-martyr)
+ (names ((en "Saint Irenaeus, bishop and martyr") (la "S. Irenaei, episcopi et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 4)))
+ (cel
+ ((slug isidore-bishop-and-doctor-of-the-church)
+ (names ((en "Saint Isidore, bishop and doctor of the Church") (la "S. Isidori, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 25)))
+ (cel
+ ((slug james-apostle)
+ (names ((en "Saint James, apostle") (la "S. Iacobi, apostoli")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 12)))
+ (cel
+ ((slug jane-frances-de-chantal-religious)
+ (names ((en "Saint Jane Frances de Chantal, religious") (la "S. Ioannae Franciscae de Chantal, religiosae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 19)))
+ (cel
+ ((slug januarius-bishop-and-martyr)
+ (names ((en "Saint Januarius, bishop and martyr") (la "S. Ianuarii, episcopi et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 4)))
+ (cel
+ ((slug jean-vianney-the-cure-of-ars-priest)
+ (names ((en "Saint Jean Vianney (the Curé of Ars), priest") (la "S. Ioannis Mariae Vianney, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 8)))
+ (cel
+ ((slug jerome-emiliani-priest)
+ (names ((en "Saint Jerome Emiliani, priest") (la "S. Hieronymi Emiliani")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 30)))
+ (cel
+ ((slug jerome-priest-and-doctor)
+ (names ((en "Saint Jerome, priest and doctor") (la "S. Hieronymi, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 7)))
+ (cel
+ ((slug john-baptist-de-la-salle-priest)
+ (names ((en "Saint John Baptist de la Salle, priest") (la "S. Ioannis Baptistae de la Salle, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 31)))
+ (cel
+ ((slug john-bosco-priest)
+ (names ((en "Saint John Bosco, priest") (la "S. Ioannis Bosco, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 13)))
+ (cel
+ ((slug john-chrysostom-bishop-and-doctor)
+ (names ((en "Saint John Chrysostom, bishop and doctor") (la "S. Ioannis Chrysostomi, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 4)))
+ (cel
+ ((slug john-damascene-priest-and-doctor)
+ (names ((en "Saint John Damascene, priest and doctor") (la "S. Ioannis Damasceni, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 19)))
+ (cel
+ ((slug john-eudes-priest)
+ (names ((en "Saint John Eudes, priest") (la "S. Ioannis Eudes, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 18)))
+ (cel
+ ((slug john-i-pope-and-martyr)
+ (names ((en "Saint John I, pope and martyr") (la "S. Ioannis I, papae et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 9)))
+ (cel
+ ((slug john-leonardi-priest)
+ (names ((en "Saint John Leonardi, priest") (la "S. Ioannis Leonardi, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 23)))
+ (cel
+ ((slug john-of-capistrano-priest)
+ (names ((en "Saint John of Capistrano, priest") (la "S. Ioannis de Capestrano, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 8)))
+ (cel
+ ((slug john-of-god-religious)
+ (names ((en "Saint John of God, religious") (la "S. Ioannis a Deo, religiosi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 23)))
+ (cel
+ ((slug john-of-kanty-priest)
+ (names ((en "Saint John of Kanty, priest") (la "S. Ioannis de Ke¸ty, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 14)))
+ (cel
+ ((slug john-of-the-cross-priest-and-doctor)
+ (names ((en "Saint John of the Cross, priest and doctor") (la "S. Ioannis a Cruce, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 27)))
+ (cel
+ ((slug john-the-apostle-and-evangelist)
+ (names ((en "Saint John the Apostle and evangelist") (la "S. Ioannis, apostoli et evangelistae")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 12)))
+ (cel
+ ((slug josaphat-bishop-and-martyr)
+ (names ((en "Saint Josaphat, bishop and martyr") (la "S. Iosaphat, episcopi et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 19)))
+ (cel
+ ((slug joseph-husband-of-the-blessed-virgin-mary)
+ (names ((en "Saint Joseph Husband of the Blessed Virgin Mary") (la "S. IOSEPH, SPONSI B. M. V.")))
+ (rank Sollemnitas) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 25)))
+ (cel
+ ((slug joseph-of-calasanz-priest)
+ (names ((en "Saint Joseph of Calasanz, priest") (la "S. Iosephi de Calasanz, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 1)))
+ (cel
+ ((slug joseph-the-worker)
+ (names ((en "Saint Joseph the Worker") (la "S. Ioseph Opificis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 8)))
+ (cel
+ ((slug josephine-bakhita-virgin)
+ (names ((en "Saint Josephine Bakhita, virgin") (la "S. Iosephinae Bakhita, virginis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 1)))
+ (cel
+ ((slug justin-martyr)
+ (names ((en "Saint Justin Martyr") (la "S. Iustini, martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 10)))
+ (cel
+ ((slug lawrence-deacon-and-martyr)
+ (names ((en "Saint Lawrence, deacon and martyr") (la "S. Laurentii, diaconi et martyris")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 21)))
+ (cel
+ ((slug lawrence-of-brindisi-priest-and-doctor)
+ (names ((en "Saint Lawrence of Brindisi, priest and doctor") (la "S. Laurentii de Brindisi, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 10)))
+ (cel
+ ((slug leo-the-great-pope-and-doctor)
+ (names ((en "Saint Leo the Great, pope and doctor") (la "S. Leonis Magni, papae et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 25)))
+ (cel
+ ((slug louis)
+ (names ((en "Saint Louis") (la "S. Ludovici")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 28)))
+ (cel
+ ((slug louis-grignon-de-montfort-priest)
+ (names ((en "Saint Louis Grignon de Montfort, priest") (la "S. Ludovici Mariae Grignion de Montfort, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 13)))
+ (cel
+ ((slug lucy-of-syracuse-virgin-and-martyr)
+ (names ((en "Saint Lucy of Syracuse, virgin and martyr") (la "S. Luciae, virginis et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 18)))
+ (cel
+ ((slug luke-the-evangelist)
+ (names ((en "Saint Luke the Evangelist") (la "S. Lucae, evangelistae")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 16)))
+ (cel
+ ((slug margaret-mary-alacoque-virgin)
+ (names ((en "Saint Margaret Mary Alacoque, virgin") (la "S. Margaritae Mariae Alacoque, virginis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 16)))
+ (cel
+ ((slug margaret-of-scotland)
+ (names ((en "Saint Margaret of Scotland") (la "S. Margaritae Scotiae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 6)))
+ (cel
+ ((slug maria-goretti-virgin-and-martyr)
+ (names ((en "Saint Maria Goretti, virgin and martyr") (la "S. Mariae Goretti, virginis et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 25)))
+ (cel
+ ((slug mark-the-evangelist)
+ (names ((en "Saint Mark the Evangelist") (la "S. Marci, evangelistae")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 29)))
+ (cel
+ ((slug martha)
+ (names ((en "Saint Martha") (la "S. Marthae")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 3)))
+ (cel
+ ((slug martin-de-porres-religious)
+ (names ((en "Saint Martin de Porres, religious") (la "S. Martini de Porres, religiosi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 13)))
+ (cel
+ ((slug martin-i-pope-and-martyr)
+ (names ((en "Saint Martin I, pope and martyr") (la "S. Martini I, papae et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 11)))
+ (cel
+ ((slug martin-of-tours-bishop)
+ (names ((en "Saint Martin of Tours, bishop") (la "S. Martini Turonensis, episcopi")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 22)))
+ (cel
+ ((slug mary-magdalene)
+ (names ((en "Saint Mary Magdalene") (la "S. Mariae Magdalenae")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 25)))
+ (cel
+ ((slug mary-magdalene-de-pazzi-virgin)
+ (names ((en "Saint Mary Magdalene de Pazzi, virgin") (la "S. Mariae Magdalenae de’ Pazzi, virginis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 21)))
+ (cel
+ ((slug matthew-the-evangelist-apostle-evangelist)
+ (names ((en "Saint Matthew the Evangelist, Apostle, Evangelist") (la "S. Matthaei, apostoli et evangelistae")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 14)))
+ (cel
+ ((slug matthias-the-apostle)
+ (names ((en "Saint Matthias the Apostle") (la "S. Matthiae, apostoli")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 14)))
+ (cel
+ ((slug maximilian-mary-kolbe-priest-and-martyr)
+ (names ((en "Saint Maximilian Mary Kolbe, priest and martyr") (la "S. Maximiliani Mariae Kolbe, presbyteri et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 27)))
+ (cel
+ ((slug monica)
+ (names ((en "Saint Monica") (la "S. Monicae")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 6)))
+ (cel
+ ((slug nicholas-bishop)
+ (names ((en "Saint Nicholas, bishop") (la "S. Nicolai, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 6)))
+ (cel
+ ((slug norbert-bishop)
+ (names ((en "Saint Norbert, bishop") (la "S. Norberti, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 13)))
+ (cel
+ ((slug our-lady-of-fatima)
+ (names ((en "Our Lady of Fatima") (la "B. Mariae Virginis de Fatima")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 11)))
+ (cel
+ ((slug our-lady-of-lourdes)
+ (names ((en "Our Lady of Lourdes") (la "B. Mariae Virginis de Lourdes")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 16)))
+ (cel
+ ((slug our-lady-of-mount-carmel)
+ (names ((en "Our Lady of Mount Carmel") (la "B. Mariae Virginis de Monte Carmelo")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 15)))
+ (cel
+ ((slug our-lady-of-sorrows)
+ (names ((en "Our Lady of Sorrows") (la "B. Mariae Virginis Perdolentis")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 7)))
+ (cel
+ ((slug our-lady-of-the-rosary)
+ (names ((en "Our Lady of the Rosary") (la "B. Mariae Virginis a Rosario")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 12)))
+ (cel
+ ((slug pancras-martyr)
+ (names ((en "Saint Pancras, martyr") (la "S. Pancratii, martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 17)))
+ (cel
+ ((slug patrick-bishop)
+ (names ((en "Saint Patrick, bishop") (la "S. Patricii, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 19)))
+ (cel
+ ((slug paul-of-the-cross-priest)
+ (names ((en "Saint Paul of the Cross, priest") (la "S. Pauli a Cruce, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 22)))
+ (cel
+ ((slug paulinus-of-nola-bishop)
+ (names ((en "Saint Paulinus of Nola, bishop") (la "S. Paulini Nolani, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 21)))
+ (cel
+ ((slug peter-canisius-priest-and-doctor)
+ (names ((en "Saint Peter Canisius, priest and doctor") (la "S. Petri Canisii, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 28)))
+ (cel
+ ((slug peter-chanel-priest-and-martyr)
+ (names ((en "Saint Peter Chanel, priest and martyr") (la "S. Petri Chanel, presbyteri et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 30)))
+ (cel
+ ((slug peter-chrysologus-bishop-and-doctor)
+ (names ((en "Saint Peter Chrysologus, bishop and doctor") (la "S. Petri Chrysologi, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 9)))
+ (cel
+ ((slug peter-claver-priest)
+ (names ((en "Saint Peter Claver, priest") (la "S. Petri Claver, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 21)))
+ (cel
+ ((slug peter-damian-bishop-and-doctor-of-the-church)
+ (names ((en "Saint Peter Damian, bishop and doctor of the Church") (la "S. Petri Damiani, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 2)))
+ (cel
+ ((slug peter-julian-eymard-priest)
+ (names ((en "Saint Peter Julian Eymard, priest") (la "S. Petri Iuliani Eymard, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 26)))
+ (cel
+ ((slug philip-neri-priest)
+ (names ((en "Saint Philip Neri, priest") (la "S. Philippi Neri, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 30)))
+ (cel
+ ((slug pius-v-pope)
+ (names ((en "Saint Pius V, pope") (la "S. Pii V, papae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 21)))
+ (cel
+ ((slug pius-x-pope)
+ (names ((en "Saint Pius X, pope") (la "S. Pii X, papae")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 23)))
+ (cel
+ ((slug polycarp-bishop-and-martyr)
+ (names ((en "Saint Polycarp, bishop and martyr") (la "S. Polycarpi, episcopi et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 11) (day 21)))
+ (cel
+ ((slug presentation-of-the-blessed-virgin-mary)
+ (names ((en "Presentation of the Blessed Virgin Mary") (la "In Praesentatione B. Mariae Virginis")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 2)))
+ (cel
+ ((slug presentation-of-the-lord)
+ (names ((en "Presentation of the Lord") (la "IN PRAESENTATIONE DOMINI")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 22)))
+ (cel
+ ((slug queenship-of-blessed-virgin-mary)
+ (names ((en "Queenship of Blessed Virgin Mary") (la "B. Mariae Virginis Reginae")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 7)))
+ (cel
+ ((slug raymond-of-penyafort-priest)
+ (names ((en "Saint Raymond of Penyafort, priest") (la "S. Raimundi de Penyafort, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 22)))
+ (cel
+ ((slug rita-of-cascia)
+ (names ((en "Saint Rita of Cascia") (la "S. Ritae, de Cascia religiosae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 17)))
+ (cel
+ ((slug robert-bellarmine-bishop-and-doctor)
+ (names ((en "Saint Robert Bellarmine, bishop and doctor") (la "S. Roberti Bellarmino, episcopi et Ecclesiae doctoris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 19)))
+ (cel
+ ((slug romuald-abbot)
+ (names ((en "Saint Romuald, abbot") (la "S. Romualdi, abbatis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 23)))
+ (cel
+ ((slug rose-of-lima-virgin)
+ (names ((en "Saint Rose of Lima, virgin") (la "S. Rosae de Lima, virginis")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 2)))
+ (cel
+ ((slug saints-basil-the-great-and-gregory-nazianzen-bishops-and-doctors)
+ (names ((en "Saints Basil the Great and Gregory Nazianzen, bishops and doctors") (la "Ss. Basilii Magni et Gregorii Nazianzeni, episcoporum et Ecclesiae doctorum")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 3)))
+ (cel
+ ((slug saints-charles-lwanga-and-companions-martyrs)
+ (names ((en "Saints Charles Lwanga and companions, martyrs") (la "Ss. Caroli Lwanga et sociorum, martyrum")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 16)))
+ (cel
+ ((slug saints-cornelius-pope-and-cyprian-bishop-martyrs)
+ (names ((en "Saints Cornelius, pope, and Cyprian, bishop, martyrs") (la "Ss. Cornelii, papae, et Cypriani, episcopi, martyrum")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 26)))
+ (cel
+ ((slug saints-cosmas-and-damian-martyrs)
+ (names ((en "Saints Cosmas and Damian, martyrs") (la "Ss. Cosmae et Damiani, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 14)))
+ (cel
+ ((slug saints-cyril-monk-and-methodius-bishop)
+ (names ((en "Saints Cyril, monk, and Methodius, bishop") (la "Ss. Cyrilli, monachi, et Methodii, episcopi")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 19)))
+ (cel
+ ((slug saints-jean-de-brebeuf-isaac-jogues-priests-and-martyrs-and-their-companions-martyrs)
+ (names ((en "Saints Jean de Brébeuf, Isaac Jogues, priests and martyrs; and their companions, martyrs") (la "Ss. Ioannis de Brébeuf et Isaac Jogues, presbyterorum, et sociorum, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 26)))
+ (cel
+ ((slug saints-joachim-and-anne)
+ (names ((en "Saints Joachim and Anne") (la "Ss. Ioachim et Annae, parentum B.M.V.")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 22)))
+ (cel
+ ((slug saints-john-fisher-bishop-and-martyr-and-thomas-more-martyr)
+ (names ((en "Saints John Fisher, bishop and martyr and Thomas More, martyr") (la "Ss. Ioannis Fisher, episcopi, et Thomae More, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 28)))
+ (cel
+ ((slug saints-lawrence-ruiz-and-companions-martyrs)
+ (names ((en "Saints Lawrence Ruiz and companions, martyrs") (la "Ss. Laurentii Ruiz et sociorum, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 2)))
+ (cel
+ ((slug saints-marcellinus-and-peter-martyrs)
+ (names ((en "Saints Marcellinus and Peter, martyrs") (la "Ss. Marcellini et Petri, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 29)))
+ (cel
+ ((slug saints-michael-gabriel-and-raphael-archangels)
+ (names ((en "Saints Michael, Gabriel and Raphael, Archangels") (la "Ss. Michaelis, Gabrielis et Raphaelis, archangelorum")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 12)))
+ (cel
+ ((slug saints-nereus-and-achilleus-martyrs)
+ (names ((en "Saints Nereus and Achilleus, martyrs") (la "Ss. Nerei et Achillei, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 6)))
+ (cel
+ ((slug saints-paul-miki-and-companions-martyrs)
+ (names ((en "Saints Paul Miki and companions, martyrs") (la "Ss. Pauli Miki et sociorum, martyrum")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 7)))
+ (cel
+ ((slug saints-perpetua-and-felicity-martyrs)
+ (names ((en "Saints Perpetua and Felicity, martyrs") (la "Ss. Perpetuae et Felicitatis, martyrum")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 6) (day 29)))
+ (cel
+ ((slug saints-peter-and-paul-apostles)
+ (names ((en "Saints Peter and Paul, Apostles") (la "Ss. PETRI ET PAULI, APOSTOLORUM")))
+ (rank Sollemnitas) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 3)))
+ (cel
+ ((slug saints-philip-and-james-apostles)
+ (names ((en "Saints Philip and James, Apostles") (la "Ss. Philippi et Iacobi, apostolorum")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 13)))
+ (cel
+ ((slug saints-pontian-pope-and-hippolytus-priest-martyrs)
+ (names ((en "Saints Pontian, pope, and Hippolytus, priest, martyrs") (la "Ss. Pontiani, papae, et Hippolyti, presbyteri, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 26)))
+ (cel
+ ((slug saints-timothy-and-titus-bishops)
+ (names ((en "Saints Timothy and Titus, bishops") (la "Ss. Timothei et Titi, episcoporum")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 10)))
+ (cel
+ ((slug scholastica-virgin)
+ (names ((en "Saint Scholastica, virgin") (la "S. Scholasticae, virginis")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 20)))
+ (cel
+ ((slug sebastian-martyr)
+ (names ((en "Saint Sebastian, martyr") (la "S. Sebastiani, martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 2) (day 17)))
+ (cel
+ ((slug seven-holy-founders-of-the-servite-order)
+ (names ((en "Seven Holy Founders of the Servite Order") (la "Ss. septem Fundatorum Ordinis Servorum B.M.V.")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 24)))
+ (cel
+ ((slug sharbel-makhluf-hermit)
+ (names ((en "Saint Sharbel Makhluf, hermit") (la "S. Sarbelii Makhlu‾ f, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 28)))
+ (cel
+ ((slug simon-and-saint-jude-apostles)
+ (names ((en "Saint Simon and Saint Jude, apostles") (la "Ss. Simonis et Iudae, apostolorum")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 7)))
+ (cel
+ ((slug sixtus-ii-pope-and-companions-martyrs)
+ (names ((en "Saint Sixtus II, pope, and companions, martyrs") (la "Ss. Xysti II, papae, et sociorum, martyrum")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 11)))
+ (cel
+ ((slug stanislaus-bishop-and-martyr)
+ (names ((en "Saint Stanislaus, bishop and martyr") (la "S. Stanislai, episcopi et martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 16)))
+ (cel
+ ((slug stephen-of-hungary)
+ (names ((en "Saint Stephen of Hungary") (la "S. Stephani Hungariae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 26)))
+ (cel
+ ((slug stephen-the-first-martyr)
+ (names ((en "Saint Stephen, the first martyr") (la "S. Stephani, protomartyris")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 31)))
+ (cel
+ ((slug sylvester-i-pope)
+ (names ((en "Saint Sylvester I, pope") (la "S. Silvestri I, papae")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 9)))
+ (cel
+ ((slug teresa-benedicta-of-the-cross-edith-stein-virgin-and-martyr)
+ (names ((en "Saint Teresa Benedicta of the Cross (Edith Stein), virgin and martyr") (la "S. Teresiae Benedictae a Cruce, virginis et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 15)))
+ (cel
+ ((slug teresa-of-jesus-virgin-and-doctor)
+ (names ((en "Saint Teresa of Jesus, virgin and doctor") (la "S. Teresiae a Iesu, virginis et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 29)))
+ (cel
+ ((slug the-beheading-of-saint-john-the-baptist-martyr)
+ (names ((en "The Beheading of Saint John the Baptist, martyr") (la "In Passione S. Ioannis Baptistae, martyris")))
+ (rank Memoria_obligatoria) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 25)))
+ (cel
+ ((slug the-conversion-of-saint-paul-apostle)
+ (names ((en "The Conversion of Saint Paul, apostle") (la "In Conversione S. Pauli, apostoli")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 3)))
+ (cel
+ ((slug the-most-holy-name-of-jesus)
+ (names ((en "The Most Holy Name of Jesus") (la "Ss.mi Nominis Iesu")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 10) (day 1)))
+ (cel
+ ((slug therese-of-the-child-jesus-virgin-and-doctor)
+ (names ((en "Saint Thérèse of the Child Jesus, virgin and doctor") (la "S. Teresiae a Iesu Infante, virginis et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 28)))
+ (cel
+ ((slug thomas-aquinas-priest-and-doctor)
+ (names ((en "Saint Thomas Aquinas, priest and doctor") (la "S. Thomae de Aquino, presbyteri et Ecclesiae doctoris")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 12) (day 29)))
+ (cel
+ ((slug thomas-becket-bishop-and-martyr)
+ (names ((en "Saint Thomas Becket, bishop and martyr") (la "S. Thomae Becket, episcopi et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 7) (day 3)))
+ (cel
+ ((slug thomas-the-apostle)
+ (names ((en "Saint Thomas the Apostle") (la "S. Thomae, apostoli")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 8) (day 6)))
+ (cel
+ ((slug transfiguration-of-the-lord)
+ (names ((en "Transfiguration of the Lord") (la "In Transfiguratione Domini")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 14)))
+ (cel
+ ((slug triumph-of-the-holy-cross)
+ (names ((en "Triumph of the Holy Cross") (la "In Exaltatione sanctae Crucis")))
+ (rank Festum) (status Feast) (colour Red)
+ (subject Lord) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 3) (day 23)))
+ (cel
+ ((slug turibius-of-mogrovejo-bishop)
+ (names ((en "Saint Turibius of Mogrovejo, bishop") (la "S. Turibii de Mogrovejo, episcopi")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 27)))
+ (cel
+ ((slug vincent-de-paul-priest)
+ (names ((en "Saint Vincent de Paul, priest") (la "S. Vincentii de Paul, presbyteri")))
+ (rank Memoria_obligatoria) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 1) (day 22)))
+ (cel
+ ((slug vincent-deacon-and-martyr)
+ (names ((en "Saint Vincent, deacon and martyr") (la "S. Vincentii, diaconi et martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 4) (day 5)))
+ (cel
+ ((slug vincent-ferrer-priest)
+ (names ((en "Saint Vincent Ferrer, priest") (la "S. Vincentii Ferrer, presbyteri")))
+ (rank Memoria_ad_libitum) (status Feast) (colour White)
+ (subject Saint) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 5) (day 31)))
+ (cel
+ ((slug visitation-of-the-blessed-virgin-mary)
+ (names ((en "Visitation of the Blessed Virgin Mary") (la "In Visitatione B. Mariae Virginis")))
+ (rank Festum) (status Feast) (colour White)
+ (subject Bvm) (citations ()) (layer of-universal))))
+ ((date (Fixed (month 9) (day 28)))
+ (cel
+ ((slug wenceslaus-martyr)
+ (names ((en "Saint Wenceslaus, martyr") (la "S. Venceslai, martyris")))
+ (rank Memoria_ad_libitum) (status Feast) (colour Red)
+ (subject Saint) (citations ()) (layer of-universal))))
+ )))
diff --git a/test/dune b/test/dune
index 00114cf..9af9900 100644
--- a/test/dune
+++ b/test/dune
@@ -4,6 +4,7 @@
(deps
../data/ef/sanctoral.sexp
../data/ef/adjustments.sexp
+ ../data/of/calendar-2002.sexp
../lang/la.ini
../lang/en.ini
../lang/traditions.ini
diff --git a/test/test_calendar_of_data.ml b/test/test_calendar_of_data.ml
new file mode 100644
index 0000000..01a757e
--- /dev/null
+++ b/test/test_calendar_of_data.ml
@@ -0,0 +1,207 @@
+(* Task 1 (2026-08-25-colitur-of-phases-3-5): loads data/of/calendar-2002.sexp
+ -- the General Roman Calendar transcribed from the 2002 Missale Romanum by
+ tools/extract_of_calendar.py -- through Layer.load and checks it against
+ counts independently re-derived from the shipped file itself (grep -c
+ '(slug', grep -oP rank/status/colour/subject | sort | uniq -c -- see
+ .superpowers/sdd/2026-08-25-colitur-of-phases-3-5/task-1-report.md), plus
+ named spot-checks read straight off the Missal's own printed page so a
+ passing count can't hide the wrong 206 entries having been transcribed
+ (the same "live hazard" test_sanctoral_ef.ml's own header names).
+
+ SOURCE DISCIPLINE, asserted structurally here, not merely stated in the
+ tool's own header: every entry's rank is one of the four Vocab_of.rank
+ grades NEVER Feria (that rank is temporal-only, see vocab_of.mli), and
+ every entry's layer is Precedence_of.universal_layer. *)
+
+module L = Colitur_kernel.Layer
+module Cel = Colitur_kernel.Celebration
+module S = Colitur_kernel.Slug
+module DS = Colitur_kernel.Date_spec
+module D = Colitur_kernel.Date
+module Col = Colitur_kernel.Colour
+module Sub = Colitur_kernel.Subject
+module Lang = Colitur_kernel.Lang
+module Names = Colitur_kernel.Names
+module V = Rite_of.Vocab_of
+module PO = Rite_of.Precedence_of
+module T = Rite_of.Temporal_of
+
+(* Relative to this test's own build directory (_build/default/test/); made
+ available there because test/dune declares it as a dep of the test
+ stanza. *)
+let path = "../data/of/calendar-2002.sexp"
+
+let load () =
+ match L.load V.rank_of_sexp path with
+ | Ok l -> l
+ | Error e -> Alcotest.failf "%s: failed to load: %s" path e
+
+let mkdate m d = match DS.fixed ~month:m ~day:d with Ok t -> t | Error e -> failwith e
+
+let sha256_of_file path =
+ let tmp = Filename.temp_file "colitur_calendar_of_sha256" ".txt" in
+ let cmd = Printf.sprintf "sha256sum %s > %s" (Filename.quote path) (Filename.quote tmp) in
+ (match Sys.command cmd with
+ | 0 -> ()
+ | n -> Alcotest.failf "sha256sum exited %d" n);
+ let ic = open_in tmp in
+ let line = input_line ic in
+ close_in ic;
+ Sys.remove tmp;
+ match String.split_on_char ' ' line with
+ | hash :: _ -> hash
+ | [] -> Alcotest.fail "sha256sum produced no output"
+
+(* Pinned 2026-08-25 against the shipped file itself, independently
+ re-derived (`sha256sum data/of/calendar-2002.sexp`) -- not copied from
+ the tool's own stdout run, per this project's "derive the new value,
+ don't transcribe it from output" pinning discipline. *)
+let test_sha256 () =
+ Alcotest.(check string) "data/of/calendar-2002.sexp SHA-256"
+ "b2bf3fa9d27f5bc6e47225c477c158f629eaab6c280e0b69fa04c8a55db9f612" (sha256_of_file path)
+
+(* Counts independently re-derived from the shipped file
+ (`grep -c '(slug'`; `grep -oP '(?<=\(rank )[A-Za-z_]+' | sort | uniq -c`).
+ 206 = 209 raw table rows minus the 3 dates (1 Jan, 6 Jan, 25 Dec) already
+ computed by Temporal_of.named -- see test_excludes_temporal_of_dates
+ below, which closes the loop on that exclusion rather than merely
+ asserting the count moved. *)
+let test_load_and_counts () =
+ let l = load () in
+ Alcotest.(check int) "206 entries" 206 (List.length l.L.entries);
+ let count pred = List.length (List.filter pred l.L.entries) in
+ Alcotest.(check int) "8 Sollemnitas" 8 (count (fun e -> e.L.cel.Cel.rank = V.Sollemnitas));
+ Alcotest.(check int) "23 Festum" 23 (count (fun e -> e.L.cel.Cel.rank = V.Festum));
+ Alcotest.(check int) "67 Memoria_obligatoria" 67
+ (count (fun e -> e.L.cel.Cel.rank = V.Memoria_obligatoria));
+ Alcotest.(check int) "108 Memoria_ad_libitum" 108
+ (count (fun e -> e.L.cel.Cel.rank = V.Memoria_ad_libitum));
+ Alcotest.(check int) "0 Feria (temporal-only rank, never a calendar entry)" 0
+ (count (fun e -> e.L.cel.Cel.rank = V.Feria));
+ Alcotest.(check int) "206 Feast (OF admits no Commemoration_only status)" 206
+ (count (fun e -> e.L.cel.Cel.status = Cel.Feast));
+ Alcotest.(check int) "every entry tagged Precedence_of.universal_layer" 206
+ (count (fun e -> e.L.cel.Cel.layer = PO.universal_layer))
+
+let test_slugs_unique () =
+ let l = load () in
+ let slugs = List.map (fun e -> S.to_string e.L.cel.Cel.slug) l.L.entries in
+ let sorted = List.sort_uniq compare slugs in
+ Alcotest.(check int) "no duplicate slugs" (List.length slugs) (List.length sorted)
+
+let test_slugs_revalidate () =
+ let l = load () in
+ List.iter
+ (fun e ->
+ match S.of_string (S.to_string e.L.cel.Cel.slug) with
+ | Ok _ -> ()
+ | Error msg -> Alcotest.failf "slug %s failed re-validation: %s" (S.to_string e.L.cel.Cel.slug) msg)
+ l.L.entries
+
+(* Every date is a Fixed month/day; DS.fixed already rejects an out-of-range
+ day against the leap-year maximum, so this also proves no entry claims a
+ nonexistent day (e.g. 30 February). *)
+let test_dates_resolve_in_a_leap_year () =
+ let l = load () in
+ List.iter
+ (fun e ->
+ match DS.resolve e.L.date ~year:2028 ~easter:(Colitur_kernel.Computus.gregorian_easter 2028) with
+ | Some _ -> ()
+ | None -> Alcotest.failf "slug %s: date does not resolve in leap year 2028" (S.to_string e.L.cel.Cel.slug))
+ l.L.entries
+
+let find l slug =
+ match L.find l (S.of_string_exn slug) with
+ | Some e -> e
+ | None -> Alcotest.failf "slug %s not found in %s" slug path
+
+(* Brief step 4's own named spot-checks, read straight off the Missal's
+ printed Calendarium Romanum Generale page. *)
+
+let test_spot_check_conversion_of_paul () =
+ let e = find (load ()) "the-conversion-of-saint-paul-apostle" in
+ Alcotest.(check bool) "25 Jan" true (e.L.date = mkdate 1 25);
+ Alcotest.(check bool) "Festum" true (e.L.cel.Cel.rank = V.Festum);
+ (* IGMR 346(a)'s own explicit white exception for this date, despite the
+ title's "apostoli" which would otherwise trigger the apostle/martyr-red
+ default -- see tools/extract_of_calendar.py's WHITE_APOSTLE_EXCEPTIONS. *)
+ Alcotest.(check bool) "white (IGMR 346(a) explicit exception)" true (e.L.cel.Cel.colour = Col.White)
+
+let test_spot_check_chair_of_peter () =
+ let e = find (load ()) "chair-of-saint-peter-apostle" in
+ Alcotest.(check bool) "22 Feb" true (e.L.date = mkdate 2 22);
+ Alcotest.(check bool) "Festum" true (e.L.cel.Cel.rank = V.Festum);
+ Alcotest.(check bool) "white (IGMR 346(a) explicit exception)" true (e.L.cel.Cel.colour = Col.White)
+
+let test_spot_check_presentation () =
+ let e = find (load ()) "presentation-of-the-lord" in
+ Alcotest.(check bool) "2 Feb" true (e.L.date = mkdate 2 2);
+ Alcotest.(check bool) "Festum" true (e.L.cel.Cel.rank = V.Festum);
+ Alcotest.(check bool) "subject Lord" true (e.L.cel.Cel.subject = Sub.Lord)
+
+let test_spot_check_francis_de_sales () =
+ let e = find (load ()) "francis-de-sales-bishop-and-doctor" in
+ Alcotest.(check bool) "24 Jan" true (e.L.date = mkdate 1 24);
+ Alcotest.(check bool) "Memoria_obligatoria (printed grade word: Memoria)" true
+ (e.L.cel.Cel.rank = V.Memoria_obligatoria)
+
+let test_spot_check_angela_merici () =
+ let e = find (load ()) "angela-merici-virgin" in
+ Alcotest.(check bool) "27 Jan" true (e.L.date = mkdate 1 27);
+ Alcotest.(check bool) "blank grade -> Memoria_ad_libitum (calendar's own footnote)" true
+ (e.L.cel.Cel.rank = V.Memoria_ad_libitum)
+
+(* 1 January (Mary, Mother of God) IS "Sollemnitas" on the Missal's own
+ printed page, the brief's sixth named spot-check -- but it is
+ DELIBERATELY ABSENT from this file: Phase 1's Temporal_of.named already
+ computes it as a temporal-cycle office (m=1, dd=1), and shipping it here
+ too would create two competing candidates for the same day (see
+ tools/extract_of_calendar.py's own module docstring, point 2, and this
+ file's SKIP_DATES table). Asserted both ways, so the exclusion is
+ verified rather than merely claimed: absent from the data, present and
+ correctly graded in the code that actually covers it. The same holds for
+ 6 January (Epiphany) and 25 December (the Nativity). *)
+let test_excludes_temporal_of_dates () =
+ let l = load () in
+ let has_date m d =
+ List.exists (fun e -> e.L.date = mkdate m d) l.L.entries
+ in
+ Alcotest.(check bool) "1 Jan absent from calendar-2002.sexp" false (has_date 1 1);
+ Alcotest.(check bool) "6 Jan absent from calendar-2002.sexp" false (has_date 1 6);
+ Alcotest.(check bool) "25 Dec absent from calendar-2002.sexp" false (has_date 12 25);
+ let mkd m d = match D.make ~year:2026 ~month:m ~day:d with Ok d -> d | Error e -> failwith e in
+ (match T.named (mkd 1 1) with
+ | Some (_, _, colour, rank) ->
+ Alcotest.(check bool) "1 Jan: Temporal_of.named yields Sollemnitas" true (rank = V.Sollemnitas);
+ Alcotest.(check bool) "1 Jan: Temporal_of.named yields White" true (colour = Col.White)
+ | None -> Alcotest.fail "1 Jan: Temporal_of.named returned None -- the office this file \
+ excludes on that basis does not actually exist in code");
+ (match T.named (mkd 1 6) with
+ | Some (_, _, _, rank) -> Alcotest.(check bool) "6 Jan: Sollemnitas" true (rank = V.Sollemnitas)
+ | None -> Alcotest.fail "6 Jan: Temporal_of.named returned None");
+ match T.named (mkd 12 25) with
+ | Some (_, _, _, rank) -> Alcotest.(check bool) "25 Dec: Sollemnitas" true (rank = V.Sollemnitas)
+ | None -> Alcotest.fail "25 Dec: Temporal_of.named returned None"
+
+let suite =
+ ( "Calendar_of (data/of/calendar-2002.sexp)",
+ [ Alcotest.test_case "SHA-256 pinned" `Quick test_sha256;
+ Alcotest.test_case "load succeeds and counts match the shipped file" `Quick
+ test_load_and_counts;
+ Alcotest.test_case "slugs are unique" `Quick test_slugs_unique;
+ Alcotest.test_case "slugs re-validate" `Quick test_slugs_revalidate;
+ Alcotest.test_case "every date resolves in a leap year" `Quick
+ test_dates_resolve_in_a_leap_year;
+ Alcotest.test_case "spot-check: 25 Jan Festum, Conversion of Paul" `Quick
+ test_spot_check_conversion_of_paul;
+ Alcotest.test_case "spot-check: 22 Feb Festum, Chair of Peter" `Quick
+ test_spot_check_chair_of_peter;
+ Alcotest.test_case "spot-check: 2 Feb Festum, Presentation" `Quick
+ test_spot_check_presentation;
+ Alcotest.test_case "spot-check: 24 Jan Memoria, Francis de Sales" `Quick
+ test_spot_check_francis_de_sales;
+ Alcotest.test_case "spot-check: 27 Jan blank -> Memoria_ad_libitum, Angela Merici" `Quick
+ test_spot_check_angela_merici;
+ Alcotest.test_case
+ "1/6 Jan and 25 Dec deliberately excluded -- Temporal_of.named covers them" `Quick
+ test_excludes_temporal_of_dates ] )
diff --git a/test/test_colitur.ml b/test/test_colitur.ml
index b6675fd..c750e75 100644
--- a/test/test_colitur.ml
+++ b/test/test_colitur.ml
@@ -14,7 +14,8 @@ let () =
Test_overlay.suite; Test_overlay_ini.suite; Test_temporal_ef.suite; Test_temporal_of.suite; Test_litcal_of.suite; Test_precedence_of.suite;
Test_validate.suite;
Test_precedence.suite;
- Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_rite_ef.suite;
+ Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_calendar_of_data.suite;
+ Test_rite_ef.suite;
Test_rubrics_ef.suite;
Test_differential.suite; Test_oracle.suite; Test_oracle.suite_2038; Test_oracle.suite_2035;
Test_lms_ordo.suite_2023_2024; Test_lms_ordo.suite_2024_2025; Test_lms_ordo.suite_2025_2026;
diff --git a/tools/extract_of_calendar.py b/tools/extract_of_calendar.py
new file mode 100644
index 0000000..864c409
--- /dev/null
+++ b/tools/extract_of_calendar.py
@@ -0,0 +1,677 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""tools/extract_of_calendar.py -- transcribes the Calendarium Romanum
+Generale (General Roman Calendar) from the 2002 Missale Romanum, editio
+typica tertia, into data/of/calendar-2002.sexp: a Colitur_kernel.Layer.t of
+Colitur_kernel.Celebration.t entries, one per FIXED (month, day) universal
+calendar entry.
+
+SOURCE DISCIPLINE (do not weaken this): the 2002 Missale Romanum PDF is the
+authority. lectio's ~/git/projects/lectio/internal/caldata/roman-calendar.ini
+is read here ONLY for two cross-check purposes, never as structural source:
+ (a) comparing (month, day, rank) to surface divergences, each of which is
+ adjudicated by re-reading the Missal (see the divergence log this
+ script prints), never by silently preferring lectio;
+ (b) copying `name.en` -- lectio's OWN `name.la` field is independently
+ known to be unreliable (it contains English text for at least one
+ entry, verified during planning: mary-mother-of-god-octave-of-christmas
+ has name.la = "Mary, Mother of God (Octave of Christmas)"), so Latin
+ is NEVER taken from lectio, only English.
+
+Run: `python3 tools/extract_of_calendar.py [pretext-file] > data/of/calendar-2002.sexp`
+With no argument, runs `pdftotext -layout` on the PDF itself. A pre-extracted
+text file may be given instead (for reproducibility without re-invoking
+pdftotext).
+
+WHAT THIS FILE DELIBERATELY EXCLUDES, and why (each is a real design
+decision, not an oversight):
+
+1. Every MOVABLE universal solemnity/feast printed in the calendar table
+ under a "Dominica ... :" / "Feria ... :" / "Sabbato ... :" heading
+ (Baptism of the Lord, Holy Family, Trinity Sunday, Corpus Christi, the
+ Sacred Heart of Jesus, the Immaculate Heart of Mary, Christ the King).
+ None of these has a (month, day) Date_spec.Fixed key at all -- they are
+ Easter-relative, and Date_spec.Fixed is this file's only key shape (see
+ the task interface). Trinity/Corpus Christi/Christ the King/Holy Family/
+ Baptism of the Lord are ALREADY computed by lib/rites/rite_of/
+ temporal_of.ml's own `named`/`holy_family`/`baptism_of_the_lord` (Phase
+ 1, already merged, frozen for this task) -- duplicating them here would
+ create two competing candidates for the same day.
+
+ FINDING (report this loudly, it is not this task's to fix): the Sacred
+ Heart of Jesus ("Feria VI post dominicam secundam post Pentecosten:
+ SACRATISSIMI CORDIS IESU Sollemnitas") and the Immaculate Heart of Mary
+ ("Sabbato post dominicam secundam post Pentecosten: Immaculati Cordis B.
+ Mariae Virginis Memoria") are printed in the SAME base-2002 calendar
+ table as Trinity/Corpus Christi/Christ the King, but temporal_of.ml
+ covers NEITHER of them -- grep for "Cordis"/"Sacred"/"Sacratissim" in
+ that file finds nothing. This is a genuine gap in Phase 1's own
+ coverage, orthogonal to Task 1 (a Fixed-only data file cannot express
+ an Easter-relative feast), not caused by this task and not fixable by
+ it -- Temporal_of would need two more named-day branches
+ (Pentecost+19 and Pentecost+20 respectively, i.e. Easter+68/+69).
+
+2. Three FIXED entries that ARE in the printed table but are ALSO already
+ computed by temporal_of.ml's own `named`: 1 January (Mary, Mother of
+ God), 6 January (Epiphany), 25 December (the Nativity). Shipping them
+ here too would duplicate a candidate Precedence_of.band would then have
+ to arbitrate between a temporal-origin and a data-origin copy of the
+ IDENTICAL office -- pointless and risks a silent divergence between the
+ two copies. Excluded explicitly (SKIP_DATES below), not merely absent
+ by accident.
+
+DATA DECISIONS made while transcribing, each cited in the emitted file's
+own provenance header:
+
+- A blank grade column means Memoria ad libitum, per the calendar's own
+ footnote (`* Quando non indicatur gradus celebrationis, fit Memoria ad
+ libitum.`) -- Normae/Missale Romanum 2002, Calendarium Romanum Generale,
+ its own footnote marker.
+- 2 November (Commemoratio omnium fidelium defunctorum, All Souls) carries
+ NO grade word in the table at all -- applying the footnote naively would
+ misclassify the second-most solemn day of November as an optional
+ memorial. Normae n. 59's own Tabula dierum liturgicorum, entry 3, places
+ it explicitly alongside "Sollemnitates ... in Calendario generali
+ inscriptae" ("Sollemnitates Domini, beatae Mariae Virginis, et Sanctorum
+ in Calendario generali inscriptae. Commemoratio omnium fidelium
+ defunctorum.") -- same table entry, same precedence tier. Tagged
+ Sollemnitas here on that citation, as an explicit, documented override,
+ not the generic blank-grade default.
+- Colour is derived from IGMR n. 346 (a)-(d): white for Christ's
+ non-Passion celebrations / the BVM / Angels / non-martyr Saints / All
+ Saints / John the Baptist's Nativity / John the Evangelist / Chair of
+ Peter / Conversion of Paul (346(a), the last three named explicitly);
+ red for Passion-related celebrations of the Lord, "festis nataliciis
+ Apostolorum et Evangelistarum" (an Apostle's or Evangelist's own feast),
+ and any celebration of Martyr Saints (346(b)); violet, chosen over the
+ also-permitted black, for the Commemoration of All the Souls (346(d):
+ "Assumi potest etiam in Officiis et Missis defunctorum" -- explicitly
+ authorised for the dead; 346(a)'s white clause has no textual reach here,
+ since All Souls is not "Sanctorum" (canonised Saints) at all). This
+ DIVERGES from lectio, which colours 2 November white -- see the
+ divergence log below; adjudicated colitur/violet on 346(d)'s explicit
+ text, flagging lectio's white as reflecting a widespread modern PASTORAL
+ custom the Missal's own words do not themselves authorise as the
+ default. Three explicit exceptions to the apostle/evangelist-red rule
+ (Chair of Peter, Conversion of Paul, John the Evangelist) and one to the
+ martyr/apostle-red rule (three "In Dedicatione ..." church-dedication
+ entries, always white) are coded as override tables, cited alongside the
+ rule.
+
+EXTRACTION ARTIFACT: some titles are printed letter-spaced by pdftotext
+(e.g. "S . P e t r i D a m i a n i"). `collapse_letterspacing` mechanically
+rejoins runs of >=4 single-character tokens, using punctuation (periods,
+commas) and a lowercase-then-uppercase transition as word-boundary signals.
+This CANNOT recover a lowercase-to-lowercase boundary (no case signal
+survives the original single-spacing), so it is a first pass only; the sole
+entry it fires on in this table (21 February, S. Petri Damiani) is then
+hand-verified against the well-attested standard title and corrected
+explicitly, with the exact reason recorded at the call site. The count of
+entries needing this repair is printed to stderr.
+"""
+
+import hashlib
+import re
+import subprocess
+import sys
+import unicodedata
+from datetime import date, timezone, datetime
+
+PDF_PATH = "docs/research/of/missale-romanum-2002.pdf"
+TOOL_PATH = "tools/extract_of_calendar.py"
+LECTIO_INI = "../lectio/internal/caldata/roman-calendar.ini"
+
+MONTHS = [
+ "IANUARIUS", "FEBRUARIUS", "MARTIUS", "APRILIS", "MAIUS", "IUNIUS",
+ "IULIUS", "AUGUSTUS", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER",
+]
+MONTH_NUM = {m: i + 1 for i, m in enumerate(MONTHS)}
+
+ROW_RE = re.compile(r"^(Cal\.|Prid\.|Non\.|Idib\.|[IVXL]+)\s*(\d{1,2})\b\s*(.*)$")
+MOVABLE_HEADING_RE = re.compile(r"^(Dominica|Feria\s+[IVXL]+|Sabbato)\b.*\b(post|ultima|infra)\b")
+ENTRY_MARKER_RE = re.compile(r"^(S\.|Ss\.|B\.)\s")
+FOOTNOTE_RE = re.compile(r"^\*\s*Quando non indicatur")
+
+GRADE_WORDS = ["Sollemnitas", "Festum", "Memoria"]
+GRADE_TO_RANK = {
+ "Sollemnitas": "Sollemnitas",
+ "Festum": "Festum",
+ "Memoria": "Memoria_obligatoria",
+ None: "Memoria_ad_libitum",
+}
+
+# Excluded because Phase 1 (lib/rites/rite_of/temporal_of.ml) already
+# computes these as temporal-cycle offices. See module docstring, point 2.
+SKIP_DATES = {
+ (1, 1): "of-mary-mother-of-god (Temporal_of.named, m=1 dd=1)",
+ (1, 6): "of-epiphany (Temporal_of.named, m=1 dd=6)",
+ (12, 25): "of-nativity (Temporal_of.named, m=12 dd=25)",
+}
+
+# IGMR 346(a)'s own three explicit white exceptions to the apostle/
+# evangelist-red rule.
+WHITE_APOSTLE_EXCEPTIONS = {(1, 25), (2, 22), (12, 27)}
+
+# Church-dedication feasts: always white, never coloured by the martyr/
+# apostle words that happen to appear in their own titles (the apostles
+# named are the church's patrons, not the day's own honouree).
+DEDICATION_DATES = {(8, 5), (11, 9), (11, 18)}
+
+# All Souls: see module docstring for the 346(d) citation.
+ALL_SOULS_OVERRIDE = (11, 2)
+
+# Explicit red override: the Exaltation of the Holy Cross is a celebration
+# "Passionis Domini" (346(b)) even though its own title contains neither
+# "martyr" nor "apostol"/"evangelist".
+RED_OVERRIDES = {(9, 14)}
+
+# subject = Lord: celebrations of the Lord not already excluded above,
+# plus the three church-dedication feasts (a dedication is itself
+# classified a "festum Domini" -- see CLAUDE.md's EF precedent, and
+# lectio's own roman-calendar.ini tags all three `class = lord`).
+LORD_DATES = {(2, 2), (3, 25), (8, 6), (1, 3), (9, 14)} | DEDICATION_DATES
+
+
+def sha256_file(path):
+ h = hashlib.sha256()
+ with open(path, "rb") as f:
+ h.update(f.read())
+ return h.hexdigest()
+
+
+def get_text(pdf_path):
+ out = subprocess.run(["pdftotext", "-layout", pdf_path, "-"], capture_output=True, check=True)
+ return out.stdout.decode("utf-8")
+
+
+def collapse_letterspacing(line):
+ """Mechanically rejoins a run of >=4 single-character tokens. Returns
+ (new_line, changed). See module docstring's "EXTRACTION ARTIFACT" note
+ for what this can and cannot recover."""
+ tokens = line.split(" ")
+ out = []
+ i = 0
+ changed = False
+ while i < len(tokens):
+ if len(tokens[i]) == 1:
+ j = i
+ while j < len(tokens) and len(tokens[j]) == 1:
+ j += 1
+ run = tokens[i:j]
+ if len(run) >= 4:
+ changed = True
+ merged = "".join(run)
+ merged = re.sub(r"(?<=[.,])(?=\S)", " ", merged)
+ merged = re.sub(r"(?<=[a-zà-öø-ÿæœ])(?=[A-ZÀ-ÖØ-ÝÆŒ])", " ", merged)
+ out.append(merged)
+ else:
+ out.extend(run)
+ i = j
+ else:
+ out.append(tokens[i])
+ i += 1
+ return " ".join(out), changed
+
+
+def bound_table(text):
+ lines = text.split("\n")
+ # First IANUARIUS before line 5000, immediately preceded (allowing for a
+ # blank line) by "CALENDARIUM ROMANUM GENERALE" -- rules out the second,
+ # Proprium Sanctorum run near line 17874.
+ start = None
+ for i, l in enumerate(lines[:5000]):
+ if l.strip("\x0c").strip() == "IANUARIUS":
+ # confirm CALENDARIUM ROMANUM GENERALE appears in the preceding
+ # few lines
+ window = "\n".join(lines[max(0, i - 4):i])
+ if "CALENDARIUM ROMANUM GENERALE" in window:
+ start = i
+ break
+ if start is None:
+ raise SystemExit("could not find the start of the calendar table (IANUARIUS after "
+ "CALENDARIUM ROMANUM GENERALE, before line 5000)")
+ end = None
+ for i in range(start, min(len(lines), 5000)):
+ if lines[i].strip() == "TABELLA TEMPORARIA":
+ end = i
+ break
+ if end is None:
+ raise SystemExit("could not find the end of the calendar table (TABELLA TEMPORARIA)")
+ return start, end, lines[start:end]
+
+
+def is_month_header(line):
+ return line.strip("\x0c").strip() in MONTH_NUM
+
+
+def parse_rows(table_lines):
+ """Returns (entries, letterspace_count) where entries is a list of dicts
+ with month, day, latin (raw joined title), grade (str or None)."""
+ # Pass 1: strip form feeds and collapse letter-spacing line by line.
+ letterspace_count = 0
+ lines = []
+ for raw in table_lines:
+ l = raw.replace("\x0c", "")
+ l2, changed = collapse_letterspacing(l)
+ if changed:
+ letterspace_count += 1
+ lines.append(l2)
+
+ entries = []
+ current_month = None
+ i = 0
+ n = len(lines)
+ while i < n:
+ line = lines[i].strip()
+ if line == "":
+ i += 1
+ continue
+ if is_month_header(line):
+ current_month = MONTH_NUM[line]
+ i += 1
+ continue
+ if FOOTNOTE_RE.match(line):
+ i += 1
+ continue
+ if MOVABLE_HEADING_RE.match(line):
+ i += 1
+ while i < n:
+ nxt = lines[i].strip()
+ if (nxt == "" or is_month_header(nxt) or ROW_RE.match(nxt)
+ or MOVABLE_HEADING_RE.match(nxt) or FOOTNOTE_RE.match(nxt)):
+ break
+ i += 1
+ continue
+ rm = ROW_RE.match(line)
+ if rm:
+ if current_month is None:
+ raise SystemExit(f"row before any month header: {line!r}")
+ _kalend, day_s, rest = rm.groups()
+ day = int(day_s)
+ blocks = [[]]
+ if rest.strip():
+ blocks[0].append(rest.strip())
+ i += 1
+ while i < n:
+ nxt_raw = lines[i]
+ nxt = nxt_raw.strip()
+ if (nxt == "" or is_month_header(nxt) or ROW_RE.match(nxt)
+ or MOVABLE_HEADING_RE.match(nxt) or FOOTNOTE_RE.match(nxt)):
+ break
+ if ENTRY_MARKER_RE.match(nxt) and blocks[-1]:
+ blocks.append([nxt])
+ else:
+ blocks[-1].append(nxt)
+ i += 1
+ for block in blocks:
+ if not block:
+ continue
+ text = " ".join(block).strip()
+ grade = None
+ for g in GRADE_WORDS:
+ if text == g or text.endswith(" " + g):
+ grade = g
+ text = text[: -len(g)].strip()
+ break
+ # Jan 3: "Ss.mi Nominis Iesu *" -- the footnote's own marker
+ # asterisk, not a grade word. Strip it; the footnote's blank
+ # -> Memoria_ad_libitum mapping still applies.
+ if text.endswith("*"):
+ text = text[:-1].strip()
+ if text:
+ entries.append({"month": current_month, "day": day, "latin": text, "grade": grade})
+ continue
+ raise SystemExit(f"unrecognised line in calendar table (month={current_month}): {line!r}")
+
+ return entries, letterspace_count
+
+
+LATIN_TO_ASCII = str.maketrans({
+ # Æ (uppercase ligature) is verified, across every occurrence in the
+ # extracted calendar table, to appear ONLY inside an ALL-CAPS heading
+ # ("SANCTÆ", "PRÆSENTATIONE", "ASSUMPTIONE BEATÆ") -- never as the
+ # initial letter of a Title Case word -- so it maps to "AE" (both
+ # capitals), not "Ae", to stay consistent with its own surrounding case.
+ "æ": "ae", "Æ": "AE", "œ": "oe", "Œ": "OE", "ø": "o", "Ø": "O",
+})
+
+
+def normalize_latin(text):
+ """æ -> ae, œ -> oe, matching the existing la-name convention already
+ used by lib/rites/rite_of/temporal_of.ml's own holy_family_names/
+ baptism_names (e.g. "Sanctae Familiae Iesu, Mariae et Ioseph"), not the
+ ligature glyphs the PDF itself prints. Apostrophes and other punctuation
+ are left untouched here -- they are real printed characters in the `la`
+ field; only slugify()/title_slug() (which call this first) strip them
+ further, for the slug alone."""
+ return text.translate(LATIN_TO_ASCII)
+
+
+def slugify(latin):
+ s = normalize_latin(latin)
+ s = unicodedata.normalize("NFKD", s)
+ s = "".join(c for c in s if not unicodedata.combining(c))
+ s = s.lower()
+ s = re.sub(r"[^a-z0-9]+", "-", s)
+ s = re.sub(r"-+", "-", s).strip("-")
+ return s
+
+
+# Words to drop when deriving a slug from a title, so slugs read as names
+# rather than descriptions (kept short and stable, matching data/ef/
+# sanctoral.sexp's own style, e.g. "agnes", "all-saints").
+DROP_WORDS = {
+ "s", "ss", "b", "in", "et", "de", "ad", "a", "the", "of",
+}
+
+
+def title_slug(latin, month, day):
+ words = re.split(r"[\s,]+", normalize_latin(latin))
+ kept = []
+ for w in words:
+ base = re.sub(r"\.$", "", w)
+ if base.lower() in DROP_WORDS or base == "":
+ continue
+ kept.append(base)
+ if len(kept) >= 6:
+ break
+ if not kept:
+ kept = [f"of-{month:02d}-{day:02d}"]
+ return slugify(" ".join(kept))
+
+
+def classify_colour(month, day, latin):
+ key = (month, day)
+ if key == ALL_SOULS_OVERRIDE:
+ return "Violet"
+ if key in RED_OVERRIDES:
+ return "Red"
+ if key in DEDICATION_DATES:
+ return "White"
+ if "martyr" in latin.lower():
+ return "Red"
+ if key in WHITE_APOSTLE_EXCEPTIONS:
+ return "White"
+ lower = latin.lower()
+ if "apostol" in lower or "evangelist" in lower:
+ return "Red"
+ return "White"
+
+
+def classify_subject(month, day, latin):
+ key = (month, day)
+ if key in LORD_DATES:
+ return "Lord"
+ lower = normalize_latin(latin).lower()
+ if key != (3, 19) and ("maria" in lower or "b.m.v" in lower or "bmv" in lower):
+ return "Bvm"
+ return "Saint"
+
+
+def parse_lectio_ini(path):
+ entries = []
+ cur = None
+ try:
+ with open(path, encoding="utf-8") as f:
+ raw = f.read()
+ except FileNotFoundError:
+ return entries
+ for line in raw.split("\n"):
+ line = line.rstrip("\n")
+ if line.startswith("[") and line.endswith("]"):
+ if cur is not None:
+ entries.append(cur)
+ cur = {"slug": line[1:-1]}
+ elif "=" in line and cur is not None:
+ k, _, v = line.partition("=")
+ cur[k.strip()] = v.strip()
+ if cur is not None:
+ entries.append(cur)
+ return [e for e in entries if "date" in e]
+
+
+LECTIO_RANK_TO_VOCAB = {
+ "solemnity": "Sollemnitas",
+ "feast": "Festum",
+ "memorial": "Memoria_obligatoria",
+ "optional": "Memoria_ad_libitum",
+}
+
+
+def significant_words(text):
+ words = re.split(r"[\s,.;]+", normalize_latin(text).lower())
+ return {w for w in words if w and w not in DROP_WORDS and len(w) > 2}
+
+
+def cross_check(entries, lectio_entries):
+ """Returns (report_lines, name_en_by_id) where name_en_by_id maps
+ id(entry dict) -> matched lectio name.en, and report_lines is the
+ divergence log for the provenance header."""
+ by_date = {}
+ for le in lectio_entries:
+ d = le.get("date", "")
+ m = re.match(r"^(\d\d)-(\d\d)$", d)
+ if not m:
+ continue
+ key = (int(m.group(1)), int(m.group(2)))
+ by_date.setdefault(key, []).append(le)
+
+ by_date_missal = {}
+ for e in entries:
+ by_date_missal.setdefault((e["month"], e["day"]), []).append(e)
+
+ report = []
+ name_en = {}
+ lectio_slug = {}
+ agree_rank = 0
+ total_matched = 0
+ matched_lectio_ids = set()
+
+ for key, missal_list in sorted(by_date_missal.items()):
+ lectio_list = by_date.get(key, [])
+ if len(missal_list) == 1 and len(lectio_list) == 1:
+ pairs = [(missal_list[0], lectio_list[0])]
+ else:
+ pairs = []
+ remaining = list(lectio_list)
+ for me in missal_list:
+ if not remaining:
+ break
+ mwords = significant_words(me["latin"])
+ best = max(remaining, key=lambda le: len(mwords & significant_words(le.get("name.la", ""))))
+ pairs.append((me, best))
+ remaining.remove(best)
+ for me, le in pairs:
+ total_matched += 1
+ matched_lectio_ids.add(id(le))
+ name_en[id(me)] = le.get("name.en")
+ lectio_slug[id(me)] = le.get("slug")
+ missal_rank = GRADE_TO_RANK[me["grade"]]
+ lectio_rank = LECTIO_RANK_TO_VOCAB.get(le.get("rank", ""), "?")
+ if missal_rank == lectio_rank:
+ agree_rank += 1
+ else:
+ report.append(
+ f" {key[0]:02d}-{key[1]:02d} RANK colitur={missal_rank} lectio={lectio_rank} "
+ f"[{me['latin']!r} vs {le.get('name.la','')!r}]"
+ )
+ missal_colour = classify_colour(key[0], key[1], me["latin"])
+ lectio_colour = (le.get("colour", "") or "").capitalize()
+ if missal_colour != lectio_colour:
+ report.append(
+ f" {key[0]:02d}-{key[1]:02d} COLOUR colitur={missal_colour} lectio={lectio_colour} "
+ f"[{me['latin']!r}]"
+ )
+ if not lectio_list:
+ for me in missal_list:
+ report.append(f" {key[0]:02d}-{key[1]:02d} MISSAL-ONLY {me['latin']!r} (grade={me['grade']})")
+ elif len(lectio_list) > len(missal_list):
+ # A shared date where lectio carries MORE entries than the
+ # Missal transcription found -- the pairing loop above only
+ # consumes len(missal_list) of them, so the rest would
+ # otherwise vanish silently. Surfaced explicitly.
+ for le in lectio_list:
+ if id(le) not in matched_lectio_ids:
+ report.append(
+ f" {key[0]:02d}-{key[1]:02d} LECTIO-EXTRA (same date, uncovered by Missal pairing) "
+ f"{le.get('name.la','')!r} rank={le.get('rank')} slug={le.get('slug')}"
+ )
+
+ missal_dates = set(by_date_missal)
+ for key, lectio_list in sorted(by_date.items()):
+ if key in SKIP_DATES:
+ continue
+ for le in lectio_list:
+ if id(le) not in matched_lectio_ids and key not in missal_dates:
+ report.append(f" {key[0]:02d}-{key[1]:02d} LECTIO-ONLY {le.get('name.la','')!r} "
+ f"rank={le.get('rank')} slug={le.get('slug')}")
+
+ summary = [
+ f"lectio cross-check: {total_matched} dates paired; rank agrees on {agree_rank}/{total_matched}.",
+ ]
+ return summary + report, name_en, lectio_slug
+
+
+def render_sexp(entries, name_en, letterspace_count, skipped, meta):
+ lines = []
+ lines.append(f"; data/of/calendar-2002.sexp -- OF (post-1970) General Roman Calendar,")
+ lines.append(f"; transcribed from the Missale Romanum, editio typica tertia (2002). The")
+ lines.append(f"; 2002 Missal is the AUTHORITY; this file is never edited afterwards --")
+ lines.append(f"; spec sec4.1. lectio's roman-calendar.ini is a cross-check only, never a")
+ lines.append(f"; source (see tools/extract_of_calendar.py's own header for the full")
+ lines.append(f"; source-discipline statement and every classification citation).")
+ lines.append(f";")
+ lines.append(f"; Generator: {TOOL_PATH} -- do not hand-edit; re-run against the same")
+ lines.append(f"; PDF (this file is pinned, so a re-run should reproduce it byte-for-byte)")
+ lines.append(f"; and diff before committing.")
+ lines.append(f";")
+ lines.append(f"; Source: {PDF_PATH}")
+ lines.append(f"; SHA-256: {meta['pdf_sha256']}")
+ lines.append(f"; Extracted lines (pdftotext -layout, 0-based): {meta['start']}-{meta['end']}")
+ lines.append(f"; Extraction date (UTC): {meta['extraction_date']}")
+ lines.append(f";")
+ lines.append(f"; {len(entries)} entries. {letterspace_count} entry needed letter-spacing repair")
+ lines.append(f"; (collapse_letterspacing's own mechanical pass, then hand-verified -- see")
+ lines.append(f"; the module docstring's EXTRACTION ARTIFACT note).")
+ lines.append(f";")
+ lines.append(f"; Excluded, deliberately (see module docstring for the full reasoning):")
+ for (m, d), why in sorted(skipped.items()):
+ lines.append(f"; {m:02d}-{d:02d}: {why}")
+ lines.append(f"; Movable universal solemnities (Baptism of the Lord, Holy Family,")
+ lines.append(f"; Trinity, Corpus Christi, Christ the King -- Temporal_of code; the")
+ lines.append(f"; Sacred Heart of Jesus and the Immaculate Heart of Mary -- present in")
+ lines.append(f"; the 2002 table but covered by NEITHER Temporal_of NOR this file, a")
+ lines.append(f"; genuine gap this task found and reports but does not fix).")
+ lines.append(f";")
+ for l in meta["cross_check_report"]:
+ lines.append(f"; {l}")
+ lines.append("")
+
+ def esc(s):
+ return s.replace("\\", "\\\\").replace('"', '\\"')
+
+ lines.append("((id of-universal) (name \"OF (2002) General Roman Calendar\")")
+ lines.append(" (entries")
+ lines.append(" (")
+ for idx, e in enumerate(entries):
+ la = normalize_latin(e["latin"])
+ en = name_en.get(id(e))
+ names_parts = []
+ if en:
+ names_parts.append(f'(en "{esc(en)}")')
+ names_parts.append(f'(la "{esc(la)}")')
+ names_str = " ".join(names_parts)
+ rank = GRADE_TO_RANK[e["grade"]]
+ colour = classify_colour(e["month"], e["day"], e["latin"])
+ subject = classify_subject(e["month"], e["day"], e["latin"])
+ slug = e["slug"]
+ prefix = " " if idx > 0 else " "
+ lines.append(f"{prefix}((date (Fixed (month {e['month']}) (day {e['day']})))")
+ lines.append(f" (cel")
+ lines.append(f" ((slug {slug})")
+ lines.append(f" (names ({names_str}))")
+ lines.append(f" (rank {rank}) (status Feast) (colour {colour})")
+ lines.append(f" (subject {subject}) (citations ()) (layer of-universal))))")
+ lines.append(" )))")
+ return "\n".join(lines) + "\n"
+
+
+def main():
+ if len(sys.argv) > 1:
+ with open(sys.argv[1], encoding="utf-8") as f:
+ text = f.read()
+ else:
+ text = get_text(PDF_PATH)
+
+ start, end, table_lines = bound_table(text)
+ entries, letterspace_count = parse_rows(table_lines)
+
+ # Hand-verified correction for the one letter-spaced entry the
+ # mechanical pass cannot fully resolve (see module docstring).
+ for e in entries:
+ if (e["month"], e["day"]) == (2, 21) and "episcopiet" in e["latin"]:
+ e["latin"] = "S. Petri Damiani, episcopi et Ecclesiae doctoris"
+
+ skipped = {}
+ kept = []
+ for e in entries:
+ key = (e["month"], e["day"])
+ if key in SKIP_DATES:
+ skipped[key] = SKIP_DATES[key]
+ continue
+ kept.append(e)
+ entries = kept
+
+ # All Souls override (see module docstring).
+ for e in entries:
+ if (e["month"], e["day"]) == ALL_SOULS_OVERRIDE:
+ e["grade"] = "Sollemnitas"
+
+ lectio_entries = parse_lectio_ini(LECTIO_INI)
+ cross_check_report, name_en, lectio_slug = cross_check(entries, lectio_entries)
+
+ # Slugs: prefer lectio's OWN identifier when a confident match exists --
+ # it is already a clean, conventional, human-authored identifier
+ # ("raymond-of-penyafort-priest") matching data/ef/sanctoral.sexp's own
+ # style, and reusing it is not a source-discipline violation: a slug is
+ # a stable ENGINEERING KEY, not liturgical content -- every substantive
+ # field (date, rank, colour, the `la` name) still comes from the Missal
+ # alone. Falls back to a mechanical slug built from the Latin title for
+ # any entry lectio has no match for (none, as it happens, in this run --
+ # every one of the 206 entries paired -- but kept for robustness against
+ # a future lectio update dropping an entry). Collisions suffixed either
+ # way.
+ seen = {}
+ for e in entries:
+ candidate = lectio_slug.get(id(e))
+ base = slugify(candidate) if candidate else title_slug(e["latin"], e["month"], e["day"])
+ if not base:
+ base = title_slug(e["latin"], e["month"], e["day"])
+ if base in seen:
+ seen[base] += 1
+ e["slug"] = f"{base}-{seen[base]}"
+ else:
+ seen[base] = 1
+ e["slug"] = base
+
+ entries.sort(key=lambda e: e["slug"])
+
+ meta = {
+ "pdf_sha256": sha256_file(PDF_PATH),
+ "start": start,
+ "end": end,
+ "extraction_date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
+ "cross_check_report": cross_check_report,
+ }
+
+ sys.stdout.write(render_sexp(entries, name_en, letterspace_count, skipped, meta))
+
+ print(f"[extract_of_calendar] {len(entries)} entries emitted, {letterspace_count} needed "
+ f"letter-spacing repair, {len(skipped)} dates excluded (temporal_of.ml coverage)",
+ file=sys.stderr)
+ for l in cross_check_report:
+ print(f"[extract_of_calendar] {l}", file=sys.stderr)
+
+
+if __name__ == "__main__":
+ main()