calendarium-romanum 0.4.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +47 -0
  5. data/.travis.yml +22 -0
  6. data/.yardopts +3 -0
  7. data/CHANGELOG.md +431 -0
  8. data/Gemfile +25 -0
  9. data/Gemfile.lock +86 -0
  10. data/README.md +598 -0
  11. data/Rakefile +16 -0
  12. data/bin/calendariumrom +4 -1
  13. data/calendarium-romanum.gemspec +31 -0
  14. data/config/locales/cs.yml +5 -0
  15. data/config/locales/en.yml +21 -14
  16. data/config/locales/es.yml +94 -0
  17. data/config/locales/fr.yml +7 -0
  18. data/config/locales/it.yml +7 -0
  19. data/config/locales/la.yml +7 -0
  20. data/data/README.md +70 -24
  21. data/data/czech-brno-cs.txt +4 -6
  22. data/data/czech-budejovice-cs.txt +4 -6
  23. data/data/czech-cechy-cs.txt +4 -5
  24. data/data/czech-cs.txt +236 -234
  25. data/data/czech-hradec-cs.txt +3 -5
  26. data/data/czech-litomerice-cs.txt +5 -7
  27. data/data/czech-morava-cs.txt +4 -5
  28. data/data/czech-olomouc-cs.txt +2 -4
  29. data/data/czech-ostrava-cs.txt +3 -5
  30. data/data/czech-plzen-cs.txt +3 -5
  31. data/data/czech-praha-cs.txt +3 -4
  32. data/data/easter_dates.txt +67 -0
  33. data/data/universal-1969-la.txt +234 -0
  34. data/data/universal-en.txt +214 -211
  35. data/data/universal-es.txt +243 -0
  36. data/data/universal-fr.txt +214 -210
  37. data/data/universal-it.txt +214 -211
  38. data/data/universal-la.txt +214 -210
  39. data/doc/data_readme.md +2 -0
  40. data/doc/images/class_diagram.png +0 -0
  41. data/doc/images/class_diagram.puml +44 -0
  42. data/doc/yard_readme.rdoc +76 -0
  43. data/lib/calendarium-romanum.rb +35 -22
  44. data/lib/calendarium-romanum/abstract_date.rb +15 -0
  45. data/lib/calendarium-romanum/calendar.rb +207 -42
  46. data/lib/calendarium-romanum/cli.rb +63 -80
  47. data/lib/calendarium-romanum/cli/comparator.rb +63 -0
  48. data/lib/calendarium-romanum/cli/date_parser.rb +30 -0
  49. data/lib/calendarium-romanum/cli/dumper.rb +68 -0
  50. data/lib/calendarium-romanum/cli/helper.rb +23 -0
  51. data/lib/calendarium-romanum/cli/querier.rb +73 -0
  52. data/lib/calendarium-romanum/cr.rb +16 -0
  53. data/lib/calendarium-romanum/data.rb +50 -20
  54. data/lib/calendarium-romanum/day.rb +208 -32
  55. data/lib/calendarium-romanum/enum.rb +42 -25
  56. data/lib/calendarium-romanum/enums.rb +124 -44
  57. data/lib/calendarium-romanum/errors.rb +4 -0
  58. data/lib/calendarium-romanum/ordinalizer.rb +23 -2
  59. data/lib/calendarium-romanum/perpetual_calendar.rb +58 -7
  60. data/lib/calendarium-romanum/rank.rb +43 -12
  61. data/lib/calendarium-romanum/rank_predicates.rb +43 -0
  62. data/lib/calendarium-romanum/sanctorale.rb +164 -24
  63. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  64. data/lib/calendarium-romanum/sanctorale_loader.rb +180 -0
  65. data/lib/calendarium-romanum/sanctorale_writer.rb +119 -0
  66. data/lib/calendarium-romanum/temporale.rb +226 -94
  67. data/lib/calendarium-romanum/temporale/celebration_factory.rb +107 -0
  68. data/lib/calendarium-romanum/temporale/dates.rb +84 -16
  69. data/lib/calendarium-romanum/temporale/easter_table.rb +27 -0
  70. data/lib/calendarium-romanum/temporale/extensions.rb +15 -0
  71. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +16 -3
  72. data/lib/calendarium-romanum/temporale/extensions/dedication_before_all_saints.rb +73 -0
  73. data/lib/calendarium-romanum/transfers.rb +60 -15
  74. data/lib/calendarium-romanum/util.rb +22 -3
  75. data/lib/calendarium-romanum/version.rb +5 -1
  76. data/liturgical_law/1969_normae_universales.md +568 -0
  77. data/liturgical_law/1977_decretum_de_celebratione_baptismatis_domini.md +58 -0
  78. data/liturgical_law/1990_decretum_de_variatione_inducenda.md +67 -0
  79. data/liturgical_law/1998_notificatio_de_occurrentia.md +57 -0
  80. data/liturgical_law/2002_normae_universales.md +946 -0
  81. data/liturgical_law/2006_notification.md +37 -0
  82. data/liturgical_law/2012_declarationes.md +38 -0
  83. data/liturgical_law/README.md +74 -0
  84. metadata +50 -28
  85. data/lib/calendarium-romanum/sanctoraleloader.rb +0 -115
  86. data/spec/abstract_date_spec.rb +0 -62
  87. data/spec/calendar_spec.rb +0 -330
  88. data/spec/celebration_spec.rb +0 -23
  89. data/spec/cli_spec.rb +0 -26
  90. data/spec/colour_spec.rb +0 -17
  91. data/spec/data_spec.rb +0 -23
  92. data/spec/date_spec.rb +0 -61
  93. data/spec/dates_spec.rb +0 -45
  94. data/spec/day_spec.rb +0 -59
  95. data/spec/enum_spec.rb +0 -51
  96. data/spec/i18n_spec.rb +0 -59
  97. data/spec/ordinalizer_spec.rb +0 -22
  98. data/spec/perpetual_calendar_spec.rb +0 -91
  99. data/spec/rank_spec.rb +0 -57
  100. data/spec/readme_spec.rb +0 -52
  101. data/spec/sanctorale_factory_spec.rb +0 -42
  102. data/spec/sanctorale_spec.rb +0 -191
  103. data/spec/sanctoraleloader_spec.rb +0 -171
  104. data/spec/season_spec.rb +0 -17
  105. data/spec/spec_helper.rb +0 -35
  106. data/spec/temporale_spec.rb +0 -519
@@ -0,0 +1,2 @@
1
+ # @title data/README.md
2
+ {include:file:data/README.md}
@@ -0,0 +1,44 @@
1
+ @startuml
2
+ object Calendar {
3
+ year
4
+ }
5
+ object PerpetualCalendar
6
+
7
+ object Temporale
8
+ object Sanctorale
9
+
10
+ object Day {
11
+ date
12
+ season
13
+ season_week
14
+ celebrations
15
+ vespers
16
+ }
17
+ object Celebration {
18
+ title
19
+ colour
20
+ rank
21
+ date
22
+ cycle
23
+ symbol
24
+ }
25
+ object AbstractDate
26
+ object Season
27
+ object Rank
28
+ object Colour
29
+
30
+ Calendar o-- Temporale
31
+ Calendar o-- Sanctorale
32
+
33
+ PerpetualCalendar *-- Calendar
34
+
35
+ Calendar ..> Day : produces
36
+ Temporale ..> Celebration : produces
37
+ Sanctorale "1" --> "0..*" Celebration
38
+
39
+ Day "1" --> "1..*" Celebration
40
+ Celebration --> Season
41
+ Celebration --> Rank
42
+ Celebration --> Colour
43
+ Celebration --> AbstractDate
44
+ @enduml
@@ -0,0 +1,76 @@
1
+ = calendarium-romanum
2
+
3
+ Ruby gem for
4
+ calendar computations according to the Roman Catholic liturgical
5
+ calendar as instituted by
6
+ MP <em>{Mysterii Paschalis}[http://w2.vatican.va/content/paul-vi/en/motu_proprio/documents/hf_p-vi_motu-proprio_19690214_mysterii-paschalis.html]</em> of Paul VI. (AAS 61 (1969), pp. 222-226).
7
+ The rules are defined in
8
+ <em>{General Norms for the Liturgical Year and the Calendar}[https://www.ewtn.com/catholicism/library/liturgical-year-2193]</em>.
9
+
10
+ == Quickstart
11
+
12
+ For usage instructions with copy-pastable code examples see {file:README.md}
13
+
14
+ == Object model overview
15
+
16
+ rdoc-image:images/class_diagram.png
17
+
18
+ <b>{CalendariumRomanum::Calendar Calendar}</b> is the library's main functional unit.
19
+ It represents calendar for a single liturgical year and allows retrieving calendar data
20
+ for any of the year's days.
21
+ Calendar basically consists of
22
+ a {CalendariumRomanum::Temporale Temporale} and a {CalendariumRomanum::Sanctorale Sanctorale}
23
+ and it's main task is to combine information from both and for any day of the year produce
24
+ a fully and correctly populated {CalendariumRomanum::Day Day} instance,
25
+ containing one or more {CalendariumRomanum::Celebration Celebrations}
26
+ (see {CalendariumRomanum::Calendar#[] Calendar#[]}).
27
+
28
+ (+Calendar+ does not own either +Temporale+ or +Sanctorale+ - it is possible to pass them
29
+ on initialization and they are treated as read-only. The only exception is method
30
+ {CalendariumRomanum::Calendar#freeze #freeze}, which freezes all the +Calendar+'s contents,
31
+ including +Temporale+ and +Sanctorale+.)
32
+
33
+ <b>{CalendariumRomanum::PerpetualCalendar PerpetualCalendar}</b> is a higher-level API
34
+ for retrieving calendar data
35
+ without bothering about liturgical years. It builds {CalendariumRomanum::Calendar Calendar}
36
+ instances internally and passes method calls to them.
37
+
38
+ <b>{CalendariumRomanum::Day Day}</b> contains complete calendar data of a liturgical day.
39
+ It owns one or more {CalendariumRomanum::Celebration Celebrations}.
40
+ If there are multiple it means that any one of them can be chosen and celebrated
41
+ that day.
42
+
43
+ <b>{CalendariumRomanum::Celebration Celebration}</b> represents a celebration
44
+ (particular solemnity / feast / memorial / Sunday / ferial)
45
+ and holds it's liturgical properties, encoded mostly by value objects.
46
+
47
+ (Unlike +Day+, +Celebration+ instances are not bound to a particular date,
48
+ they represent "celebration C in general", not "celebration C in year Y".
49
+ The latter is represented only by a combination of +Celebration+ and +Day+.
50
+ +Celebration+ instances are immutable and some may be used repeatedly in context of various
51
+ days and calendars.)
52
+
53
+ <b>{CalendariumRomanum::Season Season}</b>,
54
+ <b>{CalendariumRomanum::Rank Rank}</b>,
55
+ <b>{CalendariumRomanum::Colour Colour}</b>
56
+ are value objects representing values
57
+ of {CalendariumRomanum::Celebration Celebration} properties.
58
+ Immutable instances representing the standard values are referenced by constants and
59
+ normally there should be no need to create any more.
60
+
61
+ <b>{CalendariumRomanum::Temporale Temporale}</b> represents the temporale cycle of a single liturgical
62
+ year, computes movable feasts, determines liturgical seasons.
63
+ For any day of the year it provides a temporale {CalendariumRomanum::Celebration Celebration}.
64
+
65
+ <b>{CalendariumRomanum::Sanctorale Sanctorale}</b> represents the sanctorale cycle, i.e. the fixed-date
66
+ celebrations, mostly feasts of saints. It is not bound to a particular year, hence a single
67
+ instance can be used by many {CalendariumRomanum::Calendar Calendar} instances representing
68
+ various liturgical years. For any day it provides zero or more
69
+ {CalendariumRomanum::Celebration Celebrations}.
70
+
71
+ Most particular calendars (calendars of countries, provinces, dioceses, churches, religious
72
+ institutes) share the same temporale definition and differ in sanctorale contents.
73
+ Therefore the task of implementing a particular calendar usually consists of populating
74
+ a {CalendariumRomanum::Sanctorale Sanctorale} instance with the desired data.
75
+ A convenient way to do so is preparing a {file:data/README.md sanctorale data file}
76
+ and loading it using {CalendariumRomanum::SanctoraleLoader SanctoraleLoader}.
@@ -1,23 +1,36 @@
1
- %w{
2
- version
3
- i18n_setup
4
- rank
5
- enum
6
- enums
7
- data
8
- calendar
9
- perpetual_calendar
10
- temporale
11
- temporale/dates
12
- temporale/extensions/christ_eternal_priest
13
- sanctorale
14
- sanctoraleloader
15
- sanctorale_factory
16
- transfers
17
- day
18
- abstract_date
19
- util
20
- ordinalizer
21
- }.each do |f|
22
- require_relative File.join('calendarium-romanum', f)
1
+ # Module wrapping the gem's classes
2
+ #
3
+ # If you hate typing the long module name, see {CR}
4
+ module CalendariumRomanum
5
+ end
6
+
7
+ %w(
8
+ version
9
+ i18n_setup
10
+ abstract_date
11
+ rank_predicates
12
+ rank
13
+ enum
14
+ enums
15
+ errors
16
+ data
17
+ day
18
+ calendar
19
+ perpetual_calendar
20
+ temporale/dates
21
+ temporale/celebration_factory
22
+ temporale/easter_table
23
+ temporale/extensions
24
+ temporale/extensions/christ_eternal_priest
25
+ temporale/extensions/dedication_before_all_saints
26
+ temporale
27
+ sanctorale
28
+ sanctorale_loader
29
+ sanctorale_writer
30
+ sanctorale_factory
31
+ transfers
32
+ util
33
+ ordinalizer
34
+ ).each do |f|
35
+ require_relative File.join('calendarium-romanum', f)
23
36
  end
@@ -4,12 +4,20 @@ module CalendariumRomanum
4
4
  class AbstractDate
5
5
  include Comparable
6
6
 
7
+ # @param month [Integer]
8
+ # @param day [Integer]
9
+ # @raise [RangeError] on invalid +month+/+day+ value
7
10
  def initialize(month, day)
8
11
  validate! month, day
9
12
  @month = month
10
13
  @day = day
11
14
  end
12
15
 
16
+ # Build a new instance from a +Date+ (or an object with
17
+ # similar public interface).
18
+ #
19
+ # @param date [Date]
20
+ # @return [AbstractDate]
13
21
  def self.from_date(date)
14
22
  new(date.month, date.day)
15
23
  end
@@ -32,10 +40,17 @@ module CalendariumRomanum
32
40
  month == other.month && day == other.day
33
41
  end
34
42
 
43
+ # Produce a +Date+ by providing a year to an +AbstractDate+
44
+ #
45
+ # @param year [Integer]
46
+ # @return [Date]
35
47
  def concretize(year)
36
48
  Date.new(year, month, day)
37
49
  end
38
50
 
51
+ # @since 0.8.0
52
+ alias in_year concretize
53
+
39
54
  private
40
55
 
41
56
  def validate!(month, day)
@@ -5,47 +5,76 @@ module CalendariumRomanum
5
5
 
6
6
  # Provides complete information concerning a liturgical year,
7
7
  # it's days and celebrations occurring on them.
8
+ #
9
+ # {Calendar}'s business logic is mostly about correctly combining
10
+ # information from {Temporale} and {Sanctorale}.
8
11
  class Calendar
9
12
  extend Forwardable
10
13
 
11
14
  # Day when the implemented calendar system became effective
12
15
  EFFECTIVE_FROM = Date.new(1970, 1, 1).freeze
13
16
 
14
- # year: Integer
15
- # returns a calendar for the liturgical year beginning with
17
+ # Returns a calendar for the liturgical year beginning with
16
18
  # Advent of the specified civil year.
17
- def initialize(year, sanctorale=nil, temporale=nil)
19
+ #
20
+ # @overload initialize(year, sanctorale = nil, temporale = nil, vespers: false)
21
+ # @param year [Integer]
22
+ # Civil year when the liturgical year begins.
23
+ # @param sanctorale [Sanctorale, nil]
24
+ # If not provided, the +Calendar+ will only know celebrations
25
+ # of the temporale cycle, no feasts of the saints!
26
+ # @param temporale [Temporale, nil]
27
+ # If not provided, +Temporale+ for the given year with default
28
+ # configuration will built.
29
+ # @param vespers [Boolean] Set to true if you want the +Calendar+ to populate {Day#vespers}
30
+ #
31
+ # @overload initialize(temporale, sanctorale=nil, vespers: false)
32
+ # @param temporale [Temporale]
33
+ # @param sanctorale [Sanctorale, nil]
34
+ # If not provided, the +Calendar+ will only know celebrations
35
+ # of the temporale cycle, no feasts of the saints!
36
+ # @param vespers [Boolean] Set to true if you want the +Calendar+ to populate {Day#vespers}
37
+ # @since 0.8.0
38
+ #
39
+ # @raise [RangeError]
40
+ # if +year+ is specified for which the implemented calendar
41
+ # system wasn't in force
42
+ def initialize(year, sanctorale = nil, temporale = nil, vespers: false)
43
+ unless year.is_a? Integer
44
+ temporale = year
45
+ year = temporale.year
46
+ end
47
+
18
48
  if year < (EFFECTIVE_FROM.year - 1)
19
49
  raise system_not_effective
20
50
  end
21
51
 
22
52
  if temporale && temporale.year != year
23
- raise ArgumentError.new("Temporale year must be the same as year.")
53
+ raise ArgumentError.new('Temporale year must be the same as year.')
24
54
  end
25
55
 
26
56
  @year = year
27
57
  @sanctorale = sanctorale || Sanctorale.new
28
58
  @temporale = temporale || Temporale.new(year)
29
- @transferred = Transfers.new(@temporale, @sanctorale)
59
+ @populate_vespers = vespers
60
+
61
+ @transferred = Transfers.call(@temporale, @sanctorale).freeze
30
62
  end
31
63
 
32
64
  class << self
65
+ # @api private
33
66
  def mk_date(*args)
34
67
  ex = TypeError.new('Date, DateTime or three Integers expected')
35
68
 
36
- if args.size == 3 then
69
+ if args.size == 3
37
70
  args.each do |a|
38
- unless a.is_a? Integer
39
- raise ex
40
- end
71
+ raise ex unless a.is_a? Integer
41
72
  end
42
- return Date.new *args
73
+ return Date.new(*args)
43
74
 
44
- elsif args.size == 1 then
75
+ elsif args.size == 1
45
76
  a = args.first
46
- unless a.is_a? Date
47
- raise ex
48
- end
77
+ raise ex unless a.is_a? Date
49
78
  return a
50
79
 
51
80
  else
@@ -53,38 +82,103 @@ module CalendariumRomanum
53
82
  end
54
83
  end
55
84
 
56
- # creates a Calendar for the liturgical year including given
57
- # date
85
+ # Creates a new instance for the liturgical year which includes
86
+ # given date
87
+ #
88
+ # @param date [Date]
89
+ # @param constructor_args
90
+ # arguments that will be passed to {initialize}
91
+ # @return [Calendar]
58
92
  def for_day(date, *constructor_args)
59
- return new(Temporale.liturgical_year(date), *constructor_args)
93
+ new(Temporale.liturgical_year(date), *constructor_args)
60
94
  end
61
95
  end # class << self
62
96
 
97
+ # @!method range_check(date)
98
+ # @see Temporale#range_check
99
+ # @param date
100
+ # @return [void]
101
+ # @!method season(date)
102
+ # @see Temporale#season
103
+ # @param date
104
+ # @return [Season]
63
105
  def_delegators :@temporale, :range_check, :season
106
+
107
+ # @return [Integer]
64
108
  attr_reader :year
109
+
110
+ # @return [Temporale]
65
111
  attr_reader :temporale
112
+
113
+ # @return [Sanctorale]
66
114
  attr_reader :sanctorale
67
115
 
68
- def ==(obj)
69
- unless obj.is_a? Calendar
70
- return false
71
- end
116
+ # Solemnities transferred to a date different from the usual one
117
+ # due to occurrence with a higher-ranking celebration.
118
+ #
119
+ # @return [Hash<Date=>Celebration>]
120
+ # @since 0.8.0
121
+ attr_reader :transferred
72
122
 
73
- return year == obj.year
123
+ # Do {Day} instances returned by this +Calendar+
124
+ # have {Day#vespers} populated?
125
+ # @return [Boolean]
126
+ # @since 0.6.0
127
+ def populates_vespers?
128
+ @populate_vespers
74
129
  end
75
130
 
76
- # accepts date information represented as
77
- # Date, DateTime, or two to three integers
78
- # (month - day or year - month - day);
79
- # returns filled Day for the specified day
80
- def day(*args)
131
+ # Two +Calendar+s are equal if they have equal settings
132
+ # (which means that to equal input they return equal data)
133
+ def ==(b)
134
+ b.class == self.class &&
135
+ year == b.year &&
136
+ populates_vespers? == b.populates_vespers? &&
137
+ temporale == b.temporale &&
138
+ sanctorale == b.sanctorale
139
+ end
140
+
141
+ # Retrieve liturgical calendar information for the specified day
142
+ # or range of days.
143
+ #
144
+ # @overload [](date)
145
+ # @param date [Date]
146
+ # @return [Day]
147
+ # @overload [](range)
148
+ # @param range [Range<Date>]
149
+ # @return [Array<Day>]
150
+ def [](args)
151
+ if args.is_a?(Range)
152
+ args.map {|date| day(date) }
153
+ else
154
+ day(args)
155
+ end
156
+ end
157
+
158
+ # Retrieve liturgical calendar information for the specified day
159
+ #
160
+ # @overload day(date, vespers: false)
161
+ # @param date [Date]
162
+ # @overload day(year, month, day, vespers: false)
163
+ # @param year [Integer]
164
+ # @param month [Integer]
165
+ # @param day [Integer]
166
+ # @param vespers [Boolean]
167
+ # Set to +true+ in order to get {Day} with {Day#vespers}
168
+ # populated (overrides instance-wide setting {#populates_vespers?}).
169
+ # @return [Day]
170
+ # @raise [RangeError]
171
+ # If a date is specified on which the implemented calendar
172
+ # system was not yet in force (it became effective during
173
+ # the liturgical year 1969/1970)
174
+ def day(*args, vespers: false)
81
175
  if args.size == 2
82
176
  date = Date.new(@year, *args)
83
177
  unless @temporale.date_range.include? date
84
178
  date = Date.new(@year + 1, *args)
85
179
  end
86
180
  else
87
- date = self.class.mk_date *args
181
+ date = self.class.mk_date(*args)
88
182
  range_check date
89
183
  end
90
184
 
@@ -92,25 +186,63 @@ module CalendariumRomanum
92
186
  raise system_not_effective
93
187
  end
94
188
 
189
+ celebrations = celebrations_for(date)
190
+ vespers_celebration = nil
191
+ if @populate_vespers || vespers
192
+ begin
193
+ vespers_celebration = first_vespers_on(date, celebrations)
194
+ rescue RangeError
195
+ # there is exactly one possible case when
196
+ # range_check(date) passes and range_check(date + 1) fails:
197
+ vespers_celebration = Temporale::CelebrationFactory.first_advent_sunday
198
+ end
199
+ end
200
+
95
201
  s = @temporale.season(date)
96
- return Day.new(
97
- date: date,
98
- season: s,
99
- season_week: @temporale.season_week(s, date),
100
- celebrations: celebrations_for(date)
101
- )
202
+ Day.new(
203
+ date: date,
204
+ season: s,
205
+ season_week: @temporale.season_week(s, date),
206
+ celebrations: celebrations,
207
+ vespers: vespers_celebration
208
+ )
209
+ end
210
+
211
+ # Iterate over the whole liturgical year, day by day,
212
+ # for each day yield calendar data.
213
+ # If called without a block, returns +Enumerator+.
214
+ #
215
+ # @yield [Day]
216
+ # @return [void, Enumerator]
217
+ # @since 0.6.0
218
+ def each
219
+ return to_enum(__method__) unless block_given?
220
+
221
+ temporale.date_range
222
+ .each {|date| yield(day(date)) }
102
223
  end
103
224
 
104
225
  # Sunday lectionary cycle
226
+ #
227
+ # @return [Symbol]
228
+ # For possible values see {LECTIONARY_CYCLES}
105
229
  def lectionary
106
230
  LECTIONARY_CYCLES[@year % 3]
107
231
  end
108
232
 
109
233
  # Ferial lectionary cycle
234
+ #
235
+ # @return [1, 2]
110
236
  def ferial_lectionary
111
237
  @year % 2 + 1
112
238
  end
113
239
 
240
+ # Freezes the instance.
241
+ #
242
+ # *WARNING*: {Temporale} and {Sanctorale} instances passed
243
+ # to the +Calendar+ on initialization will be frozen, too!
244
+ # This is necessary, because a +Calendar+ would not really be
245
+ # frozen were it possible to mutate it's key components.
114
246
  def freeze
115
247
  @temporale.freeze
116
248
  @sanctorale.freeze
@@ -120,11 +252,18 @@ module CalendariumRomanum
120
252
  private
121
253
 
122
254
  def celebrations_for(date)
123
- tr = @transferred.get(date)
255
+ tr = @transferred[date]
124
256
  return [tr] if tr
125
257
 
126
- t = @temporale.get date
127
- st = @sanctorale.get date
258
+ t = @temporale[date]
259
+ st = @sanctorale[date]
260
+
261
+ if date.saturday? &&
262
+ @temporale.season(date) == Seasons::ORDINARY &&
263
+ (st.empty? || st.first.rank == Ranks::MEMORIAL_OPTIONAL) &&
264
+ t.rank <= Ranks::MEMORIAL_OPTIONAL
265
+ st = st.dup << Temporale::CelebrationFactory.saturday_memorial_bvm
266
+ end
128
267
 
129
268
  unless st.empty?
130
269
  if st.first.rank > t.rank
@@ -134,15 +273,41 @@ module CalendariumRomanum
134
273
  return st
135
274
  end
136
275
  elsif t.rank == Ranks::FERIAL_PRIVILEGED && st.first.rank.memorial?
137
- st = st.collect do |c|
138
- Celebration.new(c.title, Ranks::COMMEMORATION, t.colour)
276
+ commemorations = st.collect do |c|
277
+ c.change(rank: Ranks::COMMEMORATION, colour: t.colour)
139
278
  end
140
- st.unshift t
141
- return st
279
+ return commemorations.unshift t
280
+ elsif t.symbol == :immaculate_heart &&
281
+ [Ranks::MEMORIAL_GENERAL, Ranks::MEMORIAL_PROPER].include?(st.first.rank)
282
+ optional_memorials = ([t] + st).collect do |celebration|
283
+ celebration.change rank: Ranks::MEMORIAL_OPTIONAL
284
+ end
285
+ ferial = temporale.send :ferial, date # ugly and evil
286
+ return [ferial] + optional_memorials
287
+ end
288
+ end
289
+
290
+ [t]
291
+ end
292
+
293
+ def first_vespers_on(date, celebrations)
294
+ tomorrow = date + 1
295
+ tomorrow_celebrations = celebrations_for(tomorrow)
296
+
297
+ c = tomorrow_celebrations.first
298
+ if c.rank >= Ranks::SOLEMNITY_PROPER ||
299
+ c.rank == Ranks::SUNDAY_UNPRIVILEGED ||
300
+ (c.rank == Ranks::FEAST_LORD_GENERAL && tomorrow.sunday?)
301
+ if c.symbol == :ash_wednesday || c.symbol == :good_friday
302
+ return nil
303
+ end
304
+
305
+ if c.rank > celebrations.first.rank || c.symbol == :easter_sunday
306
+ return c
142
307
  end
143
308
  end
144
309
 
145
- return [t]
310
+ nil
146
311
  end
147
312
 
148
313
  def system_not_effective