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.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/bin/calendariumrom +4 -1
  3. data/config/locales/cs.yml +54 -2
  4. data/config/locales/en.yml +64 -14
  5. data/config/locales/es.yml +90 -0
  6. data/config/locales/fr.yml +90 -0
  7. data/config/locales/it.yml +54 -4
  8. data/config/locales/la.yml +52 -2
  9. data/data/README.md +105 -5
  10. data/data/czech-brno-cs.txt +11 -5
  11. data/data/czech-budejovice-cs.txt +11 -5
  12. data/data/czech-cechy-cs.txt +11 -5
  13. data/data/czech-cs.txt +243 -234
  14. data/data/czech-hradec-cs.txt +10 -4
  15. data/data/czech-litomerice-cs.txt +12 -6
  16. data/data/czech-morava-cs.txt +11 -5
  17. data/data/czech-olomouc-cs.txt +9 -3
  18. data/data/czech-ostrava-cs.txt +10 -4
  19. data/data/czech-plzen-cs.txt +10 -4
  20. data/data/czech-praha-cs.txt +10 -3
  21. data/data/universal-en.txt +218 -212
  22. data/data/universal-es.txt +243 -0
  23. data/data/universal-fr.txt +243 -0
  24. data/data/universal-it.txt +218 -212
  25. data/data/universal-la.txt +218 -211
  26. data/lib/calendarium-romanum.rb +30 -18
  27. data/lib/calendarium-romanum/abstract_date.rb +12 -0
  28. data/lib/calendarium-romanum/calendar.rb +210 -48
  29. data/lib/calendarium-romanum/cli.rb +101 -52
  30. data/lib/calendarium-romanum/cr.rb +16 -0
  31. data/lib/calendarium-romanum/data.rb +46 -18
  32. data/lib/calendarium-romanum/day.rb +200 -21
  33. data/lib/calendarium-romanum/enum.rb +24 -5
  34. data/lib/calendarium-romanum/enums.rb +123 -37
  35. data/lib/calendarium-romanum/errors.rb +4 -0
  36. data/lib/calendarium-romanum/ordinalizer.rb +61 -0
  37. data/lib/calendarium-romanum/perpetual_calendar.rb +97 -0
  38. data/lib/calendarium-romanum/rank.rb +43 -6
  39. data/lib/calendarium-romanum/sanctorale.rb +142 -22
  40. data/lib/calendarium-romanum/sanctorale_factory.rb +74 -3
  41. data/lib/calendarium-romanum/sanctorale_loader.rb +176 -0
  42. data/lib/calendarium-romanum/temporale.rb +296 -251
  43. data/lib/calendarium-romanum/temporale/celebration_factory.rb +106 -0
  44. data/lib/calendarium-romanum/temporale/dates.rb +232 -0
  45. data/lib/calendarium-romanum/temporale/extensions/christ_eternal_priest.rb +37 -0
  46. data/lib/calendarium-romanum/transfers.rb +43 -6
  47. data/lib/calendarium-romanum/util.rb +36 -3
  48. data/lib/calendarium-romanum/version.rb +5 -1
  49. data/spec/abstract_date_spec.rb +11 -3
  50. data/spec/calendar_spec.rb +645 -188
  51. data/spec/celebration_factory_spec.rb +40 -0
  52. data/spec/celebration_spec.rb +67 -0
  53. data/spec/cli_spec.rb +154 -11
  54. data/spec/colour_spec.rb +22 -0
  55. data/spec/data_spec.rb +26 -3
  56. data/spec/date_parser_spec.rb +68 -0
  57. data/spec/date_spec.rb +8 -8
  58. data/spec/dates_spec.rb +73 -0
  59. data/spec/day_spec.rb +151 -0
  60. data/spec/i18n_spec.rb +11 -2
  61. data/spec/ordinalizer_spec.rb +44 -0
  62. data/spec/perpetual_calendar_spec.rb +125 -0
  63. data/spec/rank_spec.rb +42 -7
  64. data/spec/readme_spec.rb +18 -10
  65. data/spec/sanctorale_factory_spec.rb +113 -9
  66. data/spec/sanctorale_loader_spec.rb +229 -0
  67. data/spec/sanctorale_spec.rb +176 -62
  68. data/spec/season_spec.rb +22 -0
  69. data/spec/spec_helper.rb +27 -1
  70. data/spec/temporale_spec.rb +473 -154
  71. data/spec/year_spec.rb +25 -0
  72. metadata +42 -7
  73. data/lib/calendarium-romanum/sanctoraleloader.rb +0 -104
  74. data/spec/sanctoraleloader_spec.rb +0 -171
@@ -1,12 +1,19 @@
1
1
  module CalendariumRomanum
2
2
 
3
+ # General utilities, not tied to the business domain
4
+ # of liturgical calendar computation.
3
5
  module Util
4
6
 
5
7
  # Abstract superclass for date enumerators.
8
+ # @abstract
6
9
  class DateEnumerator
7
10
  include Enumerable
8
11
 
12
+ # @yield [Date]
13
+ # @return [void, Enumerator]
9
14
  def each
15
+ return to_enum(__method__) unless block_given?
16
+
10
17
  d = @start
11
18
  begin
12
19
  yield d
@@ -14,14 +21,16 @@ module CalendariumRomanum
14
21
  end until enumeration_over? d
15
22
  end
16
23
 
24
+ # @param date [Date]
25
+ # @return [Boolean]
17
26
  def enumeration_over?(date)
18
27
  @start.send(@prop) != date.send(@prop)
19
28
  end
20
29
 
21
- alias_method :each_day, :each
30
+ alias each_day each
22
31
  end
23
32
 
24
- # enumerates days of a year
33
+ # Enumerates days of a year
25
34
  class Year < DateEnumerator
26
35
  def initialize(year)
27
36
  @start = Date.new year, 1, 1
@@ -29,12 +38,36 @@ module CalendariumRomanum
29
38
  end
30
39
  end
31
40
 
32
- # enumerates days of a month
41
+ # Enumerates days of a month
33
42
  class Month < DateEnumerator
34
43
  def initialize(year, month)
35
44
  @start = Date.new year, month, 1
36
45
  @prop = :month
37
46
  end
38
47
  end
48
+
49
+ # @api private
50
+ class DateParser
51
+ def initialize(date_str)
52
+ if date_str =~ /((\d{4})(\/|-)?(\d{0,2})(\/|-)?(\d{0,2}))\z/ # Accepts YYYY-MM-DD, YYYY/MM/DD where both day and month are optional
53
+ year = Regexp.last_match(2).to_i
54
+ month = Regexp.last_match(4).to_i
55
+ day = Regexp.last_match(6).to_i
56
+ @date_range = if (day == 0) && (month == 0) # Only year is given
57
+ Year.new(year)
58
+ elsif day == 0 # Year and month are given
59
+ Month.new(year, month)
60
+ else
61
+ Date.new(year, month, day)..Date.new(year, month, day)
62
+ end
63
+ else
64
+ raise ArgumentError, 'Unparseable date'
65
+ end
66
+ end
67
+
68
+ # @return [DateEnumerator, Range]
69
+ attr_reader :date_range
70
+ end
71
+
39
72
  end
40
73
  end
@@ -1,3 +1,7 @@
1
+ require 'date'
2
+
1
3
  module CalendariumRomanum
2
- VERSION = '0.2.1'
4
+ VERSION = '0.7.0'.freeze
5
+
6
+ RELEASE_DATE = Date.new(2020, 6, 21).freeze
3
7
  end
@@ -43,9 +43,17 @@ describe CR::AbstractDate do
43
43
  end
44
44
 
45
45
  describe '#<' do
46
- it { expect(AD.new(1, 1)).to be < AD.new(1, 2) }
47
- it { expect(AD.new(1, 1)).to be < AD.new(2, 1) }
48
- it { expect(AD.new(1, 1)).not_to be < AD.new(1, 1) }
46
+ it 'days of the same month' do
47
+ expect(AD.new(1, 1)).to be < AD.new(1, 2)
48
+ end
49
+
50
+ it 'the same day, different months' do
51
+ expect(AD.new(1, 1)).to be < AD.new(2, 1)
52
+ end
53
+
54
+ it 'same' do
55
+ expect(AD.new(1, 1)).not_to be < AD.new(1, 1)
56
+ end
49
57
  end
50
58
 
51
59
  describe '#==' do
@@ -1,183 +1,341 @@
1
1
  require_relative 'spec_helper'
2
2
 
3
3
  describe CR::Calendar do
4
+ before :all do
5
+ @c = described_class.new(2013).freeze
6
+ end
7
+
8
+ let(:celfactory) { CR::Temporale::CelebrationFactory }
9
+
10
+ describe '.new' do
11
+ it 'throws RangeError on invalid year' do
12
+ expect do
13
+ described_class.new(1968)
14
+ end.to raise_exception(RangeError, /in use only since 1st January 1970/)
15
+ end
16
+
17
+ it 'throws ArgumentError when Temporale year does not match' do
18
+ year = 2000
19
+ temporale = CR::Temporale.new year
20
+ expect do
21
+ CR::Calendar.new(year + 1, nil, temporale)
22
+ end.to raise_exception ArgumentError
23
+ end
24
+ end
4
25
 
5
- describe 'core functions' do
6
- before :all do
7
- @c = described_class.new 2013
26
+ describe '.for_day' do
27
+ it 'continues the previous year\'s calendar in summer' do
28
+ expect(described_class.for_day(Date.new(2014, 6, 9))).to eq described_class.new(2013)
8
29
  end
9
30
 
10
- describe '#==' do
11
- it 'considers calendars with the same year same' do
12
- described_class.new(2014).should == described_class.new(2014)
31
+ it 'provides the current year\'s calendar in December' do
32
+ expect(described_class.for_day(Date.new(2014, 12, 20))).to eq described_class.new(2014)
33
+ end
34
+ end
35
+
36
+ describe '.mk_date' do
37
+ describe 'with a Date' do
38
+ it 'returns the Date' do
39
+ date = Date.today
40
+ expect(described_class.mk_date(date))
41
+ .to be date
13
42
  end
43
+ end
14
44
 
15
- it 'considers calendars with different year different' do
16
- described_class.new(2014).should_not == described_class.new(2010)
45
+ describe 'with three integers' do
46
+ it 'builds a Date' do
47
+ expect(described_class.mk_date(1, 2, 3))
48
+ .to eq Date.new(1, 2, 3)
17
49
  end
18
50
  end
19
51
 
20
- describe '#lectionary' do
21
- it 'detects correctly' do
22
- described_class.new(2014).lectionary.should eq :B
23
- described_class.new(2013).lectionary.should eq :A
24
- described_class.new(2012).lectionary.should eq :C
52
+ describe 'invalid arguments' do
53
+ it 'only two arguments' do
54
+ expect { described_class.mk_date(1, 2) }
55
+ .to raise_exception TypeError
25
56
  end
26
57
  end
58
+ end
59
+
60
+ describe '#each' do
61
+ let (:day_count) {(@c.temporale.start_date..@c.temporale.end_date).count}
27
62
 
28
- describe '#ferial_lectionary' do
29
- it 'detects correctly' do
30
- described_class.new(2014).ferial_lectionary.should eq 1
31
- described_class.new(2013).ferial_lectionary.should eq 2
63
+ it 'yields for each iteration' do
64
+ expect {|b| @c.each(&b) }.to yield_control
65
+ end
66
+
67
+ it 'yields the expected number of times' do
68
+ expect {|b| @c.each(&b) }.to yield_control.exactly(day_count).times
69
+ end
70
+
71
+ it 'yields calendar day instances' do
72
+ expected_class = Array.new(day_count, CR::Day)
73
+ expect {|b| @c.each(&b) }.to yield_successive_args(*expected_class)
74
+ end
75
+
76
+ it 'returns Enumerator if called without a block' do
77
+ expect(@c.each).to be_a Enumerator
78
+ end
79
+ end
80
+
81
+ describe '#==' do
82
+ let(:year) { 2014 }
83
+ let(:b) { described_class.new(year) }
84
+
85
+ describe 'year' do
86
+ let(:a) { described_class.new(year) }
87
+
88
+ it 'same' do
89
+ expect(a).to be == described_class.new(year)
90
+ end
91
+
92
+ it 'different' do
93
+ expect(a).not_to be == described_class.new(year + 1)
32
94
  end
33
95
  end
34
96
 
35
- describe '.for_day' do
36
- it 'continues the previous year\'s calendar in summer' do
37
- described_class.for_day(Date.new(2014, 6, 9)).should eq described_class.new(2013)
97
+ describe 'sanctorale' do
98
+ let(:sanctorale) { CR::Data::GENERAL_ROMAN_ENGLISH.load }
99
+ let(:a) { described_class.new(year, sanctorale) }
100
+
101
+ it 'same' do
102
+ expect(a).to be == described_class.new(year, sanctorale)
38
103
  end
39
104
 
40
- it 'provides the current year\'s calendar in December' do
41
- described_class.for_day(Date.new(2014, 12, 20)).should eq described_class.new(2014)
105
+ it 'different' do
106
+ expect(a == b).to be false
42
107
  end
43
108
  end
44
109
 
45
- describe '#day' do
46
- describe 'received arguments' do
47
- describe 'Date' do
48
- it 'returns a Day' do
49
- @c.day(Date.new(2013, 12, 10)).should be_a CR::Day
50
- end
110
+ describe 'temporale' do
111
+ let(:temporale) { CR::Temporale.new year, transfer_to_sunday: [:epiphany] }
112
+ let(:a) { described_class.new(year, nil, temporale) }
113
+
114
+ it 'same' do
115
+ expect(a).to be == described_class.new(year, nil, temporale)
116
+ end
117
+
118
+ it 'different' do
119
+ expect(a).not_to be == b
120
+ end
121
+ end
122
+
123
+ describe 'vespers' do
124
+ let(:a) { described_class.new(year, vespers: true) }
125
+
126
+ it 'same' do
127
+ expect(a).to be == described_class.new(year, vespers: true)
128
+ end
129
+
130
+ it 'different' do
131
+ expect(a == b).to be false
132
+ end
133
+ end
134
+ end
135
+
136
+ describe '#lectionary' do
137
+ {
138
+ 2014 => :B,
139
+ 2013 => :A,
140
+ 2012 => :C
141
+ }.each_pair do |year, cycle|
142
+ it year.to_s do
143
+ expect(described_class.new(year).lectionary).to eq cycle
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#ferial_lectionary' do
149
+ {
150
+ 2014 => 1,
151
+ 2013 => 2
152
+ }.each do |year, cycle|
153
+ it year.to_s do
154
+ expect(described_class.new(year).ferial_lectionary).to eq cycle
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#[]' do
160
+ describe 'received arguments' do
161
+ describe 'Date' do
162
+ it 'returns a Day' do
163
+ expect(@c[Date.new(2013, 12, 10)]).to be_a CR::Day
164
+ end
165
+ end
166
+
167
+ describe 'Date range' do
168
+ it 'returns an array of Days' do
169
+ array = @c[Date.new(2013, 12, 10)..Date.new(2014, 4, 10)]
170
+ expect(array).to be_a Array
171
+ array.each{|day| expect(day).to be_a CR::Day}
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '#day' do
178
+ describe 'received arguments' do
179
+ describe 'Date' do
180
+ it 'returns a Day' do
181
+ expect(@c.day(Date.new(2013, 12, 10))).to be_a CR::Day
182
+ end
183
+ end
184
+
185
+ describe 'DateTime' do
186
+ it 'returns a Day' do
187
+ expect(@c.day(DateTime.new(2013, 12, 10, 12, 10, 0))).to be_a CR::Day
51
188
  end
189
+ end
190
+
191
+ describe 'three Integers' do
192
+ it 'returns a Day' do
193
+ expect(@c.day(2013, 12, 10)).to be_a CR::Day
194
+ end
195
+ end
52
196
 
53
- describe 'DateTime' do
54
- it 'returns a Day' do
55
- @c.day(DateTime.new(2013, 12, 10, 12, 10, 0)).should be_a CR::Day
197
+ describe 'two integers' do
198
+ describe 'autumn' do
199
+ it 'supplies year' do
200
+ day = @c.day(12, 10)
201
+ expect(day).to be_a CR::Day
202
+ expect(day.date).to eq Date.new(2013, 12, 10)
56
203
  end
57
204
  end
58
205
 
59
- describe 'three Integers' do
60
- it 'returns a Day' do
61
- @c.day(2013, 12, 10).should be_a CR::Day
206
+ describe 'spring' do
207
+ it 'supplies year' do
208
+ day = @c.day(4, 10)
209
+ expect(day).to be_a CR::Day
210
+ expect(day.date).to eq Date.new(2014, 4, 10)
62
211
  end
63
212
  end
64
213
 
65
- describe 'two integers' do
66
- describe 'autumn' do
67
- it 'supplies year' do
68
- day = @c.day(12, 10)
69
- expect(day).to be_a CR::Day
70
- expect(day.date).to eq Date.new(2013, 12, 10)
214
+ describe 'invalid' do
215
+ describe 'absolutely' do
216
+ it 'fails' do
217
+ expect do
218
+ @c.day(0, 34)
219
+ end.to raise_exception(ArgumentError, 'invalid date')
71
220
  end
72
221
  end
73
222
 
74
- describe 'spring' do
75
- it 'supplies year' do
76
- day = @c.day(4, 10)
77
- expect(day).to be_a CR::Day
78
- expect(day.date).to eq Date.new(2014, 4, 10)
223
+ describe 'for the given year' do
224
+ it 'fails' do
225
+ expect do
226
+ @c.day(2, 29)
227
+ end.to raise_exception(ArgumentError, 'invalid date')
79
228
  end
80
229
  end
81
230
  end
82
231
  end
232
+ end
83
233
 
84
- it 'throws RangeError if given date not included in the year' do
85
- expect { @c.day(2000, 1, 1) }.to raise_error RangeError
234
+ describe "date not included in the calendar's year" do
235
+ it 'throws RangeError' do
236
+ expect { @c.day(2000, 1, 1) }.to raise_exception RangeError
86
237
  end
238
+ end
87
239
 
88
- describe 'temporale features' do
89
- describe 'season' do
90
- it 'detects Advent correctly' do
91
- @c.day(2013, 12, 10).season.should eq :advent
240
+ describe 'date before system effectiveness' do
241
+ it 'throws RangeError' do
242
+ c = described_class.new 1969
243
+ expect { c.day(1969, 12, 20) }.to raise_exception RangeError
244
+ end
245
+ end
246
+
247
+ describe 'temporale features' do
248
+ describe 'season' do
249
+ it 'detects Advent correctly' do
250
+ expect(@c.day(2013, 12, 10).season).to eq CR::Seasons::ADVENT
251
+ end
252
+ end
253
+
254
+ describe 'week of the season' do
255
+ describe 'Advent' do
256
+ it 'sets Advent week correctly' do
257
+ expect(@c.day(2013, 12, 10).season_week).to eq 2
258
+ expect(@c.day(2013, 12, 15).season_week).to eq 3
92
259
  end
93
260
  end
94
261
 
95
- describe 'week of the season' do
96
- describe 'Advent' do
97
- it 'sets Advent week correctly' do
98
- expect(@c.day(2013, 12, 10).season_week).to eq 2
99
- expect(@c.day(2013, 12, 15).season_week).to eq 3
100
- end
262
+ describe 'Christmas' do
263
+ it 'days before the first Sunday are week 0' do
264
+ expect(@c.day(2013, 12, 25).season_week).to eq 0
101
265
  end
102
266
 
103
- describe 'Christmas' do
104
- it 'days before the first Sunday are week 0' do
105
- expect(@c.day(2013, 12, 25).season_week).to eq 0
106
- end
267
+ it 'first Sunday starts week 1' do
268
+ expect(@c.day(2013, 12, 29).season_week).to eq 1
269
+ end
270
+ end
107
271
 
108
- it 'first Sunday starts week 1' do
109
- expect(@c.day(2013, 12, 29).season_week).to eq 1
110
- end
272
+ describe 'Lent' do
273
+ it 'Ash Wednesday is week 0' do
274
+ expect(@c.day(2014, 3, 5).season_week).to eq 0
111
275
  end
276
+ end
112
277
 
113
- describe 'Lent' do
114
- it 'Ash Wednesday is week 0' do
115
- expect(@c.day(2014, 3, 5).season_week).to eq 0
116
- end
278
+ describe 'Easter' do
279
+ it 'Easter Sunday opens week 1' do
280
+ expect(@c.day(2014, 4, 20).season_week).to eq 1
117
281
  end
282
+ end
118
283
 
119
- describe 'Easter' do
120
- it 'Easter Sunday opens week 1' do
121
- expect(@c.day(2014, 4, 20).season_week).to eq 1
122
- end
284
+ describe 'Ordinary time' do
285
+ it 'Monday after Baptism of the Lord is week 1' do
286
+ expect(@c.day(2014, 1, 13).season_week).to eq 1
123
287
  end
124
288
 
125
- describe 'Ordinary time' do
126
- it 'Monday after Baptism of the Lord is week 1' do
127
- expect(@c.day(2014, 1, 13).season_week).to eq 1
289
+ describe 'after Pentecost' do
290
+ it '2014' do
291
+ c = described_class.new(2013)
292
+ expect(c.day(2014, 6, 9).season_week).to eq 10
128
293
  end
129
294
 
130
- describe 'after Pentecost' do
131
- it '2014' do
132
- c = described_class.new(2013)
133
- expect(c.day(2014, 6, 9).season_week).to eq 10
134
- end
135
-
136
- it '2015' do
137
- c = described_class.new(2014)
138
- expect(c.day(2015, 5, 25).season_week).to eq 8
139
- end
295
+ it '2015' do
296
+ c = described_class.new(2014)
297
+ expect(c.day(2015, 5, 25).season_week).to eq 8
298
+ end
140
299
 
141
- it '2016' do
142
- c = described_class.new(2015)
143
- expect(c.day(2016, 5, 16).season_week).to eq 7
144
- end
300
+ it '2016' do
301
+ c = described_class.new(2015)
302
+ expect(c.day(2016, 5, 16).season_week).to eq 7
303
+ end
145
304
 
146
- it '2017' do
147
- c = described_class.new(2016)
148
- expect(c.day(2017, 6, 5).season_week).to eq 9
149
- end
305
+ it '2017' do
306
+ c = described_class.new(2016)
307
+ expect(c.day(2017, 6, 5).season_week).to eq 9
308
+ end
150
309
 
151
- describe 'works correctly for the whole week' do
152
- describe 'first' do
153
- Date.new(2014, 6, 9).upto(Date.new(2014, 6, 14)) do |date|
154
- it date do
155
- expect(@c.day(date).season_week).to eq 10
156
- end
310
+ describe 'works correctly for the whole week' do
311
+ describe 'first' do
312
+ Date.new(2014, 6, 9).upto(Date.new(2014, 6, 14)) do |date|
313
+ it date do
314
+ expect(@c.day(date).season_week).to eq 10
157
315
  end
158
316
  end
317
+ end
159
318
 
160
- describe 'second' do
161
- Date.new(2014, 6, 15).upto(Date.new(2014, 6, 21)) do |date|
162
- it date do
163
- expect(@c.day(date).season_week).to eq 11
164
- end
319
+ describe 'second' do
320
+ Date.new(2014, 6, 15).upto(Date.new(2014, 6, 21)) do |date|
321
+ it date do
322
+ expect(@c.day(date).season_week).to eq 11
165
323
  end
166
324
  end
325
+ end
167
326
 
168
- describe 'second last' do
169
- Date.new(2014, 11, 16).upto(Date.new(2014, 11, 22)) do |date|
170
- it date do
171
- expect(@c.day(date).season_week).to eq 33
172
- end
327
+ describe 'second last' do
328
+ Date.new(2014, 11, 16).upto(Date.new(2014, 11, 22)) do |date|
329
+ it date do
330
+ expect(@c.day(date).season_week).to eq 33
173
331
  end
174
332
  end
333
+ end
175
334
 
176
- describe 'last' do
177
- Date.new(2014, 11, 23).upto(Date.new(2014, 11, 29)) do |date|
178
- it date do
179
- expect(@c.day(date).season_week).to eq 34
180
- end
335
+ describe 'last' do
336
+ Date.new(2014, 11, 23).upto(Date.new(2014, 11, 29)) do |date|
337
+ it date do
338
+ expect(@c.day(date).season_week).to eq 34
181
339
  end
182
340
  end
183
341
  end
@@ -185,115 +343,414 @@ describe CR::Calendar do
185
343
  end
186
344
  end
187
345
  end
346
+ end
347
+
348
+ describe 'Temporale x Sanctorale resolution' do
349
+ before :all do
350
+ @s = CR::Data::GENERAL_ROMAN_ENGLISH.load
351
+ @c = described_class.new(2013, @s).freeze
352
+ end
353
+
354
+ it '"empty" day results in a ferial' do
355
+ d = @c.day(7, 2)
356
+ expect(d.celebrations.size).to eq 1
357
+ expect(d.celebrations[0].rank).to eq CR::Ranks::FERIAL
358
+ end
359
+
360
+ it 'sanctorale feast' do
361
+ d = @c.day(7, 3)
362
+ expect(d.celebrations.size).to eq 1
363
+ expect(d.celebrations[0].rank).to eq CR::Ranks::FEAST_GENERAL
364
+ expect(d.celebrations[0].title).to include 'Thomas'
365
+ end
366
+
367
+ it 'optional memorial does not suppress ferial' do
368
+ d = @c.day(7, 14)
369
+ expect(d.celebrations.size).to eq 2
370
+
371
+ expect(d.celebrations[0].rank).to eq CR::Ranks::FERIAL
372
+
373
+ expect(d.celebrations[1].rank).to eq CR::Ranks::MEMORIAL_OPTIONAL
374
+ expect(d.celebrations[1].title).to include 'Lellis'
375
+ end
376
+
377
+ it 'obligatory memorial does suppress ferial' do
378
+ d = @c.day(1, 17)
379
+ expect(d.celebrations.size).to eq 1
380
+
381
+ expect(d.celebrations[0].rank).to eq CR::Ranks::MEMORIAL_GENERAL
382
+ end
383
+
384
+ it 'memorial in Lent becomes mere commemoration' do
385
+ d = @c.day(4, 2)
386
+ expect(d.celebrations.size).to eq 2
387
+
388
+ comm = d.celebrations[1]
389
+ expect(comm.rank).to eq CR::Ranks::COMMEMORATION
390
+ expect(comm.title).to eq 'Saint Francis of Paola, hermit'
391
+ end
392
+
393
+ it 'Sunday suppresses feast' do
394
+ san = CR::Sanctorale.new
395
+
396
+ d = Date.new 2015, 6, 28
397
+ expect(d).to be_sunday # ensure
398
+ san.add d.month, d.day, CR::Celebration.new('St. None, programmer', CR::Ranks::FEAST_GENERAL)
399
+
400
+ c = described_class.new 2014, san
401
+
402
+ celebs = c.day(d).celebrations
403
+ expect(celebs.size).to eq 1
404
+ expect(celebs[0].rank).to eq CR::Ranks::SUNDAY_UNPRIVILEGED
405
+ end
406
+
407
+ it 'suppressed fictive solemnity is transferred' do
408
+ san = CR::Sanctorale.new
409
+
410
+ d = CR::Temporale.new(2014).good_friday
411
+ st_none = CR::Celebration.new('St. None, abbot, founder of the Order of Programmers (OProg)', CR::Ranks::SOLEMNITY_PROPER)
412
+ san.add d.month, d.day, st_none
413
+
414
+ c = described_class.new 2014, san
415
+
416
+ # Good Friday suppresses the solemnity
417
+ celebs = c.day(d).celebrations
418
+ expect(celebs.size).to eq 1
419
+ expect(celebs[0]).to eq celfactory.good_friday
420
+
421
+ # it is transferred on a day after the Easter octave
422
+ d = c.temporale.easter_sunday + 8
423
+ celebs = c.day(d).celebrations
424
+ expect(celebs.size).to eq 1
425
+ expect(celebs[0]).to eq st_none
426
+ end
427
+
428
+ it 'transfer of suppressed Annunciation (real world example)' do
429
+ c = described_class.new 2015, @s
430
+
431
+ d = Date.new(2016, 3, 25)
432
+
433
+ # Good Friday suppresses the solemnity
434
+ celebs = c.day(d).celebrations
435
+ expect(celebs.size).to eq 1
436
+ expect(celebs[0]).to eq celfactory.good_friday
437
+
438
+ # it is transferred on a day after the Easter octave
439
+ d = c.temporale.easter_sunday + 8
440
+ celebs = c.day(d).celebrations
441
+ expect(celebs.size).to eq 1
442
+ expect(celebs[0].title).to eq 'Annunciation of the Lord'
443
+ end
444
+
445
+ describe 'collision of Immaculate Heart with another obligatory memorial' do
446
+ let(:year) { 2002 }
447
+ let(:c) { described_class.new year, @s }
448
+ let(:date) { Date.new(2003, 6, 28) }
449
+
450
+ it 'makes both memorials optional' do
451
+ # make sure
452
+ expect(c.sanctorale.get(date).first.rank).to eq CR::Ranks::MEMORIAL_GENERAL
453
+ expect(c.temporale.get(date).rank).to eq CR::Ranks::MEMORIAL_GENERAL
454
+
455
+ celebrations = c.day(date).celebrations
456
+ expect(celebrations.size).to eq 3
457
+
458
+ expect(celebrations[0].rank).to eq CR::Ranks::FERIAL
459
+ expect(celebrations[1..-1].collect(&:rank).uniq)
460
+ .to eq([CR::Ranks::MEMORIAL_OPTIONAL])
461
+ end
462
+ end
463
+
464
+ describe 'collision of Mary, Mother of the Church with another obligatory memorial' do
465
+ let(:year) { 2019 }
466
+ let(:c) { described_class.new year, @s }
467
+ let(:date) { Date.new(2020, 6, 1) }
188
468
 
189
- describe 'Temporale x Sanctorale resolution' do
190
- before :all do
191
- @s = CR::Sanctorale.new
192
- loader = CR::SanctoraleLoader.new
193
- loader.load_from_file(File.join(File.dirname(__FILE__), '..', 'data', 'universal-en.txt'), @s)
194
- @c = described_class.new 2013, @s
469
+ it 'the Marian memorial takes precedence' do
470
+ expect(c.sanctorale.get(date).first.rank).to eq CR::Ranks::MEMORIAL_GENERAL
471
+ expect(c.temporale.get(date).rank).to eq CR::Ranks::MEMORIAL_GENERAL
472
+
473
+ celebrations = c.day(date).celebrations
474
+ expect(celebrations.size).to eq 1
475
+
476
+ expect(celebrations[0].symbol).to eq :mother_of_church
477
+ end
478
+ end
479
+ end
480
+
481
+ describe 'Saturday memorial' do
482
+ let(:sanctorale) { double(CR::Sanctorale, solemnities: {}) }
483
+ let(:calendar) { described_class.new(2013, sanctorale) }
484
+ let(:celebrations) { calendar.day(date).celebrations }
485
+
486
+ describe 'free Saturday in Ordinary Time' do
487
+ let(:date) { Date.new(2014, 8, 16) }
488
+
489
+ before :each do
490
+ allow(sanctorale).to receive(:[]).and_return([])
491
+ end
492
+
493
+ it 'offers Saturday memorial' do
494
+ expect(celebrations)
495
+ .to include(an_object_eq_to(celfactory.saturday_memorial_bvm))
496
+ end
497
+ end
498
+
499
+ describe 'Saturday in Ordinary Time with optional memorial(s)' do
500
+ let(:date) { Date.new(2014, 8, 23) }
501
+
502
+ before :each do
503
+ memorial = CR::Celebration.new('', CR::Ranks::MEMORIAL_OPTIONAL)
504
+ allow(sanctorale).to receive(:[]).and_return([memorial])
195
505
  end
196
506
 
197
- it '"empty" day results in a ferial' do
198
- d = @c.day(7, 2)
199
- expect(d.celebrations.size).to eq 1
200
- expect(d.celebrations[0].rank).to eq CR::Ranks::FERIAL
507
+ it 'offers Saturday memorial' do
508
+ expect(celebrations)
509
+ .to include(an_object_eq_to(celfactory.saturday_memorial_bvm))
201
510
  end
511
+ end
202
512
 
203
- it 'sanctorale feast' do
204
- d = @c.day(7, 3)
205
- expect(d.celebrations.size).to eq 1
206
- expect(d.celebrations[0].rank).to eq CR::Ranks::FEAST_GENERAL
207
- expect(d.celebrations[0].title).to include 'Thomas'
513
+ describe 'non-free Saturday in Ordinary Time' do
514
+ let(:date) { Date.new(2014, 9, 13) }
515
+
516
+ before :each do
517
+ memorial = CR::Celebration.new('', CR::Ranks::MEMORIAL_GENERAL)
518
+ allow(sanctorale).to receive(:[]).and_return([memorial])
208
519
  end
209
520
 
210
- it 'optional memorial does not suppress ferial' do
211
- d = @c.day(7, 14)
212
- expect(d.celebrations.size).to eq 2
521
+ it 'does not offer Saturday memorial' do
522
+ expect(celebrations)
523
+ .not_to include(an_object_eq_to(celfactory.saturday_memorial_bvm))
524
+ end
525
+ end
526
+
527
+ describe 'free Saturday in another season' do
528
+ let(:date) { Date.new(2013, 12, 14) }
213
529
 
214
- expect(d.celebrations[0].rank).to eq CR::Ranks::FERIAL
530
+ before :each do
531
+ allow(sanctorale).to receive(:[]).and_return([])
532
+ end
215
533
 
216
- expect(d.celebrations[1].rank).to eq CR::Ranks::MEMORIAL_OPTIONAL
217
- expect(d.celebrations[1].title).to include 'Lellis'
534
+ it 'does not offer Saturday memorial' do
535
+ expect(celebrations)
536
+ .not_to include(an_object_eq_to(celfactory.saturday_memorial_bvm))
218
537
  end
538
+ end
539
+ end
540
+
541
+ describe 'Vespers' do
542
+ let(:saturday) { Date.new(2014, 1, 4) }
543
+ let(:year) { 2013 }
544
+ let(:calendar) { described_class.new(year) }
545
+
546
+ describe 'not opted in' do
547
+ it 'does not fill Vespers' do
548
+ day = calendar.day saturday
549
+ expect(day.vespers).to be nil
550
+ end
551
+ end
219
552
 
220
- it 'obligate memorial does suppress ferial' do
221
- d = @c.day(1, 17)
222
- expect(d.celebrations.size).to eq 1
553
+ describe 'opted in by constructor argument' do
554
+ let(:calendar) { described_class.new(year, nil, nil, vespers: true) }
223
555
 
224
- expect(d.celebrations[0].rank).to eq CR::Ranks::MEMORIAL_GENERAL
556
+ it 'fills Vespers' do
557
+ day = calendar.day saturday
558
+ expect(day.vespers).to be_a CR::Celebration
225
559
  end
226
560
 
227
- it 'Sunday suppresses feast' do
228
- san = CR::Sanctorale.new
561
+ describe 'but the day has not Vespers from following' do
562
+ it 'does not fill Vespers' do
563
+ friday = saturday - 1
564
+ day = calendar.day friday
565
+ expect(day.vespers).to be nil
566
+ end
567
+ end
568
+ end
229
569
 
230
- d = Date.new 2015, 6, 28
231
- expect(d).to be_sunday # ensure
232
- san.add d.month, d.day, CR::Celebration.new('St. None, programmer', CR::Ranks::FEAST_GENERAL)
570
+ describe 'opted in by argument' do
571
+ it 'fills Vespers' do
572
+ day = calendar.day saturday, vespers: true
573
+ expect(day.vespers).to be_a CR::Celebration
574
+ end
575
+ end
233
576
 
234
- c = described_class.new 2014, san
577
+ describe 'first Vespers of' do
578
+ let(:sanctorale) { CR::Data::GENERAL_ROMAN_ENGLISH.load }
579
+ let(:calendar) { described_class.new(year, sanctorale, nil, vespers: true) }
235
580
 
236
- celebs = c.day(d).celebrations
237
- expect(celebs.size).to eq 1
238
- expect(celebs[0].rank).to eq CR::Ranks::SUNDAY_UNPRIVILEGED
581
+ describe 'a Sunday' do
582
+ it 'has first Vespers' do
583
+ day = calendar.day saturday
584
+ expect(day.vespers.rank).to eq CR::Ranks::SUNDAY_UNPRIVILEGED
585
+ end
239
586
  end
240
587
 
241
- it 'suppressed fictive solemnity is transferred' do
242
- san = CR::Sanctorale.new
588
+ describe 'a solemnity' do
589
+ let(:testing_solemnity) do
590
+ CR::Celebration.new(
591
+ 'Testing solemnity',
592
+ CR::Ranks::SOLEMNITY_GENERAL,
593
+ CR::Colours::WHITE,
594
+ :test
595
+ )
596
+ end
597
+
598
+ it 'has first Vespers' do
599
+ day = calendar.day(Date.new(2014, 11, 1) - 1)
600
+ expect(day.vespers.rank).to be CR::Ranks::SOLEMNITY_GENERAL
601
+ end
243
602
 
244
- d = CR::Temporale.new(2014).good_friday
245
- st_none = CR::Celebration.new('St. None, abbot, founder of the Order of Programmers (OProg)', CR::Ranks::SOLEMNITY_PROPER)
246
- san.add d.month, d.day, st_none
603
+ describe 'clash with Sunday Vespers' do
604
+ it 'wins over Sunday' do
605
+ sunday = Date.new(2014, 8, 17)
606
+ expect(sunday).to be_sunday # make sure
607
+ sanctorale.replace(8, 17, [testing_solemnity])
247
608
 
248
- c = described_class.new 2014, san
609
+ day = calendar.day(sunday - 1)
610
+ expect(day.vespers.symbol).to eq :test
611
+ end
612
+ end
249
613
 
250
- # Good Friday suppresses the solemnity
251
- celebs = c.day(d).celebrations
252
- expect(celebs.size).to eq 1
253
- expect(celebs[0].rank).to eq CR::Ranks::TRIDUUM
254
- expect(celebs[0].title).to eq 'Friday of the Passion of the Lord'
614
+ describe 'clash with second Vespers of another solemnity' do
615
+ it "the day's Vespers win" do
616
+ assumption = Date.new(2014, 8, 15)
617
+ sanctorale.replace(8, 16, [testing_solemnity])
255
618
 
256
- # it is transferred on a day after the Easter octave
257
- d = c.temporale.easter_sunday + 8
258
- celebs = c.day(d).celebrations
259
- expect(celebs.size).to eq 1
260
- expect(celebs[0]).to eq st_none
619
+ day = calendar.day(assumption)
620
+ expect(day.celebrations.first.rank).to be CR::Ranks::SOLEMNITY_GENERAL
621
+ expect(day.vespers).to be nil
622
+
623
+ # make sure
624
+ next_day = calendar.day(assumption + 1)
625
+ expect(next_day.celebrations.first).to be testing_solemnity
626
+ end
627
+ end
261
628
  end
262
629
 
263
- it 'transfer of suppressed Annunciation (real world example)' do
264
- c = described_class.new 2015, @s
630
+ describe 'feast of the Lord' do
631
+ describe 'not falling on a Sunday' do
632
+ it 'does not have first Vespers' do
633
+ calendar = described_class.new(2015, sanctorale, nil, vespers: true)
634
+ presentation = Date.new(2016, 2, 2)
635
+ expect(presentation).not_to be_sunday # make sure
265
636
 
266
- d = Date.new(2016, 3, 25)
637
+ day = calendar.day(presentation - 1)
638
+ expect(day.vespers).to be nil
639
+ end
640
+ end
267
641
 
268
- # Good Friday suppresses the solemnity
269
- celebs = c.day(d).celebrations
270
- expect(celebs.size).to eq 1
271
- expect(celebs[0].rank).to eq CR::Ranks::TRIDUUM
272
- expect(celebs[0].title).to eq 'Friday of the Passion of the Lord'
642
+ describe 'falling on a Sunday' do
643
+ it 'has first Vespers' do
644
+ presentation = Date.new(2014, 2, 2)
645
+ expect(presentation).to be_sunday # make sure
273
646
 
274
- # it is transferred on a day after the Easter octave
275
- d = c.temporale.easter_sunday + 8
276
- celebs = c.day(d).celebrations
277
- expect(celebs.size).to eq 1
278
- expect(celebs[0].title).to eq 'Annunciation of the Lord'
647
+ day = calendar.day(presentation - 1)
648
+ expect(day.vespers.rank).to be CR::Ranks::FEAST_LORD_GENERAL
649
+ end
650
+ end
651
+ end
652
+
653
+ # this group contains both days having and days not having
654
+ # first Vespers: special care must be taken
655
+ describe 'primary liturgical days' do
656
+ describe 'Ash Wednesday' do
657
+ it 'does not have first Vespers' do
658
+ aw = CR::Temporale::Dates.ash_wednesday year
659
+ day = calendar.day(aw - 1)
660
+ expect(day.vespers).to be nil
661
+ end
662
+ end
663
+
664
+ describe 'Nativity' do
665
+ it 'has first Vespers' do
666
+ day = calendar.day Date.new(2013, 12, 24)
667
+ expect(day.vespers).to eq celfactory.nativity
668
+ end
669
+ end
670
+
671
+ describe 'Epiphany' do
672
+ it 'has first Vespers' do
673
+ day = calendar.day Date.new(2014, 1, 5)
674
+ expect(day.vespers.rank).to be CR::Ranks::PRIMARY
675
+ expect(day.vespers.symbol).to be :epiphany
676
+ end
677
+ end
678
+
679
+ describe 'Palm Sunday' do
680
+ it 'has first Vespers' do
681
+ ps = CR::Temporale::Dates.palm_sunday year
682
+ day = calendar.day(ps - 1)
683
+ expect(day.vespers).to eq celfactory.palm_sunday
684
+ end
685
+ end
686
+
687
+ describe 'day in the Holy week' do
688
+ it 'does not have first Vespers' do
689
+ tuesday = CR::Temporale::Dates.palm_sunday(year) + 2
690
+ day = calendar.day(tuesday - 1)
691
+ expect(day.vespers).to be nil
692
+ end
693
+ end
694
+
695
+ describe 'Good Friday' do
696
+ it 'does not have first Vespers' do
697
+ gf = CR::Temporale::Dates.good_friday(year)
698
+ day = calendar.day(gf - 1)
699
+ expect(day.vespers).to be nil
700
+ end
701
+ end
702
+
703
+ describe 'Easter' do
704
+ it 'has first Vespers' do
705
+ es = CR::Temporale::Dates.easter_sunday year
706
+ day = calendar.day(es - 1)
707
+ expect(day.vespers).to eq celfactory.easter_sunday
708
+ end
709
+ end
710
+
711
+ describe 'day in Easter octave' do
712
+ it 'does not have first Vespers' do
713
+ tuesday = CR::Temporale::Dates.easter_sunday(year) + 2
714
+ day = calendar.day(tuesday - 1)
715
+ expect(day.vespers).to be nil
716
+ end
717
+ end
718
+ end
719
+
720
+ describe 'edge cases' do
721
+ describe 'First Sunday of Advent' do
722
+ it 'has first Vespers (and does not cause an exception)' do
723
+ sunday = CR::Temporale::Dates.first_advent_sunday(year + 1)
724
+ day = calendar.day(sunday - 1)
725
+ expect(day.vespers).to eq celfactory.first_advent_sunday
726
+ end
727
+ end
279
728
  end
280
729
  end
281
730
  end
731
+ end
282
732
 
283
- describe '#pred' do
284
- it 'returns a calendar for the previous year' do
285
- new_cal = @c.pred
286
- expect(new_cal.year).to eq(@c.year - 1)
287
- expect(new_cal.sanctorale).to eq (@c.sanctorale)
733
+ # only s small subset of the Sanctorale public interface
734
+ # is used by Calendar. These specs show how small it is.
735
+ describe 'required sanctorale interface' do
736
+ class HardcodedEmptySanctorale
737
+ def solemnities
738
+ {} # no solemnities
288
739
  end
289
- end
290
740
 
291
- describe '#succ' do
292
- it 'returns a calendar for the subsequent year' do
293
- new_cal = @c.succ
294
- expect(new_cal.year).to eq(@c.year + 1)
295
- expect(new_cal.sanctorale).to eq (@c.sanctorale)
741
+ def [](date)
742
+ [] # no celebrations
296
743
  end
297
744
  end
745
+
746
+ let(:year) { 2000 }
747
+ let(:sanctorale) { HardcodedEmptySanctorale.new }
748
+ let(:calendar) { described_class.new(year, sanctorale) }
749
+
750
+ it 'comprises only two methods' do
751
+ expect do
752
+ calendar[Date.new(year, 12, 20)]
753
+ end.not_to raise_exception
754
+ end
298
755
  end
299
756
  end