calendarium-romanum 0.3.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) 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 +20 -0
  6. data/.yardopts +3 -0
  7. data/CHANGELOG.md +340 -0
  8. data/Gemfile +25 -0
  9. data/Gemfile.lock +86 -0
  10. data/README.md +515 -0
  11. data/Rakefile +9 -0
  12. data/bin/calendariumrom +4 -1
  13. data/calendarium-romanum.gemspec +26 -0
  14. data/config/locales/cs.yml +17 -1
  15. data/config/locales/en.yml +28 -14
  16. data/config/locales/es.yml +90 -0
  17. data/config/locales/fr.yml +90 -0
  18. data/config/locales/it.yml +18 -2
  19. data/config/locales/la.yml +17 -1
  20. data/data/README.md +43 -1
  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 +237 -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/universal-en.txt +214 -211
  33. data/data/universal-es.txt +243 -0
  34. data/data/universal-fr.txt +243 -0
  35. data/data/universal-it.txt +214 -211
  36. data/data/universal-la.txt +214 -210
  37. data/doc/data_readme.md +2 -0
  38. data/doc/images/class_diagram.png +0 -0
  39. data/doc/images/class_diagram.puml +44 -0
  40. data/doc/yard_readme.rdoc +76 -0
  41. data/lib/calendarium-romanum.rb +30 -21
  42. data/lib/calendarium-romanum/abstract_date.rb +12 -0
  43. data/lib/calendarium-romanum/calendar.rb +207 -52
  44. data/lib/calendarium-romanum/cli.rb +101 -52
  45. data/lib/calendarium-romanum/cr.rb +16 -0
  46. data/lib/calendarium-romanum/data.rb +46 -18
  47. data/lib/calendarium-romanum/day.rb +202 -20
  48. data/lib/calendarium-romanum/enum.rb +24 -5
  49. data/lib/calendarium-romanum/enums.rb +102 -36
  50. data/lib/calendarium-romanum/errors.rb +4 -0
  51. data/lib/calendarium-romanum/ordinalizer.rb +30 -6
  52. data/lib/calendarium-romanum/perpetual_calendar.rb +97 -0
  53. data/lib/calendarium-romanum/rank.rb +43 -6
  54. data/lib/calendarium-romanum/sanctorale.rb +170 -24
  55. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  56. data/lib/calendarium-romanum/sanctorale_loader.rb +176 -0
  57. data/lib/calendarium-romanum/temporale.rb +251 -119
  58. data/lib/calendarium-romanum/temporale/celebration_factory.rb +106 -0
  59. data/lib/calendarium-romanum/temporale/dates.rb +117 -36
  60. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +18 -6
  61. data/lib/calendarium-romanum/transfers.rb +20 -1
  62. data/lib/calendarium-romanum/util.rb +36 -3
  63. data/lib/calendarium-romanum/version.rb +5 -1
  64. metadata +29 -21
  65. data/lib/calendarium-romanum/sanctoraleloader.rb +0 -115
  66. data/spec/abstract_date_spec.rb +0 -62
  67. data/spec/calendar_spec.rb +0 -352
  68. data/spec/cli_spec.rb +0 -26
  69. data/spec/data_spec.rb +0 -23
  70. data/spec/date_spec.rb +0 -61
  71. data/spec/dates_spec.rb +0 -45
  72. data/spec/enum_spec.rb +0 -51
  73. data/spec/i18n_spec.rb +0 -59
  74. data/spec/rank_spec.rb +0 -42
  75. data/spec/readme_spec.rb +0 -52
  76. data/spec/sanctorale_factory_spec.rb +0 -42
  77. data/spec/sanctorale_spec.rb +0 -167
  78. data/spec/sanctoraleloader_spec.rb +0 -171
  79. data/spec/spec_helper.rb +0 -35
  80. data/spec/temporale_spec.rb +0 -500
@@ -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,22 +1,31 @@
1
- %w{
2
- version
3
- i18n_setup
4
- rank
5
- enum
6
- enums
7
- data
8
- calendar
9
- temporale
10
- temporale/dates
11
- temporale/extensions/christ_eternal_priest
12
- sanctorale
13
- sanctoraleloader
14
- sanctorale_factory
15
- transfers
16
- day
17
- abstract_date
18
- util
19
- ordinalizer
20
- }.each do |f|
21
- 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
12
+ enum
13
+ enums
14
+ errors
15
+ data
16
+ day
17
+ calendar
18
+ perpetual_calendar
19
+ temporale/dates
20
+ temporale/celebration_factory
21
+ temporale/extensions/christ_eternal_priest
22
+ temporale
23
+ sanctorale
24
+ sanctorale_loader
25
+ sanctorale_factory
26
+ transfers
27
+ util
28
+ ordinalizer
29
+ ).each do |f|
30
+ require_relative File.join('calendarium-romanum', f)
22
31
  end
@@ -4,12 +4,20 @@ module CalendariumRomanum
4
4
  class AbstractDate
5
5
  include Comparable
6
6
 
7
+ # @param month [Fixnum]
8
+ # @param day [Fixnum]
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,6 +40,10 @@ 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 [Fixnum]
46
+ # @return [Date]
35
47
  def concretize(year)
36
48
  Date.new(year, month, day)
37
49
  end
@@ -5,38 +5,61 @@ 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
-
10
12
  extend Forwardable
11
13
 
12
- # year: Integer
13
- # returns a calendar for the liturgical year beginning with
14
+ # Day when the implemented calendar system became effective
15
+ EFFECTIVE_FROM = Date.new(1970, 1, 1).freeze
16
+
17
+ # Returns a calendar for the liturgical year beginning with
14
18
  # Advent of the specified civil year.
15
- def initialize(year, sanctorale=nil, temporale_class=nil)
19
+ #
20
+ # @param year [Fixnum]
21
+ # Civil year when the liturgical year begins.
22
+ # @param sanctorale [Sanctorale, nil]
23
+ # If not provided, the +Calendar+ will only know celebrations
24
+ # of the temporale cycle, no feasts of the saints!
25
+ # @param temporale [Temporale, nil]
26
+ # If not provided, +Temporale+ for the given year with default
27
+ # configuration will built.
28
+ # @param vespers [Boolean] Set to true if you want the +Calendar+ to populate {Day#vespers}
29
+ # @raise [RangeError]
30
+ # if +year+ is specified for which the implemented calendar
31
+ # system wasn't in force
32
+ def initialize(year, sanctorale = nil, temporale = nil, vespers: false)
33
+ if year < (EFFECTIVE_FROM.year - 1)
34
+ raise system_not_effective
35
+ end
36
+
37
+ if temporale && temporale.year != year
38
+ raise ArgumentError.new('Temporale year must be the same as year.')
39
+ end
40
+
16
41
  @year = year
17
42
  @sanctorale = sanctorale || Sanctorale.new
18
- @temporale_class = temporale_class || Temporale
19
- @temporale = @temporale_class.new(year)
43
+ @temporale = temporale || Temporale.new(year)
44
+ @populate_vespers = vespers
45
+
20
46
  @transferred = Transfers.new(@temporale, @sanctorale)
21
47
  end
22
48
 
23
49
  class << self
50
+ # @api private
24
51
  def mk_date(*args)
25
52
  ex = TypeError.new('Date, DateTime or three Integers expected')
26
53
 
27
- if args.size == 3 then
54
+ if args.size == 3
28
55
  args.each do |a|
29
- unless a.is_a? Integer
30
- raise ex
31
- end
56
+ raise ex unless a.is_a? Integer
32
57
  end
33
- return Date.new *args
58
+ return Date.new(*args)
34
59
 
35
- elsif args.size == 1 then
60
+ elsif args.size == 1
36
61
  a = args.first
37
- unless a.is_a? Date
38
- raise ex
39
- end
62
+ raise ex unless a.is_a? Date
40
63
  return a
41
64
 
42
65
  else
@@ -44,97 +67,229 @@ module CalendariumRomanum
44
67
  end
45
68
  end
46
69
 
47
- # creates a Calendar for the liturgical year including given
48
- # date
70
+ # Creates a new instance for the liturgical year which includes
71
+ # given date
72
+ #
73
+ # @param date [Date]
74
+ # @param constructor_args
75
+ # arguments that will be passed to {initialize}
76
+ # @return [Calendar]
49
77
  def for_day(date, *constructor_args)
50
- return new(Temporale.liturgical_year(date), *constructor_args)
78
+ new(Temporale.liturgical_year(date), *constructor_args)
51
79
  end
52
80
  end # class << self
53
81
 
82
+ # @!method range_check(date)
83
+ # @see Temporale#range_check
84
+ # @param date
85
+ # @return [void]
86
+ # @!method season(date)
87
+ # @see Temporale#season
88
+ # @param date
89
+ # @return [Season]
54
90
  def_delegators :@temporale, :range_check, :season
91
+
92
+ # @return [Fixnum]
55
93
  attr_reader :year
94
+
95
+ # @return [Temporale]
56
96
  attr_reader :temporale
97
+
98
+ # @return [Sanctorale]
57
99
  attr_reader :sanctorale
58
100
 
59
- # returns a Calendar for the subsequent year
60
- def succ
61
- c = Calendar.new @year + 1, @sanctorale, @temporale_class
62
- return c
101
+ # Do {Day} instances returned by this +Calendar+
102
+ # have {Day#vespers} populated?
103
+ # @return [Boolean]
104
+ # @since 0.6.0
105
+ def populates_vespers?
106
+ @populate_vespers
63
107
  end
64
108
 
65
- # returns a Calendar for the previous year
66
- def pred
67
- c = Calendar.new @year - 1, @sanctorale, @temporale_class
68
- return c
109
+ # Two +Calendar+s are equal if they have equal settings
110
+ # (which means that to equal input they return equal data)
111
+ def ==(b)
112
+ b.class == self.class &&
113
+ year == b.year &&
114
+ populates_vespers? == b.populates_vespers? &&
115
+ temporale == b.temporale &&
116
+ sanctorale == b.sanctorale
69
117
  end
70
118
 
71
- def ==(obj)
72
- unless obj.is_a? Calendar
73
- return false
119
+ # Retrieve liturgical calendar information for the specified day
120
+ # or range of days.
121
+ #
122
+ # @overload [](date)
123
+ # @param date [Date]
124
+ # @return [Day]
125
+ # @overload [](range)
126
+ # @param range [Range<Date>]
127
+ # @return [Array<Day>]
128
+ def [](args)
129
+ if args.is_a?(Range)
130
+ args.map {|date| day(date) }
131
+ else
132
+ day(args)
74
133
  end
75
-
76
- return year == obj.year
77
134
  end
78
135
 
79
- # accepts date information represented as
80
- # Date, DateTime, or two to three integers
81
- # (month - day or year - month - day);
82
- # returns filled Day for the specified day
83
- def day(*args)
136
+ # Retrieve liturgical calendar information for the specified day
137
+ #
138
+ # @overload day(date, vespers: false)
139
+ # @param date [Date]
140
+ # @overload day(year, month, day, vespers: false)
141
+ # @param year [Fixnum]
142
+ # @param month [Fixnum]
143
+ # @param day [Fixnum]
144
+ # @param vespers [Boolean]
145
+ # Set to +true+ in order to get {Day} with {Day#vespers}
146
+ # populated (overrides instance-wide setting {#populates_vespers?}).
147
+ # @return [Day]
148
+ # @raise [RangeError]
149
+ # If a date is specified on which the implemented calendar
150
+ # system was not yet in force (it became effective during
151
+ # the liturgical year 1969/1970)
152
+ def day(*args, vespers: false)
84
153
  if args.size == 2
85
154
  date = Date.new(@year, *args)
86
155
  unless @temporale.date_range.include? date
87
156
  date = Date.new(@year + 1, *args)
88
157
  end
89
158
  else
90
- date = self.class.mk_date *args
159
+ date = self.class.mk_date(*args)
91
160
  range_check date
92
161
  end
93
162
 
163
+ if date < EFFECTIVE_FROM
164
+ raise system_not_effective
165
+ end
166
+
167
+ celebrations = celebrations_for(date)
168
+ vespers_celebration = nil
169
+ if @populate_vespers || vespers
170
+ begin
171
+ vespers_celebration = first_vespers_on(date, celebrations)
172
+ rescue RangeError
173
+ # there is exactly one possible case when
174
+ # range_check(date) passes and range_check(date + 1) fails:
175
+ vespers_celebration = Temporale::CelebrationFactory.first_advent_sunday
176
+ end
177
+ end
178
+
94
179
  s = @temporale.season(date)
95
- return Day.new(
96
- date: date,
97
- season: s,
98
- season_week: @temporale.season_week(s, date),
99
- celebrations: celebrations_for(date)
100
- )
180
+ Day.new(
181
+ date: date,
182
+ season: s,
183
+ season_week: @temporale.season_week(s, date),
184
+ celebrations: celebrations,
185
+ vespers: vespers_celebration
186
+ )
187
+ end
188
+
189
+ # Iterate over the whole liturgical year, day by day,
190
+ # for each day yield calendar data.
191
+ # If called without a block, returns +Enumerator+.
192
+ #
193
+ # @yield [Day]
194
+ # @return [void, Enumerator]
195
+ # @since 0.6.0
196
+ def each
197
+ return to_enum(__method__) unless block_given?
198
+
199
+ temporale.date_range
200
+ .each {|date| yield(day(date)) }
101
201
  end
102
202
 
103
203
  # Sunday lectionary cycle
204
+ #
205
+ # @return [Symbol]
206
+ # For possible values see {LECTIONARY_CYCLES}
104
207
  def lectionary
105
208
  LECTIONARY_CYCLES[@year % 3]
106
209
  end
107
210
 
108
211
  # Ferial lectionary cycle
212
+ #
213
+ # @return [1, 2]
109
214
  def ferial_lectionary
110
215
  @year % 2 + 1
111
216
  end
112
217
 
218
+ # Freezes the instance.
219
+ #
220
+ # *WARNING*: {Temporale} and {Sanctorale} instances passed
221
+ # to the +Calendar+ on initialization will be frozen, too!
222
+ # This is necessary, because a +Calendar+ would not really be
223
+ # frozen were it possible to mutate it's key components.
224
+ def freeze
225
+ @temporale.freeze
226
+ @sanctorale.freeze
227
+ super
228
+ end
229
+
230
+ private
231
+
113
232
  def celebrations_for(date)
114
233
  tr = @transferred.get(date)
115
234
  return [tr] if tr
116
235
 
117
- t = @temporale.get date
118
- st = @sanctorale.get date
236
+ t = @temporale[date]
237
+ st = @sanctorale[date]
238
+
239
+ if date.saturday? &&
240
+ @temporale.season(date) == Seasons::ORDINARY &&
241
+ (st.empty? || st.first.rank == Ranks::MEMORIAL_OPTIONAL) &&
242
+ t.rank <= Ranks::MEMORIAL_OPTIONAL
243
+ st = st.dup << Temporale::CelebrationFactory.saturday_memorial_bvm
244
+ end
119
245
 
120
246
  unless st.empty?
121
247
  if st.first.rank > t.rank
122
248
  if st.first.rank == Ranks::MEMORIAL_OPTIONAL
123
- st.unshift t
124
- return st
249
+ return st.dup.unshift t
125
250
  else
126
251
  return st
127
252
  end
128
253
  elsif t.rank == Ranks::FERIAL_PRIVILEGED && st.first.rank.memorial?
129
- st = st.collect do |c|
130
- Celebration.new(c.title, Ranks::COMMEMORATION, t.colour)
254
+ commemorations = st.collect do |c|
255
+ c.change(rank: Ranks::COMMEMORATION, colour: t.colour)
131
256
  end
132
- st.unshift t
133
- return st
257
+ return commemorations.unshift t
258
+ elsif t.symbol == :immaculate_heart &&
259
+ [Ranks::MEMORIAL_GENERAL, Ranks::MEMORIAL_PROPER].include?(st.first.rank)
260
+ optional_memorials = ([t] + st).collect do |celebration|
261
+ celebration.change rank: Ranks::MEMORIAL_OPTIONAL
262
+ end
263
+ ferial = temporale.send :ferial, date # ugly and evil
264
+ return [ferial] + optional_memorials
134
265
  end
135
266
  end
136
267
 
137
- return [t]
268
+ [t]
269
+ end
270
+
271
+ def first_vespers_on(date, celebrations)
272
+ tomorrow = date + 1
273
+ tomorrow_celebrations = celebrations_for(tomorrow)
274
+
275
+ c = tomorrow_celebrations.first
276
+ if c.rank >= Ranks::SOLEMNITY_PROPER ||
277
+ c.rank == Ranks::SUNDAY_UNPRIVILEGED ||
278
+ (c.rank == Ranks::FEAST_LORD_GENERAL && tomorrow.sunday?)
279
+ if c.symbol == :ash_wednesday || c.symbol == :good_friday
280
+ return nil
281
+ end
282
+
283
+ if c.rank > celebrations.first.rank || c.symbol == :easter_sunday
284
+ return c
285
+ end
286
+ end
287
+
288
+ nil
289
+ end
290
+
291
+ def system_not_effective
292
+ RangeError.new('Year out of range. Implemented calendar system has been in use only since 1st January 1970.')
138
293
  end
139
294
  end # class Calendar
140
295
  end