1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
# colitur
**Download:** [colitur-latest.tar.gz](https://git.labunix.xyz/colitur-latest.tar.gz)
— a stable URL that always serves the current release, so a script can fetch it
without knowing the version. Per-tag tarballs are on the
[tags page](https://git.labunix.xyz/colitur.git/refs/tags) if you want a
specific one.
*computus liturgicus* — a safe, highly-tested, deterministic engine that computes
and validates liturgical calendars, with template-driven output.
Computes day-identity (season, celebration, rank, colour, precedence) and reading
citations, correct to year 9999.
**Two rites are built: the Roman EF (1962) and the Roman OF (post-1970,
base calendar the 2002 *Missale Romanum*).** `lib/rites/` contains
`rite_ef` and `rite_of`, both plugging into the same rite-agnostic kernel
via `Rite.t` — the abstraction the OF module exists to prove generalises.
Select the rite with `--rite ef|of` on `day`/`readings` (default `ef`); see
`colitur.1`'s own `--rite` entry. The EF is the more thoroughly validated
of the two, checked against five independent layers including a published
Ordo witness; the OF is younger — no published-ordo witness yet, a
Polish-vernacular lectionary lineage (not the Latin OLM itself), a handful
of known-wrong days pinned rather than fixed (St Joseph anticipated onto
Palm Sunday, Normae n. 56(f); the Holy Family Sunday fallback of Normae
n. 35(a)), and its lectionary carries only the first reading and the
Gospel, not the Second Reading or the responsorial psalm. See the design
and rules research under `docs/` (local).
## Build
```sh
opam switch create . 5.2.0 -y # first time: local OCaml switch
opam install -y dune alcotest qcheck qcheck-alcotest sexplib ppx_sexp_conv
dune build
dune test # fast suite, 909 tests (~40s)
COLITUR_EXHAUSTIVE_SWEEP=1 dune test --force # + every year 1583-9999 (~8min)
dune exec colitur -- easter 2026 # Easter and its Easter-relative anchors
dune exec colitur -- temporal 2026 # the EF temporal cycle only, one line per day
dune exec colitur -- day 2026 # the full resolved EF calendar (temporal + sanctoral)
dune exec colitur -- day --rite of 2026 # the same, for the OF
```
## Rendering
```sh
# The ordo booklet has a table of contents (page numbers via \pageref), so
# it needs TWO pdflatex passes -- the first pass leaves every entry showing
# "??"; the second resolves them. A single pass is not a bug in the
# template, it is just an unfinished build. Either form works:
dune exec colitur -- table --year 2027 --template templates/ef/ordo.tex > ordo.tex && pdflatex ordo.tex && pdflatex ordo.tex
# or: latexmk -pdf ordo.tex
# The wall calendar has no cross-references, so one pass is enough:
dune exec colitur -- table --year 2027 --template templates/ef/grid.tex > grid.tex && pdflatex grid.tex
dune exec colitur -- publish --from 2027 --to 2027 --out ./public
```
See `colitur-templates(5)` for the template format (syntax, escaping, the
full field reference) and `colitur(1)` for `emit`, `table`/`render` and
`publish` in full.
## Language
Every command's output is names, not slugs, by default (`--raw` restores
the bare-slug form). Start a new translation from the shipped Latin table,
edit a couple of entries, check it, and render with it -- every line below
is real, not transcribed:
```sh
$ dune exec colitur -- lang --dump la > my-lang.ini
$ sed -i \
-e 's/^ef-circumcision = .*/ef-circumcision = My Own Circumcision Text/' \
-e 's/^ef-epiphany = .*/ef-epiphany = My Own Epiphany Text/' \
my-lang.ini
$ dune exec colitur -- lang --check my-lang.ini
my-lang.ini: 725 of 725 celebrations named, 0 missing, 0 unknown
$ dune exec colitur -- day 2026 --lang ./my-lang.ini | head -1
2026-01-01 thursday christmastide - ef-circumcision class-1 white My Own Circumcision Text
```
`--check` reports what a language file is still missing, and rejects any
entry naming a slug that does not exist (a typo, otherwise silently dead).
`colitur lang --list` shows what this build can find; `colitur config
--show` shows the effective `--lang` (and every other setting) plus where
it came from. See `colitur(1)`'s `NAMING` section and `colitur-config(5)`
for both in full.
## Citations
A reading citation (`Jn 3:16`) is parsed into structure and re-rendered, so
its book names, its punctuation/abbreviation convention, and its numbering
tradition are each a file to edit, not something the engine hardcodes. One
day, four ways -- every line below is real, not transcribed:
```sh
$ dune exec colitur -- readings 2026 | grep ^2026-02-25
2026-02-25 ef-lent-ember-wed | 3 Reg 19:3-8 | Matth 12:38-50 | Feria IV Quatuor Temporum Quadragesimae
$ dune exec colitur -- readings 2026 --sigla-book full | grep ^2026-02-25
2026-02-25 ef-lent-ember-wed | Liber Regum III 19:3-8 | Evangelium secundum Matthaeum 12:38-50 | Feria IV Quatuor Temporum Quadragesimae
$ dune exec colitur -- readings 2026 --lang en | grep ^2026-02-25
2026-02-25 ef-lent-ember-wed | 3 Kgs. 19:3-8 | Matt 12:38-50 | Lenten Ember Wednesday
$ dune exec colitur -- readings 2026 --lang en --sigla-tradition modern | grep ^2026-02-25
2026-02-25 ef-lent-ember-wed | 1 Kgs 19:3-8 | Matt 12:38-50 | Lenten Ember Wednesday
```
The same four results, from a config file instead of flags (each block is
the entire `[defaults]` section on its own -- not one file with all four):
```ini
# 1. Latin abbreviated -- the built-in default, no entries needed
[defaults]
# 2. Latin full
[defaults]
sigla_book = full
# 3. English (Vulgate numbering: "3 Kings", not "1 Kings")
[defaults]
lang = en
# 4. English, modern numbering
[defaults]
lang = en
sigla_tradition = modern
```
Two axes are independent and easy to conflate. `--lang`/`sigla_book` (and
a language file's own `[bible]` section) decide what a book is *called* --
this varies by language. `--sigla-tradition` (`lang/traditions.ini`)
decides which book a reference *denotes* -- this does not vary by
language. `3 Kings 19:3-8` and `1 Kings 19:3-8` above are the *same* verse,
renumbered, not two different readings; conflating naming with renumbering
is how a citation ends up naming the wrong book.
`--raw` bypasses all of it: every citation is emitted exactly as stored,
byte-for-byte, independently of the parser -- what a script diffing this
engine's output against the sibling `lectio` engine depends on:
```sh
$ dune exec colitur -- readings 2026 --raw | grep ^2026-02-25
2026-02-25 ef-lent-ember-wed | 3 Kgs. 19:3-8 | Matt 12:38-50
```
See `colitur(1)`'s `SIGLA` section for the `[bible]`/`[sigla]` language-file
sections, `lang/traditions.ini` in full, and all three `--sigla-*` flags.
## Status
Releases through `v1.0.0` are EF only; `v1.1.0` added the OF module as a
peer rite (see above), and `v1.2.0` adds `--pretty` and the `--month`/
`--date`/`--today` narrowing flags.
**916 tests**, of which the property suite runs against every year in
1583-9999 rather than a sample — that is how confidence extends past the
~2050 horizon of any reference to check against. The six layers below
describe the EF's own validation specifically; the OF's own (also
property, differential — against `litcal`, an independent second lineage
— and golden) is younger and lighter, with no published-ordo witness yet.
Six validation layers, each blind to something the others catch (EF):
| | |
|---|---|
| types | illegal states unrepresentable; resolution total |
| properties | invariants over all 8 417 years (QCheck) |
| differential | 16 801 days compared against the sibling `lectio` engine |
| oracle | three captured years compared against missalemeum |
| ordo | one liturgical year compared against a printed Latin Mass Society Ordo |
| golden | 11 pinned template outputs |
Knowing what each layer *cannot* see is load-bearing here: the differential and
the missalemeum oracle share one data lineage (Divinum Officium → missalemeum
→ lectio → colitur), so an error inherited by both is invisible to either. The
Ordo layer is a genuinely separate lineage — a clergy-compiled book, never
passed through that chain — but has its own blind spots: it covers one civil
year, only three of the reading-formulary's five sources (a documented
data-representation ambiguity excludes the rest), and England & Wales
diocesan propers are excluded from every comparison, universal-calendar days
only. That is written up in full in `docs/` rather than left implied.
**Chants are deliberately absent.** Psalm, Tract, Alleluia and Sequence have no
source and no oracle, so `Validate` rejects any citation part outside
First/Gospel — one appearing would be a defect, not a feature arriving early.
A companion X11 viewer lives in
[colitur-x](https://git.labunix.xyz/colitur-x.git/): it reads
`colitur emit --format csv` on stdin and draws the month.
## License
AGPL-3.0-or-later. See `LICENSE`.
|