calendarium-romanum 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/calendariumrom +65 -0
- data/lib/calendarium-romanum.rb +13 -0
- data/lib/calendarium-romanum/abstract_date.rb +59 -0
- data/lib/calendarium-romanum/calendar.rb +130 -0
- data/lib/calendarium-romanum/day.rb +59 -0
- data/lib/calendarium-romanum/enums.rb +84 -0
- data/lib/calendarium-romanum/sanctorale.rb +80 -0
- data/lib/calendarium-romanum/sanctoraleloader.rb +100 -0
- data/lib/calendarium-romanum/temporale.rb +382 -0
- data/lib/calendarium-romanum/transfers.rb +44 -0
- data/lib/calendarium-romanum/util.rb +40 -0
- data/spec/abstract_date_spec.rb +45 -0
- data/spec/calendar_spec.rb +217 -0
- data/spec/date_spec.rb +10 -0
- data/spec/rank_spec.rb +30 -0
- data/spec/sanctorale_spec.rb +144 -0
- data/spec/sanctoraleloader_spec.rb +142 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/temporale_spec.rb +342 -0
- metadata +231 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
module CalendariumRomanum
|
2
|
+
|
3
|
+
# understands a plaintext calendar format
|
4
|
+
# and knows how to transform it to Celebrations
|
5
|
+
# and fill them in a Sanctorale
|
6
|
+
#
|
7
|
+
# Format of the file:
|
8
|
+
# 1/31 m : S. Ioannis Bosco, presbyteri
|
9
|
+
#
|
10
|
+
# <month>/<day> <rank shortcut> : <title>
|
11
|
+
# rank shortcut is optional, default value is optional memorial
|
12
|
+
class SanctoraleLoader
|
13
|
+
|
14
|
+
RANK_CODES = {
|
15
|
+
nil => Ranks::MEMORIAL_OPTIONAL,
|
16
|
+
'm' => Ranks::MEMORIAL_GENERAL,
|
17
|
+
'f' => Ranks::FEAST_GENERAL,
|
18
|
+
's' => Ranks::SOLEMNITY_GENERAL
|
19
|
+
}
|
20
|
+
COLOUR_CODES = {
|
21
|
+
nil => Colours::WHITE,
|
22
|
+
'W' => Colours::WHITE,
|
23
|
+
'V' => Colours::VIOLET,
|
24
|
+
'G' => Colours::GREEN,
|
25
|
+
'R' => Colours::RED
|
26
|
+
}
|
27
|
+
|
28
|
+
# dest should be a Sanctorale,
|
29
|
+
# src anything with #each_line
|
30
|
+
def load(dest, src)
|
31
|
+
month_section = nil
|
32
|
+
src.each_line.each_with_index do |l, li|
|
33
|
+
line_num = li + 1
|
34
|
+
|
35
|
+
# strip whitespace and comments
|
36
|
+
l.sub!(/#.*/, '')
|
37
|
+
l.strip!
|
38
|
+
next if l.empty?
|
39
|
+
|
40
|
+
# month section heading
|
41
|
+
n = l.match /^=\s*(\d+)\s*$/
|
42
|
+
unless n.nil?
|
43
|
+
month_section = n[1].to_i
|
44
|
+
unless month_section >= 1 && month_section <= 12
|
45
|
+
raise error("Invalid month #{month_section}", line_num)
|
46
|
+
end
|
47
|
+
next
|
48
|
+
end
|
49
|
+
|
50
|
+
# celebration record
|
51
|
+
m = l.match /^((\d+)\/)?(\d+)\s*(([mfs])(\d\.\d+)?)?\s*([WVRG])?\s*:(.*)$/
|
52
|
+
if m.nil?
|
53
|
+
raise error("Syntax error, line skipped '#{l}'", line_num)
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
month, day, rank_char, rank_num, colour, title = m.values_at(2, 3, 5, 6, 7, 8)
|
58
|
+
month ||= month_section
|
59
|
+
day = day.to_i
|
60
|
+
month = month.to_i
|
61
|
+
|
62
|
+
rank = RANK_CODES[rank_char]
|
63
|
+
if rank.nil?
|
64
|
+
raise error("Invalid celebration rank code #{rank_char}", line_num)
|
65
|
+
end
|
66
|
+
|
67
|
+
if rank_num
|
68
|
+
rank_num = rank_num.to_f
|
69
|
+
rank_by_num = Ranks[rank_num]
|
70
|
+
|
71
|
+
if rank_by_num.nil?
|
72
|
+
raise error("Invalid celebration rank code #{rank_num}", line_num)
|
73
|
+
elsif rank.priority.to_i != rank_by_num.priority.to_i
|
74
|
+
raise error("Invalid combination of rank letter #{rank_char.inspect} and number #{rank_num}.", line_num)
|
75
|
+
end
|
76
|
+
|
77
|
+
rank = rank_by_num
|
78
|
+
end
|
79
|
+
|
80
|
+
dest.add month, day, Celebration.new(
|
81
|
+
title.strip,
|
82
|
+
rank,
|
83
|
+
COLOUR_CODES[colour]
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
alias_method :load_from_string, :load
|
89
|
+
|
90
|
+
def load_from_file(dest, filename, encoding='utf-8')
|
91
|
+
self.load dest, File.open(filename, 'r', encoding: encoding)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def error(message, line_number)
|
97
|
+
RuntimeError.new("L#{line_number}: #{message}")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module CalendariumRomanum
|
4
|
+
|
5
|
+
# determine seasons and dates of the Temporale feasts of the given year
|
6
|
+
class Temporale
|
7
|
+
|
8
|
+
WEEK = 7
|
9
|
+
|
10
|
+
SEASON_COLOUR = {
|
11
|
+
Seasons::ADVENT => Colours::VIOLET,
|
12
|
+
Seasons::CHRISTMAS => Colours::WHITE,
|
13
|
+
Seasons::ORDINARY => Colours::GREEN,
|
14
|
+
Seasons::LENT => Colours::VIOLET,
|
15
|
+
Seasons::EASTER => Colours::WHITE,
|
16
|
+
}
|
17
|
+
|
18
|
+
# year is Integer - the civil year when the liturgical year begins
|
19
|
+
def initialize(year=nil)
|
20
|
+
@year = year
|
21
|
+
prepare_solemnities unless @year.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# Determines liturgical year for the given date
|
26
|
+
def liturgical_year(date)
|
27
|
+
year = date.year
|
28
|
+
temporale = Temporale.new year
|
29
|
+
|
30
|
+
if date < temporale.first_advent_sunday
|
31
|
+
return year - 1
|
32
|
+
end
|
33
|
+
|
34
|
+
return year
|
35
|
+
end
|
36
|
+
|
37
|
+
# creates a Calendar for the liturgical year including given
|
38
|
+
# date
|
39
|
+
def for_day(date)
|
40
|
+
return new(liturgical_year(date))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def start_date(year=nil)
|
45
|
+
first_advent_sunday(year)
|
46
|
+
end
|
47
|
+
|
48
|
+
def end_date(year=nil)
|
49
|
+
year ||= @year
|
50
|
+
first_advent_sunday(year+1) - 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def date_range(year=nil)
|
54
|
+
start_date(year) .. end_date(year)
|
55
|
+
end
|
56
|
+
|
57
|
+
def range_check(date)
|
58
|
+
unless date_range.include? date
|
59
|
+
raise RangeError.new "Date out of range #{date}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# converts an AbstractDate to a Date in the given
|
64
|
+
# liturgical year
|
65
|
+
def concretize_abstract_date(abstract_date)
|
66
|
+
d = abstract_date.concretize(@year + 1)
|
67
|
+
if date_range.include? d
|
68
|
+
d
|
69
|
+
else
|
70
|
+
abstract_date.concretize(@year)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def weekday_before(weekday, date)
|
75
|
+
if date.wday == weekday then
|
76
|
+
return date - WEEK
|
77
|
+
elsif weekday < date.wday
|
78
|
+
return date - (date.wday - weekday)
|
79
|
+
else
|
80
|
+
return date - (date.wday + WEEK - weekday)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def weekday_after(weekday, date)
|
85
|
+
if date.wday == weekday then
|
86
|
+
return date + WEEK
|
87
|
+
elsif weekday > date.wday
|
88
|
+
return date + (weekday - date.wday)
|
89
|
+
else
|
90
|
+
return date + (WEEK - date.wday + weekday)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def octave_of(date)
|
95
|
+
date + WEEK
|
96
|
+
end
|
97
|
+
|
98
|
+
WEEKDAYS = %w{sunday monday tuesday wednesday thursday friday saturday}
|
99
|
+
WEEKDAYS.each_with_index do |weekday, weekday_i|
|
100
|
+
define_method "#{weekday}_before" do |date|
|
101
|
+
send('weekday_before', weekday_i, date)
|
102
|
+
end
|
103
|
+
|
104
|
+
define_method "#{weekday}_after" do |date|
|
105
|
+
send('weekday_after', weekday_i, date)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# first_advent_sunday -> advent_sunday(1)
|
110
|
+
%w{first second third fourth}.each_with_index do |word,i|
|
111
|
+
define_method "#{word}_advent_sunday" do |year=nil|
|
112
|
+
send("advent_sunday", i + 1, year)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def advent_sunday(num, year=nil)
|
117
|
+
advent_sundays_total = 4
|
118
|
+
unless (1..advent_sundays_total).include? num
|
119
|
+
raise ArgumentError.new "Invalid Advent Sunday #{num}"
|
120
|
+
end
|
121
|
+
|
122
|
+
year ||= @year
|
123
|
+
return sunday_before(nativity(year)) - ((advent_sundays_total - num) * WEEK)
|
124
|
+
end
|
125
|
+
|
126
|
+
def nativity(year=nil)
|
127
|
+
year ||= @year
|
128
|
+
return Date.new(year, 12, 25)
|
129
|
+
end
|
130
|
+
|
131
|
+
def holy_family(year=nil)
|
132
|
+
sunday_after(nativity(year))
|
133
|
+
end
|
134
|
+
|
135
|
+
def mother_of_god(year=nil)
|
136
|
+
octave_of(nativity(year))
|
137
|
+
end
|
138
|
+
|
139
|
+
def epiphany(year=nil)
|
140
|
+
year ||= @year
|
141
|
+
return Date.new(year+1, 1, 6)
|
142
|
+
end
|
143
|
+
|
144
|
+
def baptism_of_lord(year=nil)
|
145
|
+
year ||= @year
|
146
|
+
return sunday_after epiphany(year)
|
147
|
+
end
|
148
|
+
|
149
|
+
def ash_wednesday(year=nil)
|
150
|
+
year ||= @year
|
151
|
+
return easter_sunday(year) - (6 * WEEK + 4)
|
152
|
+
end
|
153
|
+
|
154
|
+
def easter_sunday(year=nil)
|
155
|
+
year ||= @year
|
156
|
+
year += 1
|
157
|
+
|
158
|
+
# algorithm below taken from the 'easter' gem:
|
159
|
+
# https://github.com/jrobertson/easter
|
160
|
+
|
161
|
+
golden_number = (year % 19) + 1
|
162
|
+
if year <= 1752 then
|
163
|
+
# Julian calendar
|
164
|
+
dominical_number = (year + (year / 4) + 5) % 7
|
165
|
+
paschal_full_moon = (3 - (11 * golden_number) - 7) % 30
|
166
|
+
else
|
167
|
+
# Gregorian calendar
|
168
|
+
dominical_number = (year + (year / 4) - (year / 100) + (year / 400)) % 7
|
169
|
+
solar_correction = (year - 1600) / 100 - (year - 1600) / 400
|
170
|
+
lunar_correction = (((year - 1400) / 100) * 8) / 25
|
171
|
+
paschal_full_moon = (3 - 11 * golden_number + solar_correction - lunar_correction) % 30
|
172
|
+
end
|
173
|
+
dominical_number += 7 until dominical_number > 0
|
174
|
+
paschal_full_moon += 30 until paschal_full_moon > 0
|
175
|
+
paschal_full_moon -= 1 if paschal_full_moon == 29 or (paschal_full_moon == 28 and golden_number > 11)
|
176
|
+
difference = (4 - paschal_full_moon - dominical_number) % 7
|
177
|
+
difference += 7 if difference < 0
|
178
|
+
day_easter = paschal_full_moon + difference + 1
|
179
|
+
if day_easter < 11 then
|
180
|
+
# Easter occurs in March.
|
181
|
+
return Date.new(y=year, m=3, d=day_easter + 21)
|
182
|
+
else
|
183
|
+
# Easter occurs in April.
|
184
|
+
return Date.new(y=year, m=4, d=day_easter - 10)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def good_friday(year=nil)
|
189
|
+
return friday_before(easter_sunday(year))
|
190
|
+
end
|
191
|
+
|
192
|
+
def holy_saturday(year=nil)
|
193
|
+
return saturday_before(easter_sunday(year))
|
194
|
+
end
|
195
|
+
|
196
|
+
def pentecost(year=nil)
|
197
|
+
year ||= @year
|
198
|
+
return easter_sunday(year) + 7 * WEEK
|
199
|
+
end
|
200
|
+
|
201
|
+
def holy_trinity(year=nil)
|
202
|
+
sunday_after(pentecost(year))
|
203
|
+
end
|
204
|
+
|
205
|
+
def body_blood(year=nil)
|
206
|
+
thursday_after(holy_trinity(year))
|
207
|
+
end
|
208
|
+
|
209
|
+
def sacred_heart(year=nil)
|
210
|
+
friday_after(sunday_after(body_blood(year)))
|
211
|
+
end
|
212
|
+
|
213
|
+
def christ_king(year=nil)
|
214
|
+
year ||= @year
|
215
|
+
sunday_before(first_advent_sunday(year + 1))
|
216
|
+
end
|
217
|
+
|
218
|
+
# which liturgical season is it?
|
219
|
+
def season(date)
|
220
|
+
range_check date
|
221
|
+
|
222
|
+
if first_advent_sunday <= date and
|
223
|
+
nativity > date then
|
224
|
+
Seasons::ADVENT
|
225
|
+
|
226
|
+
elsif nativity <= date and
|
227
|
+
baptism_of_lord >= date then
|
228
|
+
Seasons::CHRISTMAS
|
229
|
+
|
230
|
+
elsif ash_wednesday <= date and
|
231
|
+
easter_sunday > date then
|
232
|
+
Seasons::LENT
|
233
|
+
|
234
|
+
elsif easter_sunday <= date and
|
235
|
+
pentecost >= date then
|
236
|
+
Seasons::EASTER
|
237
|
+
|
238
|
+
else
|
239
|
+
Seasons::ORDINARY
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def season_beginning(s)
|
244
|
+
case s
|
245
|
+
when Seasons::ADVENT
|
246
|
+
first_advent_sunday
|
247
|
+
when Seasons::CHRISTMAS
|
248
|
+
nativity
|
249
|
+
when Seasons::LENT
|
250
|
+
ash_wednesday
|
251
|
+
when Seasons::EASTER
|
252
|
+
easter_sunday
|
253
|
+
else # ordinary time
|
254
|
+
monday_after(baptism_of_lord)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def season_week(seasonn, date)
|
259
|
+
week1_beginning = season_beginning = season_beginning(seasonn)
|
260
|
+
unless season_beginning.sunday?
|
261
|
+
week1_beginning = sunday_after(season_beginning)
|
262
|
+
end
|
263
|
+
|
264
|
+
week = date_difference(date, week1_beginning) / Temporale::WEEK + 1
|
265
|
+
|
266
|
+
if seasonn == Seasons::ORDINARY
|
267
|
+
# ordinary time does not begin with Sunday, but the first week
|
268
|
+
# is week 1, not 0
|
269
|
+
week += 1
|
270
|
+
|
271
|
+
if date > pentecost
|
272
|
+
# gap made by Lent and Easter time
|
273
|
+
week -= 12
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
return week
|
278
|
+
end
|
279
|
+
|
280
|
+
# returns a Celebration
|
281
|
+
# scheduled for the given day
|
282
|
+
#
|
283
|
+
# expected arguments: Date or two Integers (month, day)
|
284
|
+
def get(*args)
|
285
|
+
if args.size == 1 && args[0].is_a?(Date)
|
286
|
+
date = args[0]
|
287
|
+
else
|
288
|
+
month, day = args
|
289
|
+
date = Date.new @year, month, day
|
290
|
+
unless date_range.include? date
|
291
|
+
date = Date.new @year + 1, month, day
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
return solemnity(date) || sunday(date) || ferial(date)
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
# the celebration determination split in methods:
|
301
|
+
|
302
|
+
def solemnity(date)
|
303
|
+
if @solemnities.has_key?(date)
|
304
|
+
return @solemnities[date]
|
305
|
+
end
|
306
|
+
|
307
|
+
seas = season(date)
|
308
|
+
case seas
|
309
|
+
when Seasons::EASTER
|
310
|
+
if date <= sunday_after(easter_sunday)
|
311
|
+
return Celebration.new '', Ranks::PRIMARY, SEASON_COLOUR[seas]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
return nil
|
316
|
+
end
|
317
|
+
|
318
|
+
def sunday(date)
|
319
|
+
return nil unless date.sunday?
|
320
|
+
|
321
|
+
seas = season date
|
322
|
+
rank = Ranks::SUNDAY_UNPRIVILEGED
|
323
|
+
if [Seasons::ADVENT, Seasons::LENT, Seasons::EASTER].include?(seas)
|
324
|
+
rank = Ranks::PRIMARY
|
325
|
+
end
|
326
|
+
|
327
|
+
return Celebration.new '', rank, SEASON_COLOUR[seas]
|
328
|
+
end
|
329
|
+
|
330
|
+
def ferial(date)
|
331
|
+
seas = season date
|
332
|
+
rank = Ranks::FERIAL
|
333
|
+
case seas
|
334
|
+
when Seasons::ADVENT
|
335
|
+
if date >= Date.new(@year, 12, 17)
|
336
|
+
rank = Ranks::FERIAL_PRIVILEGED
|
337
|
+
end
|
338
|
+
when Seasons::CHRISTMAS
|
339
|
+
if date < mother_of_god
|
340
|
+
rank = Ranks::FERIAL_PRIVILEGED
|
341
|
+
end
|
342
|
+
when Seasons::LENT
|
343
|
+
rank = Ranks::FERIAL_PRIVILEGED
|
344
|
+
end
|
345
|
+
|
346
|
+
return Celebration.new '', rank, SEASON_COLOUR[seas]
|
347
|
+
end
|
348
|
+
|
349
|
+
# helper: difference between two Dates in days
|
350
|
+
def date_difference(d1, d2)
|
351
|
+
return (d1 - d2).numerator
|
352
|
+
end
|
353
|
+
|
354
|
+
# prepare dates of temporale solemnities and their octaves
|
355
|
+
def prepare_solemnities
|
356
|
+
@solemnities = {}
|
357
|
+
|
358
|
+
{
|
359
|
+
nativity: ['The Nativity of the Lord', nil, nil],
|
360
|
+
holy_family: ['The Holy Family of Jesus, Mary and Joseph', Ranks::PRIMARY, nil],
|
361
|
+
epiphany: ['The Epiphany of the Lord', nil, nil],
|
362
|
+
baptism_of_lord: ['The Baptism of the Lord', Ranks::FEAST_LORD_GENERAL, nil],
|
363
|
+
good_friday: ['Friday of the Passion of the Lord', Ranks::TRIDUUM, Colours::RED],
|
364
|
+
holy_saturday: ['Holy Saturday', Ranks::TRIDUUM, nil],
|
365
|
+
easter_sunday: ['Easter Sunday of the Resurrection of the Lord', Ranks::TRIDUUM, nil],
|
366
|
+
pentecost: ['Pentecost Sunday', nil, Colours::RED],
|
367
|
+
holy_trinity: ['The Most Holy Trinity', Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
368
|
+
body_blood: ['The Most Holy Body and Blood of Christ', Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
369
|
+
sacred_heart: ['The Most Sacred Heart of Jesus', Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
370
|
+
christ_king: ['Our Lord Jesus Christ, King of the Universe', Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
371
|
+
}.each_pair do |method_name, data|
|
372
|
+
date = send(method_name)
|
373
|
+
title, rank, colour = data
|
374
|
+
@solemnities[date] = Celebration.new(
|
375
|
+
title,
|
376
|
+
rank || Ranks::PRIMARY,
|
377
|
+
colour || SEASON_COLOUR[season(date)]
|
378
|
+
)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
end
|