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
|
(* EF (1962) temporal cycle. Every boundary and rank rule cites its Rubricae
Generales paragraph; see docs/research/rules-register.md §3-§4. *)
open Colitur_kernel
open Vocab_ef
let mk y m d =
match Date.make ~year:y ~month:m ~day:d with
| Ok t -> t
| Error e -> failwith ("temporal_ef: " ^ e)
let weekday_index d =
match Date.weekday d with
| Date.Sun -> 0 | Date.Mon -> 1 | Date.Tue -> 2 | Date.Wed -> 3
| Date.Thu -> 4 | Date.Fri -> 5 | Date.Sat -> 6
(* The Sunday on or before [d]. *)
let sunday_on_or_before d = Date.add_days d (-(weekday_index d))
(* RG 71: Advent I is the Sunday nearest 30 November -- equivalently the fourth
Sunday before Christmas, i.e. three weeks before the last Sunday on or before
24 December. *)
let advent_start y = Date.add_days (sunday_on_or_before (mk y 12 24)) (-21)
let year_start = advent_start
let before a b = Date.compare a b < 0
let on_or_after a b = Date.compare a b >= 0
(* RG 71-77. Tested in chronological order within the civil year. *)
let season d =
let y = Date.year d in
let easter = Computus.gregorian_easter y in
let advent_this = advent_start y in
let christmas_this = mk y 12 25 in
let jan14 = mk y 1 14 in
let septuagesima_sunday = Date.add_days easter (-63) in
let ash_wednesday = Date.add_days easter (-46) in
let passion_sunday = Date.add_days easter (-14) in
let paschal_end = Date.add_days easter 55 in
if on_or_after d advent_this && before d christmas_this then Advent (* RG 71 *)
else if on_or_after d christmas_this then Christmastide (* RG 72-73: 25-31 Dec *)
else if before d jan14 then Christmastide (* RG 72-73: 1-13 Jan inclusive *)
else if before d septuagesima_sunday then Time_after_epiphany (* RG 77: from 14 Jan *)
else if before d ash_wednesday then Septuagesima (* RG 73 *)
else if before d passion_sunday then Lent (* RG 74 *)
else if before d easter then Passiontide (* RG 75; Holy Saturday included *)
else if Date.compare d paschal_end <= 0 then Paschaltide (* RG 76 *)
else Time_after_pentecost (* RG 77 *)
(* Last Sunday of October, per the 1960 calendar -- NOT the OF's last Sunday
before Advent. Register §6 flags this for primary-source confirmation. *)
let christ_the_king y = sunday_on_or_before (mk y 10 31)
let same a b = Date.compare a b = 0
(* Named temporal days: the I-class feasts of the Lord (RG 91 entries 1 and 3),
the Vigil and Octave Day of the Nativity and the Vigil of Pentecost (RG
28-34, RG 91 entries 5 and 9), the I-class Sundays of Passiontide and Low
Sunday (RG 91 entry 6), Ash Wednesday (RG 91 entry 7), and the days within
the Octave of the Nativity (RG 63-70, RG 91 entry 17).
Returns (season, slug, colour, rank, week). *)
let named d =
let y = Date.year d in
let easter = Computus.gregorian_easter y in
let off n = Date.add_days easter n in
let m = Date.month d and dd = Date.day d in
if m = 12 && dd = 25 then Some (Christmastide, "ef-nativity", Colour.White, Class1, None)
else if m = 12 && dd = 24 then
(* RG 91 entry 5: the Vigil of the Nativity is I class. lectio has no slug
for it, so this key has no lectionary entry until Plan 3 fills it. *)
Some (Advent, "ef-nativity-vigil", Colour.Violet, Class1, None)
else if m = 12 && (dd = 29 || dd = 30 || dd = 31) then
(* Days within the Octave of the Nativity; 26-28 Dec are Stephen, John and
the Innocents, hence sanctoral (Plan 3). Colitur slugs -- lectionary gap. *)
Some (Christmastide, Printf.sprintf "ef-nativity-octave-day-%d" (dd - 24), Colour.White, Class2, None)
else if m = 1 && dd = 1 then
(* RG 91 entry 5: 1 Jan is the Octave Day of the Nativity, the same table
entry as the Nativity vigil above. *)
Some (Christmastide, "ef-circumcision", Colour.White, Class1, None)
else if m = 1 && dd = 6 then Some (Christmastide, "ef-epiphany", Colour.White, Class1, None)
else if same d (off (-46)) then
Some (Lent, "ef-ash-wednesday", Colour.Violet, Class1, None) (* RG 91 entry 7 *)
else if same d (off (-14)) then
Some (Passiontide, "ef-passion-sunday", Colour.Violet, Class1, Some 1) (* RG 91 entry 6 *)
else if same d (off (-7)) then
Some (Passiontide, "ef-palm-sunday", Colour.Violet, Class1, Some 2) (* RG 91 entry 6 *)
else if same d easter then Some (Paschaltide, "ef-easter-sunday", Colour.White, Class1, Some 1)
else if same d (off 7) then
Some (Paschaltide, "ef-low-sunday", Colour.White, Class1, Some 2) (* RG 91 entry 6 *)
else if same d (off 38) then
(* RG 91 entry 21: II-class vigil. It is also Rogation Wednesday; with no
precedence framework until Plan 3, temporal emits the higher-ranked
vigil and the Rogation commemoration waits for RG 108-111. *)
Some (Paschaltide, "ef-ascension-vigil", Colour.White, Class2, None)
else if same d (off 39) then Some (Paschaltide, "ef-ascension", Colour.White, Class1, None)
else if same d (off 48) then
Some (Paschaltide, "ef-pentecost-vigil", Colour.Red, Class1, None) (* RG 91 entry 9 *)
else if same d (off 49) then Some (Paschaltide, "ef-pentecost", Colour.Red, Class1, None)
else if same d (off 56) then Some (Time_after_pentecost, "ef-trinity", Colour.White, Class1, Some 1)
else if same d (off 60) then
Some (Time_after_pentecost, "ef-corpus-christi", Colour.White, Class1, None)
else if same d (off 68) then
Some (Time_after_pentecost, "ef-sacred-heart", Colour.White, Class1, None)
else if same d (christ_the_king y) then
Some (Time_after_pentecost, "ef-christ-the-king", Colour.White, Class1, None)
else None
let days_between a b = Date.to_rata b - Date.to_rata a
(* Floor division. OCaml's [/] truncates toward zero, so a date before a
season's week origin would round up into week 1 instead of falling out of the
numbering: Ash Wednesday is 4 days before the Lent I origin, and -4/7 = 0
would make it week 1. *)
let floor_div a b = if a >= 0 then a / b else ((a + 1) / b) - 1
(* The Sunday on which week 1 of a season begins. Every origin is a Sunday, so
week numbers are constant Sunday-to-Saturday.
Christmastide has no numbered weeks. Time after Epiphany counts from the
first Sunday after Epiphany -- which itself falls 7-13 January and is
therefore inside Christmastide (RG 72-73), so the season's own days start
part-way through week 1. Time after Pentecost counts from Pentecost, making
Trinity Sunday the first Sunday after Pentecost. *)
let week_origin s y =
let easter = Computus.gregorian_easter y in
match s with
| Advent -> Some (advent_start y)
| Christmastide -> None
| Time_after_epiphany -> Some (Date.add_days (sunday_on_or_before (mk y 1 6)) 7)
| Septuagesima -> Some (Date.add_days easter (-63))
| Lent -> Some (Date.add_days easter (-42)) (* Lent I Sunday *)
| Passiontide -> Some (Date.add_days easter (-14))
| Paschaltide -> Some easter
| Time_after_pentecost -> Some (Date.add_days easter 49) (* Pentecost *)
let week d =
let s = season d in
match week_origin s (Date.year d) with
| None -> None
| Some origin ->
let n = floor_div (days_between origin d) 7 in
let n = match s with Time_after_pentecost -> n | _ -> n + 1 in
if n < 1 then None else Some n
(* Sunday slugs. These are lectionary keys: they use [season_slug_word], and for
Christmastide they keep lectio's keys even though colitur's season differs
(spec §4.4 -- slugs are opaque keys, not truth). *)
let sunday_slug d =
if Date.weekday d <> Date.Sun then None
else
let y = Date.year d in
let m = Date.month d and dd = Date.day d in
let s = season d in
match s with
| Christmastide ->
if m = 12 && dd >= 26 then Some "ef-christmas-sunday-0"
else if m = 1 && dd >= 7 && dd <= 13 then
(* 1st Sunday after Epiphany (Holy Family). Season is Christmastide per
RG 72-73; the key stays lectio's. *)
Some "ef-time-after-epiphany-sunday-1"
else if m = 1 && dd >= 2 && dd <= 5 then
(* Most Holy Name of Jesus. Colitur slug -- lectionary gap; confirm the
placement against MR1962 while coding (register §6). *)
Some "ef-holy-name-sunday"
else None
| Time_after_pentecost -> (
let easter = Computus.gregorian_easter y in
let pentecost = Date.add_days easter 49 in
let last_sunday = Date.add_days (advent_start y) (-7) in
let n = days_between pentecost d / 7 in
if same d last_sunday then
(* The last Sunday before Advent always keeps the 24th (Last) Mass. *)
Some "ef-time-after-pentecost-sunday-24"
else if n > 23 then
(* Surplus Sundays resume the Sundays after Epiphany that Septuagesima
cut short -- the highest-numbered ones, so the 6th sits just before
the Last. *)
let total = days_between pentecost last_sunday / 7 in
Some (Printf.sprintf "ef-time-after-epiphany-sunday-%d" (n - total + 7))
else Some (Printf.sprintf "ef-time-after-pentecost-sunday-%d" n))
| _ -> (
match week d with
| Some n -> Some (Printf.sprintf "ef-%s-sunday-%d" (season_slug_word s) n)
| None -> None)
|