calendarium-romanum 0.2.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/calendariumrom +4 -1
- data/config/locales/cs.yml +54 -2
- data/config/locales/en.yml +64 -14
- data/config/locales/es.yml +90 -0
- data/config/locales/fr.yml +90 -0
- data/config/locales/it.yml +54 -4
- data/config/locales/la.yml +52 -2
- data/data/README.md +105 -5
- data/data/czech-brno-cs.txt +11 -5
- data/data/czech-budejovice-cs.txt +11 -5
- data/data/czech-cechy-cs.txt +11 -5
- data/data/czech-cs.txt +243 -234
- data/data/czech-hradec-cs.txt +10 -4
- data/data/czech-litomerice-cs.txt +12 -6
- data/data/czech-morava-cs.txt +11 -5
- data/data/czech-olomouc-cs.txt +9 -3
- data/data/czech-ostrava-cs.txt +10 -4
- data/data/czech-plzen-cs.txt +10 -4
- data/data/czech-praha-cs.txt +10 -3
- data/data/universal-en.txt +218 -212
- data/data/universal-es.txt +243 -0
- data/data/universal-fr.txt +243 -0
- data/data/universal-it.txt +218 -212
- data/data/universal-la.txt +218 -211
- data/lib/calendarium-romanum.rb +30 -18
- data/lib/calendarium-romanum/abstract_date.rb +12 -0
- data/lib/calendarium-romanum/calendar.rb +210 -48
- data/lib/calendarium-romanum/cli.rb +101 -52
- data/lib/calendarium-romanum/cr.rb +16 -0
- data/lib/calendarium-romanum/data.rb +46 -18
- data/lib/calendarium-romanum/day.rb +200 -21
- data/lib/calendarium-romanum/enum.rb +24 -5
- data/lib/calendarium-romanum/enums.rb +123 -37
- data/lib/calendarium-romanum/errors.rb +4 -0
- data/lib/calendarium-romanum/ordinalizer.rb +61 -0
- data/lib/calendarium-romanum/perpetual_calendar.rb +97 -0
- data/lib/calendarium-romanum/rank.rb +43 -6
- data/lib/calendarium-romanum/sanctorale.rb +142 -22
- data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
- data/lib/calendarium-romanum/sanctorale_loader.rb +176 -0
- data/lib/calendarium-romanum/temporale.rb +296 -251
- data/lib/calendarium-romanum/temporale/celebration_factory.rb +106 -0
- data/lib/calendarium-romanum/temporale/dates.rb +232 -0
- data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +37 -0
- data/lib/calendarium-romanum/transfers.rb +43 -6
- data/lib/calendarium-romanum/util.rb +36 -3
- data/lib/calendarium-romanum/version.rb +5 -1
- data/spec/abstract_date_spec.rb +11 -3
- data/spec/calendar_spec.rb +645 -188
- data/spec/celebration_factory_spec.rb +40 -0
- data/spec/celebration_spec.rb +67 -0
- data/spec/cli_spec.rb +154 -11
- data/spec/colour_spec.rb +22 -0
- data/spec/data_spec.rb +26 -3
- data/spec/date_parser_spec.rb +68 -0
- data/spec/date_spec.rb +8 -8
- data/spec/dates_spec.rb +73 -0
- data/spec/day_spec.rb +151 -0
- data/spec/i18n_spec.rb +11 -2
- data/spec/ordinalizer_spec.rb +44 -0
- data/spec/perpetual_calendar_spec.rb +125 -0
- data/spec/rank_spec.rb +42 -7
- data/spec/readme_spec.rb +18 -10
- data/spec/sanctorale_factory_spec.rb +113 -9
- data/spec/sanctorale_loader_spec.rb +229 -0
- data/spec/sanctorale_spec.rb +176 -62
- data/spec/season_spec.rb +22 -0
- data/spec/spec_helper.rb +27 -1
- data/spec/temporale_spec.rb +473 -154
- data/spec/year_spec.rb +25 -0
- metadata +42 -7
- data/lib/calendarium-romanum/sanctoraleloader.rb +0 -104
- data/spec/sanctoraleloader_spec.rb +0 -171
@@ -1,15 +1,50 @@
|
|
1
1
|
module CalendariumRomanum
|
2
|
-
#
|
2
|
+
# Utility loading {Sanctorale} from several sources
|
3
|
+
# and building a single {Sanctorale} by layering them
|
4
|
+
# over each other.
|
3
5
|
class SanctoraleFactory
|
4
6
|
class << self
|
5
|
-
#
|
7
|
+
# Takes several {Sanctorale} instances, returns a new one,
|
8
|
+
# resulting by merging them all together
|
9
|
+
# (using {Sanctorale#update})
|
10
|
+
#
|
11
|
+
# @return [Sanctorale]
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# include CalendariumRomanum
|
15
|
+
#
|
16
|
+
# prague_sanctorale = SanctoraleFactory.create_layered(
|
17
|
+
# Data['czech-cs'].load, # Czech Republic
|
18
|
+
# Data['czech-cechy-cs'].load, # Province of Bohemia
|
19
|
+
# Data['czech-praha-cs'].load, # Archdiocese of Prague
|
20
|
+
# )
|
6
21
|
def create_layered(*instances)
|
7
22
|
r = Sanctorale.new
|
8
23
|
instances.each {|i| r.update i }
|
24
|
+
|
25
|
+
metadata = instances
|
26
|
+
.collect(&:metadata)
|
27
|
+
.select {|i| i.is_a? Hash }
|
28
|
+
r.metadata = metadata.inject((metadata.first || {}).dup) {|merged,i| merged.update i }
|
29
|
+
r.metadata.delete 'extends'
|
30
|
+
r.metadata['components'] = instances.collect(&:metadata)
|
31
|
+
|
9
32
|
r
|
10
33
|
end
|
11
34
|
|
12
|
-
#
|
35
|
+
# Takes several filesystem paths, loads a {Sanctorale}
|
36
|
+
# from each of them (using {SanctoraleLoader})
|
37
|
+
# and then merges them (using {.create_layered})
|
38
|
+
#
|
39
|
+
# @return [Sanctorale]
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# include CalendariumRomanum
|
43
|
+
#
|
44
|
+
# my_sanctorale = SanctoraleFactory.load_layered_from_files(
|
45
|
+
# 'my_data/general_calendar.txt',
|
46
|
+
# 'my_data/particular_calendar.txt'
|
47
|
+
# )
|
13
48
|
def load_layered_from_files(*paths)
|
14
49
|
loader = SanctoraleLoader.new
|
15
50
|
instances = paths.collect do |p|
|
@@ -17,6 +52,42 @@ module CalendariumRomanum
|
|
17
52
|
end
|
18
53
|
create_layered(*instances)
|
19
54
|
end
|
55
|
+
|
56
|
+
# Takes a single filesystem path. If the file's YAML front
|
57
|
+
# matter references any parent data files using the
|
58
|
+
# 'extends' key, it loads all the parents and assembles
|
59
|
+
# the resulting {Sanctorale}.
|
60
|
+
# If the data file doesn't reference any parents,
|
61
|
+
# result is the same as {SanctoraleLoader#load_from_file}.
|
62
|
+
#
|
63
|
+
# @return [Sanctorale]
|
64
|
+
# @since 0.7.0
|
65
|
+
def load_with_parents(path)
|
66
|
+
loader = SanctoraleLoader.new
|
67
|
+
|
68
|
+
hierarchy = load_parent_hierarchy(path, loader)
|
69
|
+
return hierarchy.first if hierarchy.size == 1
|
70
|
+
|
71
|
+
create_layered *hierarchy
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def load_parent_hierarchy(path, loader)
|
77
|
+
main = loader.load_from_file path
|
78
|
+
return [main] unless main.metadata.has_key? 'extends'
|
79
|
+
|
80
|
+
to_merge = [main]
|
81
|
+
parents = main.metadata['extends']
|
82
|
+
parents = [parents] unless parents.is_a? Array
|
83
|
+
parents.reverse.each do |parent_path|
|
84
|
+
expanded_path = File.expand_path parent_path, File.dirname(path)
|
85
|
+
subtree = load_parent_hierarchy(expanded_path, loader)
|
86
|
+
to_merge = subtree + to_merge
|
87
|
+
end
|
88
|
+
|
89
|
+
to_merge
|
90
|
+
end
|
20
91
|
end
|
21
92
|
end
|
22
93
|
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module CalendariumRomanum
|
4
|
+
|
5
|
+
# Understands a custom plaintext calendar format
|
6
|
+
# and knows how to transform it to {Celebration}s
|
7
|
+
# and fill them in a {Sanctorale}.
|
8
|
+
#
|
9
|
+
# For specification of the data format see {file:data/README.md},
|
10
|
+
# for a complete example see the file describing General Roman Calendar:
|
11
|
+
# {file:data/universal-en.txt}
|
12
|
+
class SanctoraleLoader
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
RANK_CODES = {
|
16
|
+
nil => Ranks::MEMORIAL_OPTIONAL, # default
|
17
|
+
'm' => Ranks::MEMORIAL_GENERAL,
|
18
|
+
'f' => Ranks::FEAST_GENERAL,
|
19
|
+
's' => Ranks::SOLEMNITY_GENERAL
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
# @api private
|
23
|
+
COLOUR_CODES = {
|
24
|
+
nil => Colours::WHITE, # default
|
25
|
+
'w' => Colours::WHITE,
|
26
|
+
'v' => Colours::VIOLET,
|
27
|
+
'g' => Colours::GREEN,
|
28
|
+
'r' => Colours::RED
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
# Load from an object which understands +#each_line+
|
32
|
+
#
|
33
|
+
# @param src [String, File, #each_line]
|
34
|
+
# source of the loaded data
|
35
|
+
# @param dest [Sanctorale, nil]
|
36
|
+
# objects to populate. If not provided, a new {Sanctorale}
|
37
|
+
# instance will be created
|
38
|
+
# @return [Sanctorale]
|
39
|
+
# @raise [InvalidDataError]
|
40
|
+
def load(src, dest = nil)
|
41
|
+
dest ||= Sanctorale.new
|
42
|
+
|
43
|
+
in_front_matter = false
|
44
|
+
front_matter = ''
|
45
|
+
month_section = nil
|
46
|
+
src.each_line.with_index(1) do |l, line_num|
|
47
|
+
# skip YAML front matter
|
48
|
+
if line_num == 1 && l.start_with?('---')
|
49
|
+
in_front_matter = true
|
50
|
+
front_matter += l
|
51
|
+
next
|
52
|
+
elsif in_front_matter
|
53
|
+
if l.start_with?('---')
|
54
|
+
in_front_matter = false
|
55
|
+
dest.metadata = YAML.load(front_matter).freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
front_matter += l
|
59
|
+
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
# strip whitespace and comments
|
64
|
+
l.sub!(/#.*/, '')
|
65
|
+
l.strip!
|
66
|
+
next if l.empty?
|
67
|
+
|
68
|
+
# month section heading
|
69
|
+
n = l.match(/^=\s*(\d+)\s*$/)
|
70
|
+
unless n.nil?
|
71
|
+
month_section = n[1].to_i
|
72
|
+
unless month_section >= 1 && month_section <= 12
|
73
|
+
raise error("Invalid month #{month_section}", line_num)
|
74
|
+
end
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
begin
|
79
|
+
celebration = load_line l, month_section
|
80
|
+
rescue RangeError, RuntimeError => err
|
81
|
+
raise error(err.message, line_num)
|
82
|
+
end
|
83
|
+
|
84
|
+
dest.add(
|
85
|
+
celebration.date.month,
|
86
|
+
celebration.date.day,
|
87
|
+
celebration
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
dest
|
92
|
+
end
|
93
|
+
|
94
|
+
alias load_from_string load
|
95
|
+
|
96
|
+
# Load from a filesystem path
|
97
|
+
#
|
98
|
+
# @param filename [String]
|
99
|
+
# @param dest [Sanctorale, nil]
|
100
|
+
# @param encoding [String]
|
101
|
+
# @return (see #load)
|
102
|
+
# @raise (see #load)
|
103
|
+
def load_from_file(filename, dest = nil, encoding = 'utf-8')
|
104
|
+
load File.open(filename, 'r', encoding: encoding), dest
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def line_regexp
|
110
|
+
@line_regexp ||=
|
111
|
+
begin
|
112
|
+
rank_letters = RANK_CODES.keys.compact.join('')
|
113
|
+
colour_letters = COLOUR_CODES.keys.compact.join('')
|
114
|
+
|
115
|
+
Regexp.new(
|
116
|
+
'^((?<month>\d+)\/)?(?<day>\d+)' + # date
|
117
|
+
'(\s+(?<rank_char>[' + rank_letters + '])?(?<rank_num>\d\.\d{1,2})?)?' + # rank (optional)
|
118
|
+
'(\s+(?<colour>[' + colour_letters + ']))?' + # colour (optional)
|
119
|
+
'(\s+(?<symbol>[\w]{2,}))?' + # symbol (optional)
|
120
|
+
'\s*:(?<title>.*)$', # title
|
121
|
+
Regexp::IGNORECASE
|
122
|
+
)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# parses a line containing celebration record,
|
127
|
+
# returns a single Celebration
|
128
|
+
def load_line(line, month_section = nil)
|
129
|
+
# celebration record
|
130
|
+
m = line.match(line_regexp)
|
131
|
+
if m.nil?
|
132
|
+
raise RuntimeError.new("Syntax error, line skipped '#{line}'")
|
133
|
+
end
|
134
|
+
|
135
|
+
month = (m[:month] || month_section).to_i
|
136
|
+
day = m[:day].to_i
|
137
|
+
rank_char = m[:rank_char]
|
138
|
+
rank_num = m[:rank_num]
|
139
|
+
colour = m[:colour]
|
140
|
+
symbol_str = m[:symbol]
|
141
|
+
title = m[:title]
|
142
|
+
|
143
|
+
rank = RANK_CODES[rank_char && rank_char.downcase]
|
144
|
+
|
145
|
+
if rank_num
|
146
|
+
rank_num = rank_num.to_f
|
147
|
+
rank_by_num = Ranks[rank_num]
|
148
|
+
|
149
|
+
if rank_by_num.nil?
|
150
|
+
raise RuntimeError.new("Invalid celebration rank code #{rank_num}")
|
151
|
+
elsif rank_char && (rank.priority.to_i != rank_by_num.priority.to_i)
|
152
|
+
raise RuntimeError.new("Invalid combination of rank letter #{rank_char.inspect} and number #{rank_num}.")
|
153
|
+
end
|
154
|
+
|
155
|
+
rank = rank_by_num
|
156
|
+
end
|
157
|
+
|
158
|
+
symbol = nil
|
159
|
+
if symbol_str
|
160
|
+
symbol = symbol_str.to_sym
|
161
|
+
end
|
162
|
+
|
163
|
+
Celebration.new(
|
164
|
+
title.strip,
|
165
|
+
rank,
|
166
|
+
COLOUR_CODES[colour && colour.downcase],
|
167
|
+
symbol,
|
168
|
+
AbstractDate.new(month, day)
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
def error(message, line_number)
|
173
|
+
InvalidDataError.new("L#{line_number}: #{message}")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -2,27 +2,44 @@ require 'date'
|
|
2
2
|
|
3
3
|
module CalendariumRomanum
|
4
4
|
|
5
|
-
#
|
5
|
+
# One of the two main {Calendar} components.
|
6
|
+
# Handles seasons and celebrations of the temporale cycle
|
7
|
+
# for a given liturgical year.
|
6
8
|
class Temporale
|
7
9
|
|
10
|
+
# How many days in a week
|
8
11
|
WEEK = 7
|
9
12
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
|
13
|
+
# Which solemnities can be transferred to Sunday
|
14
|
+
SUNDAY_TRANSFERABLE_SOLEMNITIES =
|
15
|
+
%i(epiphany ascension corpus_christi).freeze
|
16
|
+
|
17
|
+
# @param year [Fixnum]
|
18
|
+
# the civil year when the liturgical year _begins_
|
19
|
+
# @param extensions [Array<#each_celebration>]
|
20
|
+
# extensions implementing custom temporale celebrations
|
21
|
+
# @param transfer_to_sunday [Array<Symbol>]
|
22
|
+
# which solemnities should be transferred to a nearby
|
23
|
+
# Sunday - see {SUNDAY_TRANSFERABLE_SOLEMNITIES}
|
24
|
+
# for possible values
|
25
|
+
def initialize(year, extensions: [], transfer_to_sunday: [])
|
20
26
|
@year = year
|
21
|
-
|
27
|
+
|
28
|
+
@extensions = extensions
|
29
|
+
@transfer_to_sunday = transfer_to_sunday.sort
|
30
|
+
validate_sunday_transfer!
|
31
|
+
|
32
|
+
prepare_solemnities
|
22
33
|
end
|
23
34
|
|
35
|
+
# @return [Fixnum]
|
36
|
+
attr_reader :year
|
37
|
+
|
24
38
|
class << self
|
25
39
|
# Determines liturgical year for the given date
|
40
|
+
#
|
41
|
+
# @param date [Date]
|
42
|
+
# @return [Fixnum]
|
26
43
|
def liturgical_year(date)
|
27
44
|
year = date.year
|
28
45
|
temporale = Temporale.new year
|
@@ -31,225 +48,190 @@ module CalendariumRomanum
|
|
31
48
|
return year - 1
|
32
49
|
end
|
33
50
|
|
34
|
-
|
51
|
+
year
|
35
52
|
end
|
36
53
|
|
37
|
-
#
|
54
|
+
# Creates an instance for the liturgical year including given
|
38
55
|
# date
|
56
|
+
#
|
57
|
+
# @param date [Date]
|
58
|
+
# @return [Temporale]
|
39
59
|
def for_day(date)
|
40
|
-
|
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
|
-
# necessary in order to handle Date correctly
|
59
|
-
date = date.to_date if date.class != Date
|
60
|
-
|
61
|
-
unless date_range.include? date
|
62
|
-
raise RangeError.new "Date out of range #{date}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# converts an AbstractDate to a Date in the given
|
67
|
-
# liturgical year
|
68
|
-
def concretize_abstract_date(abstract_date)
|
69
|
-
d = abstract_date.concretize(@year + 1)
|
70
|
-
if date_range.include? d
|
71
|
-
d
|
72
|
-
else
|
73
|
-
abstract_date.concretize(@year)
|
60
|
+
new(liturgical_year(date))
|
74
61
|
end
|
75
|
-
end
|
76
62
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
return date - (date.wday + WEEK - weekday)
|
63
|
+
# Factory method creating temporale {Celebration}s
|
64
|
+
# with sensible defaults
|
65
|
+
#
|
66
|
+
# See {Celebration#initialize} for argument description.
|
67
|
+
def create_celebration(title, rank, colour, symbol: nil, date: nil)
|
68
|
+
Celebration.new(title, rank, colour, symbol, date, :temporale)
|
84
69
|
end
|
85
|
-
end
|
86
70
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
71
|
+
C = Struct.new(:date_method, :celebration)
|
72
|
+
private_constant :C
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
def celebrations
|
76
|
+
@celebrations ||=
|
77
|
+
begin
|
78
|
+
%i(
|
79
|
+
nativity
|
80
|
+
holy_family
|
81
|
+
mother_of_god
|
82
|
+
epiphany
|
83
|
+
baptism_of_lord
|
84
|
+
ash_wednesday
|
85
|
+
good_friday
|
86
|
+
holy_saturday
|
87
|
+
palm_sunday
|
88
|
+
easter_sunday
|
89
|
+
ascension
|
90
|
+
pentecost
|
91
|
+
holy_trinity
|
92
|
+
corpus_christi
|
93
|
+
mother_of_church
|
94
|
+
sacred_heart
|
95
|
+
christ_king
|
96
|
+
immaculate_heart
|
97
|
+
).collect do |symbol|
|
98
|
+
date_method = symbol
|
99
|
+
C.new(
|
100
|
+
date_method,
|
101
|
+
CelebrationFactory.public_send(symbol)
|
102
|
+
)
|
103
|
+
end
|
104
|
+
# Immaculate Heart of Mary and Mary, Mother of the Church
|
105
|
+
# are actually movable *sanctorale* feasts,
|
106
|
+
# but as it would make little sense
|
107
|
+
# to add support for movable sanctorale feasts because of
|
108
|
+
# two, we cheat a bit and handle them in temporale.
|
109
|
+
end
|
94
110
|
end
|
95
111
|
end
|
96
112
|
|
97
|
-
|
98
|
-
|
113
|
+
# Does this instance transfer the specified solemnity to Sunday?
|
114
|
+
#
|
115
|
+
# @param solemnity [Symbol]
|
116
|
+
# @return [Boolean]
|
117
|
+
def transferred_to_sunday?(solemnity)
|
118
|
+
@transfer_to_sunday.include?(solemnity)
|
99
119
|
end
|
100
120
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
define_method "#{weekday}_after" do |date|
|
108
|
-
send('weekday_after', weekday_i, date)
|
109
|
-
end
|
121
|
+
# First day of the liturgical year
|
122
|
+
#
|
123
|
+
# @return [Date]
|
124
|
+
def start_date
|
125
|
+
first_advent_sunday
|
110
126
|
end
|
111
127
|
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
128
|
+
# Last day of the liturgical year
|
129
|
+
#
|
130
|
+
# @return [Date]
|
131
|
+
def end_date
|
132
|
+
Dates.first_advent_sunday(year + 1) - 1
|
117
133
|
end
|
118
134
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
year ||= @year
|
126
|
-
return sunday_before(nativity(year)) - ((advent_sundays_total - num) * WEEK)
|
135
|
+
# Date range of the liturgical year
|
136
|
+
#
|
137
|
+
# @return [Range<Date>]
|
138
|
+
def date_range
|
139
|
+
start_date .. end_date
|
127
140
|
end
|
128
141
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
142
|
+
# Check that the date belongs to the liturgical year.
|
143
|
+
# If it does not, throw exception.
|
144
|
+
#
|
145
|
+
# @param date [Date]
|
146
|
+
# @return [void]
|
147
|
+
# @raise [RangeError]
|
148
|
+
def range_check(date)
|
149
|
+
# necessary in order to handle Date correctly
|
150
|
+
date = date.to_date if date.class != Date
|
133
151
|
|
134
|
-
|
135
|
-
|
136
|
-
xmas = nativity(year)
|
137
|
-
if xmas.sunday?
|
138
|
-
return Date.new(year, 12, 30)
|
139
|
-
else
|
140
|
-
sunday_after(xmas)
|
152
|
+
unless date_range.include? date
|
153
|
+
raise RangeError.new "Date out of range #{date}"
|
141
154
|
end
|
142
155
|
end
|
143
156
|
|
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
|
-
|
157
|
+
# @!method nativity
|
158
|
+
# @return [Date]
|
159
|
+
# @!method holy_family
|
160
|
+
# @return [Date]
|
161
|
+
# @!method mother_of_god
|
162
|
+
# @return [Date]
|
163
|
+
# @!method epiphany
|
164
|
+
# @return [Date]
|
165
|
+
# @!method baptism_of_lord
|
166
|
+
# @return [Date]
|
167
|
+
# @!method ash_wednesday
|
168
|
+
# @return [Date]
|
169
|
+
# @!method good_friday
|
170
|
+
# @return [Date]
|
171
|
+
# @!method holy_saturday
|
172
|
+
# @return [Date]
|
173
|
+
# @!method palm_sunday
|
174
|
+
# @return [Date]
|
175
|
+
# @!method easter_sunday
|
176
|
+
# @return [Date]
|
177
|
+
# @!method ascension
|
178
|
+
# @return [Date]
|
179
|
+
# @!method pentecost
|
180
|
+
# @return [Date]
|
181
|
+
# @!method holy_trinity
|
182
|
+
# @return [Date]
|
183
|
+
# @!method corpus_christi
|
184
|
+
# @return [Date]
|
185
|
+
# @!method mother_of_church
|
186
|
+
# @return [Date]
|
187
|
+
# @!method sacred_heart
|
188
|
+
# @return [Date]
|
189
|
+
# @!method christ_king
|
190
|
+
# @return [Date]
|
191
|
+
# @!method immaculate_heart
|
192
|
+
# @return [Date]
|
193
|
+
# @!method first_advent_sunday
|
194
|
+
# @return [Date]
|
195
|
+
(celebrations.collect(&:date_method) + [:first_advent_sunday])
|
196
|
+
.each do |feast|
|
197
|
+
if SUNDAY_TRANSFERABLE_SOLEMNITIES.include? feast
|
198
|
+
define_method feast do
|
199
|
+
Dates.public_send feast, year, sunday: transferred_to_sunday?(feast)
|
200
|
+
end
|
201
|
+
elsif feast == :baptism_of_lord
|
202
|
+
define_method feast do
|
203
|
+
Dates.public_send feast, year, epiphany_on_sunday: transferred_to_sunday?(:epiphany)
|
204
|
+
end
|
191
205
|
else
|
192
|
-
|
193
|
-
|
206
|
+
define_method feast do
|
207
|
+
Dates.public_send feast, year
|
208
|
+
end
|
194
209
|
end
|
195
210
|
end
|
196
211
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
204
|
-
|
205
|
-
def holy_saturday(year=nil)
|
206
|
-
return easter_sunday(year) - 1
|
207
|
-
end
|
208
|
-
|
209
|
-
def ascension(year=nil)
|
210
|
-
return pentecost(year) - 10
|
211
|
-
end
|
212
|
-
|
213
|
-
def pentecost(year=nil)
|
214
|
-
year ||= @year
|
215
|
-
return easter_sunday(year) + 7 * WEEK
|
216
|
-
end
|
217
|
-
|
218
|
-
def holy_trinity(year=nil)
|
219
|
-
sunday_after(pentecost(year))
|
220
|
-
end
|
221
|
-
|
222
|
-
def body_blood(year=nil)
|
223
|
-
thursday_after(holy_trinity(year))
|
224
|
-
end
|
225
|
-
|
226
|
-
def sacred_heart(year=nil)
|
227
|
-
friday_after(sunday_after(body_blood(year)))
|
228
|
-
end
|
229
|
-
|
230
|
-
def christ_king(year=nil)
|
231
|
-
year ||= @year
|
232
|
-
sunday_before(first_advent_sunday(year + 1))
|
233
|
-
end
|
234
|
-
|
235
|
-
# which liturgical season is it?
|
212
|
+
# Determine liturgical season for a given date
|
213
|
+
#
|
214
|
+
# @param date [Date]
|
215
|
+
# @return [Season]
|
216
|
+
# @raise [RangeError]
|
217
|
+
# if the given date doesn't belong to the liturgical year
|
236
218
|
def season(date)
|
237
219
|
range_check date
|
238
220
|
|
239
|
-
if first_advent_sunday <= date
|
240
|
-
|
221
|
+
if (first_advent_sunday <= date) &&
|
222
|
+
nativity > date
|
241
223
|
Seasons::ADVENT
|
242
224
|
|
243
|
-
elsif nativity <= date
|
244
|
-
|
225
|
+
elsif (nativity <= date) &&
|
226
|
+
(baptism_of_lord >= date)
|
245
227
|
Seasons::CHRISTMAS
|
246
228
|
|
247
|
-
elsif ash_wednesday <= date
|
248
|
-
|
229
|
+
elsif (ash_wednesday <= date) &&
|
230
|
+
easter_sunday > date
|
249
231
|
Seasons::LENT
|
250
232
|
|
251
|
-
elsif easter_sunday <= date
|
252
|
-
|
233
|
+
elsif (easter_sunday <= date) &&
|
234
|
+
(pentecost >= date)
|
253
235
|
Seasons::EASTER
|
254
236
|
|
255
237
|
else
|
@@ -257,6 +239,10 @@ module CalendariumRomanum
|
|
257
239
|
end
|
258
240
|
end
|
259
241
|
|
242
|
+
# When the specified liturgical season begins
|
243
|
+
#
|
244
|
+
# @param s [Season]
|
245
|
+
# @return [Date]
|
260
246
|
def season_beginning(s)
|
261
247
|
case s
|
262
248
|
when Seasons::ADVENT
|
@@ -267,18 +253,24 @@ module CalendariumRomanum
|
|
267
253
|
ash_wednesday
|
268
254
|
when Seasons::EASTER
|
269
255
|
easter_sunday
|
270
|
-
|
271
|
-
|
256
|
+
when Seasons::ORDINARY # ordinary time
|
257
|
+
baptism_of_lord + 1
|
258
|
+
else
|
259
|
+
raise ArgumentError.new('unsupported season')
|
272
260
|
end
|
273
261
|
end
|
274
262
|
|
263
|
+
# Determine week of a season for a given date
|
264
|
+
#
|
265
|
+
# @param seasonn [Season]
|
266
|
+
# @param date [Date]
|
275
267
|
def season_week(seasonn, date)
|
276
268
|
week1_beginning = season_beginning = season_beginning(seasonn)
|
277
269
|
unless season_beginning.sunday?
|
278
|
-
week1_beginning = sunday_after(season_beginning)
|
270
|
+
week1_beginning = Dates.sunday_after(season_beginning)
|
279
271
|
end
|
280
272
|
|
281
|
-
week = date_difference(date, week1_beginning) /
|
273
|
+
week = date_difference(date, week1_beginning) / WEEK + 1
|
282
274
|
|
283
275
|
if seasonn == Seasons::ORDINARY
|
284
276
|
# ordinary time does not begin with Sunday, but the first week
|
@@ -286,19 +278,32 @@ module CalendariumRomanum
|
|
286
278
|
week += 1
|
287
279
|
|
288
280
|
if date > pentecost
|
289
|
-
weeks_after_date = date_difference(first_advent_sunday(@year + 1), date) /
|
281
|
+
weeks_after_date = date_difference(Dates.first_advent_sunday(@year + 1), date) / WEEK
|
290
282
|
week = 34 - weeks_after_date
|
291
283
|
week += 1 if date.sunday?
|
292
284
|
end
|
293
285
|
end
|
294
286
|
|
295
|
-
|
287
|
+
week
|
288
|
+
end
|
289
|
+
|
290
|
+
# Retrieve temporale celebration for the given day
|
291
|
+
#
|
292
|
+
# @param date [Date]
|
293
|
+
# @return [Celebration]
|
294
|
+
# @since 0.6.0
|
295
|
+
def [](date)
|
296
|
+
@solemnities[date] || @feasts[date] || sunday(date) || @memorials[date] || ferial(date)
|
296
297
|
end
|
297
298
|
|
298
|
-
#
|
299
|
-
# scheduled for the given day
|
299
|
+
# Retrieve temporale celebration for the given day
|
300
300
|
#
|
301
|
-
#
|
301
|
+
# @overload get(date)
|
302
|
+
# @param date [Date]
|
303
|
+
# @overload get(month, day)
|
304
|
+
# @param month [Fixnum]
|
305
|
+
# @param day [Fixnum]
|
306
|
+
# @return (see #[])
|
302
307
|
def get(*args)
|
303
308
|
if args.size == 1 && args[0].is_a?(Date)
|
304
309
|
date = args[0]
|
@@ -310,94 +315,134 @@ module CalendariumRomanum
|
|
310
315
|
end
|
311
316
|
end
|
312
317
|
|
313
|
-
|
318
|
+
self[date]
|
314
319
|
end
|
315
320
|
|
316
|
-
|
321
|
+
# @return [Boolean]
|
322
|
+
# @since 0.6.0
|
323
|
+
def ==(b)
|
324
|
+
self.class == b.class &&
|
325
|
+
year == b.year &&
|
326
|
+
transfer_to_sunday == b.transfer_to_sunday &&
|
327
|
+
Set.new(extensions) == Set.new(b.extensions)
|
328
|
+
end
|
317
329
|
|
318
|
-
|
330
|
+
protected
|
319
331
|
|
320
|
-
|
321
|
-
if @solemnities.has_key?(date)
|
322
|
-
return @solemnities[date]
|
323
|
-
end
|
332
|
+
attr_reader :transfer_to_sunday, :extensions
|
324
333
|
|
325
|
-
|
326
|
-
case seas
|
327
|
-
when Seasons::EASTER
|
328
|
-
if date <= sunday_after(easter_sunday)
|
329
|
-
return Celebration.new '', Ranks::PRIMARY, SEASON_COLOUR[seas]
|
330
|
-
end
|
331
|
-
end
|
334
|
+
private
|
332
335
|
|
333
|
-
|
334
|
-
|
336
|
+
# seasons when Sundays have higher rank
|
337
|
+
SEASONS_SUNDAY_PRIMARY = [Seasons::ADVENT, Seasons::LENT, Seasons::EASTER].freeze
|
335
338
|
|
336
339
|
def sunday(date)
|
337
340
|
return nil unless date.sunday?
|
338
341
|
|
339
342
|
seas = season date
|
340
343
|
rank = Ranks::SUNDAY_UNPRIVILEGED
|
341
|
-
if
|
344
|
+
if SEASONS_SUNDAY_PRIMARY.include?(seas)
|
342
345
|
rank = Ranks::PRIMARY
|
343
346
|
end
|
344
347
|
|
345
|
-
|
348
|
+
week = Ordinalizer.ordinal season_week(seas, date)
|
349
|
+
title = I18n.t "temporale.#{seas.to_sym}.sunday", week: week
|
350
|
+
|
351
|
+
self.class.create_celebration title, rank, seas.colour
|
346
352
|
end
|
347
353
|
|
348
354
|
def ferial(date)
|
349
355
|
seas = season date
|
356
|
+
week = season_week(seas, date)
|
350
357
|
rank = Ranks::FERIAL
|
358
|
+
title = nil
|
351
359
|
case seas
|
352
360
|
when Seasons::ADVENT
|
353
361
|
if date >= Date.new(@year, 12, 17)
|
354
362
|
rank = Ranks::FERIAL_PRIVILEGED
|
363
|
+
nth = Ordinalizer.ordinal(date.day)
|
364
|
+
title = I18n.t 'temporale.advent.before_christmas', day: nth
|
355
365
|
end
|
356
366
|
when Seasons::CHRISTMAS
|
357
367
|
if date < mother_of_god
|
358
368
|
rank = Ranks::FERIAL_PRIVILEGED
|
369
|
+
|
370
|
+
nth = Ordinalizer.ordinal(date.day - nativity.day + 1) # 1-based counting
|
371
|
+
title = I18n.t 'temporale.christmas.nativity_octave.ferial', day: nth
|
372
|
+
elsif date > epiphany
|
373
|
+
title = I18n.t 'temporale.christmas.after_epiphany.ferial', weekday: I18n.t("weekday.#{date.wday}")
|
359
374
|
end
|
360
375
|
when Seasons::LENT
|
361
|
-
|
376
|
+
if week == 0
|
377
|
+
title = I18n.t 'temporale.lent.after_ashes.ferial', weekday: I18n.t("weekday.#{date.wday}")
|
378
|
+
elsif date > palm_sunday
|
379
|
+
rank = Ranks::PRIMARY
|
380
|
+
title = I18n.t 'temporale.lent.holy_week.ferial', weekday: I18n.t("weekday.#{date.wday}")
|
381
|
+
end
|
382
|
+
rank = Ranks::FERIAL_PRIVILEGED unless rank > Ranks::FERIAL_PRIVILEGED
|
383
|
+
when Seasons::EASTER
|
384
|
+
if week == 1
|
385
|
+
rank = Ranks::PRIMARY
|
386
|
+
title = I18n.t 'temporale.easter.octave.ferial', weekday: I18n.t("weekday.#{date.wday}")
|
387
|
+
end
|
362
388
|
end
|
363
389
|
|
364
|
-
|
390
|
+
week_ord = Ordinalizer.ordinal week
|
391
|
+
title ||= I18n.t "temporale.#{seas.to_sym}.ferial", week: week_ord, weekday: I18n.t("weekday.#{date.wday}")
|
392
|
+
|
393
|
+
self.class.create_celebration title, rank, seas.colour
|
365
394
|
end
|
366
395
|
|
367
396
|
# helper: difference between two Dates in days
|
368
397
|
def date_difference(d1, d2)
|
369
|
-
|
398
|
+
(d1 - d2).numerator
|
370
399
|
end
|
371
400
|
|
372
|
-
# prepare dates of temporale solemnities
|
401
|
+
# prepare dates of temporale solemnities
|
373
402
|
def prepare_solemnities
|
374
403
|
@solemnities = {}
|
404
|
+
@feasts = {}
|
405
|
+
@memorials = {}
|
406
|
+
|
407
|
+
self.class.celebrations.each do |c|
|
408
|
+
prepare_celebration_date c.date_method, c.celebration
|
409
|
+
end
|
410
|
+
|
411
|
+
@extensions.each do |extension|
|
412
|
+
extension.each_celebration do |date_method, celebration|
|
413
|
+
date_proc = date_method
|
414
|
+
if date_method.is_a? Symbol
|
415
|
+
date_proc = extension.method(date_method)
|
416
|
+
end
|
417
|
+
|
418
|
+
prepare_celebration_date date_proc, celebration
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def prepare_celebration_date(date_method, celebration)
|
424
|
+
date =
|
425
|
+
if date_method.respond_to? :call
|
426
|
+
date_method.call(year)
|
427
|
+
else
|
428
|
+
public_send(date_method)
|
429
|
+
end
|
430
|
+
|
431
|
+
add_to =
|
432
|
+
if celebration.feast?
|
433
|
+
@feasts
|
434
|
+
elsif celebration.memorial?
|
435
|
+
@memorials
|
436
|
+
else
|
437
|
+
@solemnities
|
438
|
+
end
|
439
|
+
add_to[date] = celebration
|
440
|
+
end
|
375
441
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
epiphany: [Ranks::PRIMARY, nil],
|
381
|
-
baptism_of_lord: [Ranks::FEAST_LORD_GENERAL, nil],
|
382
|
-
ash_wednesday: [Ranks::PRIMARY, nil],
|
383
|
-
good_friday: [Ranks::TRIDUUM, Colours::RED],
|
384
|
-
holy_saturday: [Ranks::TRIDUUM, nil],
|
385
|
-
palm_sunday: [Ranks::PRIMARY, Colours::RED],
|
386
|
-
easter_sunday: [Ranks::TRIDUUM, nil],
|
387
|
-
ascension: [Ranks::PRIMARY, Colours::WHITE],
|
388
|
-
pentecost: [Ranks::PRIMARY, Colours::RED],
|
389
|
-
holy_trinity: [Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
390
|
-
body_blood: [Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
391
|
-
sacred_heart: [Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
392
|
-
christ_king: [Ranks::SOLEMNITY_GENERAL, Colours::WHITE],
|
393
|
-
}.each_pair do |method_name, data|
|
394
|
-
date = send(method_name)
|
395
|
-
rank, colour = data
|
396
|
-
@solemnities[date] = Celebration.new(
|
397
|
-
proc { I18n.t("temporale.solemnity.#{method_name}") },
|
398
|
-
rank,
|
399
|
-
colour || SEASON_COLOUR[season(date)]
|
400
|
-
)
|
442
|
+
def validate_sunday_transfer!
|
443
|
+
unsupported = @transfer_to_sunday - SUNDAY_TRANSFERABLE_SOLEMNITIES
|
444
|
+
unless unsupported.empty?
|
445
|
+
raise RuntimeError.new("Transfer of #{unsupported.inspect} to a Sunday not supported. Only #{SUNDAY_TRANSFERABLE_SOLEMNITIES} are allowed.")
|
401
446
|
end
|
402
447
|
end
|
403
448
|
end
|