calendarium-romanum 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ module CalendariumRomanum
2
+
3
+ # Resolves transfers of solemnities.
4
+ class Transfers
5
+ def initialize(temporale, sanctorale)
6
+ @transferred = {}
7
+
8
+ dates = sanctorale.solemnities.keys.collect do |abstract_date|
9
+ temporale.concretize_abstract_date abstract_date
10
+ end.sort
11
+
12
+ dates.each do |date|
13
+ tc = temporale.get(date)
14
+ next unless tc.solemnity?
15
+
16
+ sc = sanctorale.get(date)
17
+ next unless sc.size == 1 && sc.first.solemnity?
18
+
19
+ loser = [tc, sc.first].sort_by(&:rank).first
20
+
21
+ transfer_to = date
22
+ begin
23
+ transfer_to = transfer_to.succ
24
+ end until valid_destination?(transfer_to, temporale, sanctorale)
25
+ @transferred[transfer_to] = loser
26
+ end
27
+ end
28
+
29
+ def get(date)
30
+ @transferred[date]
31
+ end
32
+
33
+ private
34
+
35
+ def valid_destination?(day, temporale, sanctorale)
36
+ return false if temporale.get(day).rank >= Ranks::FEAST_PROPER
37
+
38
+ sc = sanctorale.get(day)
39
+ return false if sc.size > 0 && sc.first.rank >= Ranks::FEAST_PROPER
40
+
41
+ true
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ module CalendariumRomanum
2
+
3
+ module Util
4
+
5
+ # Abstract superclass for date enumerators.
6
+ class DateEnumerator
7
+ include Enumerable
8
+
9
+ def each
10
+ d = @start
11
+ begin
12
+ yield d
13
+ d = d.succ
14
+ end until enumeration_over? d
15
+ end
16
+
17
+ def enumeration_over?(date)
18
+ @start.send(@prop) != date.send(@prop)
19
+ end
20
+
21
+ alias_method :each_day, :each
22
+ end
23
+
24
+ # enumerates days of a year
25
+ class Year < DateEnumerator
26
+ def initialize(year)
27
+ @start = Date.new year, 1, 1
28
+ @prop = :year
29
+ end
30
+ end
31
+
32
+ # enumerates days of a month
33
+ class Month < DateEnumerator
34
+ def initialize(year, month)
35
+ @start = Date.new year, month, 1
36
+ @prop = :month
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe AbstractDate do
4
+ AD = AbstractDate
5
+
6
+ describe '.new' do
7
+ describe 'fails on invalid' do
8
+ it 'month' do
9
+ expect do
10
+ AD.new(13, 1)
11
+ end.to raise_exception /Invalid month/
12
+ end
13
+
14
+ it 'day' do
15
+ expect do
16
+ AD.new(1, 32)
17
+ end.to raise_exception /Invalid day/
18
+ end
19
+
20
+ it 'day of month' do
21
+ expect do
22
+ AD.new(2, 30)
23
+ end.to raise_exception /Invalid day/
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#<' do
29
+ it { expect(AD.new(1, 1)).to be < AD.new(1, 2) }
30
+ it { expect(AD.new(1, 1)).to be < AD.new(2, 1) }
31
+ it { expect(AD.new(1, 1)).not_to be < AD.new(1, 1) }
32
+ end
33
+
34
+ describe '#==' do
35
+ it { expect(AD.new(1, 1)).to be == AD.new(1, 1) }
36
+ it { expect(AD.new(1, 1)).not_to be == AD.new(1, 2) }
37
+ end
38
+
39
+ describe 'as a Hash key' do
40
+ it 'different objects with the same values are considered same key' do
41
+ h = {AD.new(1, 1) => 1}
42
+ expect(h).to have_key AD.new(1, 1)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,217 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Calendar do
4
+
5
+ describe 'core functions' do
6
+ before :all do
7
+ @c = Calendar.new 2013
8
+ end
9
+
10
+ describe '#==' do
11
+ it 'considers calendars with the same year same' do
12
+ Calendar.new(2014).should == Calendar.new(2014)
13
+ end
14
+
15
+ it 'considers calendars with different year different' do
16
+ Calendar.new(2014).should_not == Calendar.new(2010)
17
+ end
18
+ end
19
+
20
+ describe '#lectionary' do
21
+ it 'detects correctly' do
22
+ Calendar.new(2014).lectionary.should eq :B
23
+ Calendar.new(2013).lectionary.should eq :A
24
+ Calendar.new(2012).lectionary.should eq :C
25
+ end
26
+ end
27
+
28
+ describe '#ferial_lectionary' do
29
+ it 'detects correctly' do
30
+ Calendar.new(2014).ferial_lectionary.should eq 1
31
+ Calendar.new(2013).ferial_lectionary.should eq 2
32
+ end
33
+ end
34
+
35
+ describe '.for_day' do
36
+ it 'continues the previous year\'s calendar in summer' do
37
+ Calendar.for_day(Date.new(2014, 6, 9)).should eq Calendar.new(2013)
38
+ end
39
+
40
+ it 'provides the current year\'s calendar in December' do
41
+ Calendar.for_day(Date.new(2014, 12, 20)).should eq Calendar.new(2014)
42
+ end
43
+ end
44
+
45
+ describe '#day' do
46
+ it 'returns a Day' do
47
+ @c.day(2013, 12, 10).should be_a Day
48
+ end
49
+
50
+ it 'inserts correct year if not given' do
51
+ expect(@c.day(12, 10).date).to eq Date.new(2013, 12, 10)
52
+ end
53
+
54
+ it 'throws RangeError if given date not included in the year' do
55
+ expect { @c.day(2000, 1, 1) }.to raise_error RangeError
56
+ end
57
+
58
+ describe 'temporale features' do
59
+ describe 'season' do
60
+ it 'detects Advent correctly' do
61
+ @c.day(2013, 12, 10).season.should eq :advent
62
+ end
63
+ end
64
+
65
+ describe 'week of the season' do
66
+ describe 'Advent' do
67
+ it 'sets Advent week correctly' do
68
+ expect(@c.day(2013, 12, 10).season_week).to eq 2
69
+ expect(@c.day(2013, 12, 15).season_week).to eq 3
70
+ end
71
+ end
72
+
73
+ describe 'Christmas' do
74
+ it 'days before the first Sunday are week 0' do
75
+ expect(@c.day(2013, 12, 25).season_week).to eq 0
76
+ end
77
+
78
+ it 'first Sunday starts week 1' do
79
+ expect(@c.day(2013, 12, 29).season_week).to eq 1
80
+ end
81
+ end
82
+
83
+ describe 'Lent' do
84
+ it 'Ash Wednesday is week 0' do
85
+ expect(@c.day(2014, 3, 5).season_week).to eq 0
86
+ end
87
+ end
88
+
89
+ describe 'Easter' do
90
+ it 'Easter Sunday opens week 1' do
91
+ expect(@c.day(2014, 4, 20).season_week).to eq 1
92
+ end
93
+ end
94
+
95
+ describe 'Ordinary time' do
96
+ it 'Monday after Baptism of the Lord is week 1' do
97
+ expect(@c.day(2014, 1, 13).season_week).to eq 1
98
+ end
99
+
100
+ it 'Continue after Pentecost' do
101
+ expect(@c.day(2014, 6, 9).season_week).to eq 10
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'Temporale x Sanctorale resolution' do
108
+ before :all do
109
+ @s = Sanctorale.new
110
+ loader = SanctoraleLoader.new
111
+ loader.load_from_file(@s, File.join(File.dirname(__FILE__), '..', 'data', 'universal-en.txt'))
112
+ @c = Calendar.new 2013, @s
113
+ end
114
+
115
+ it '"empty" day results in a ferial' do
116
+ d = @c.day(7, 2)
117
+ expect(d.celebrations.size).to eq 1
118
+ expect(d.celebrations[0].rank).to eq FERIAL
119
+ end
120
+
121
+ it 'sanctorale feast' do
122
+ d = @c.day(7, 3)
123
+ expect(d.celebrations.size).to eq 1
124
+ expect(d.celebrations[0].rank).to eq FEAST_GENERAL
125
+ expect(d.celebrations[0].title).to include 'Thomas'
126
+ end
127
+
128
+ it 'optional memorial does not suppress ferial' do
129
+ d = @c.day(7, 14)
130
+ expect(d.celebrations.size).to eq 2
131
+
132
+ expect(d.celebrations[0].rank).to eq FERIAL
133
+
134
+ expect(d.celebrations[1].rank).to eq MEMORIAL_OPTIONAL
135
+ expect(d.celebrations[1].title).to include 'Lellis'
136
+ end
137
+
138
+ it 'obligate memorial does suppress ferial' do
139
+ d = @c.day(1, 17)
140
+ expect(d.celebrations.size).to eq 1
141
+
142
+ expect(d.celebrations[0].rank).to eq MEMORIAL_GENERAL
143
+ end
144
+
145
+ it 'Sunday suppresses feast' do
146
+ san = Sanctorale.new
147
+
148
+ d = Date.new 2015, 6, 28
149
+ expect(d).to be_sunday # ensure
150
+ san.add d.month, d.day, Celebration.new('St. None, programmer', FEAST_GENERAL)
151
+
152
+ c = Calendar.new 2014, san
153
+
154
+ celebs = c.day(d).celebrations
155
+ expect(celebs.size).to eq 1
156
+ expect(celebs[0].rank).to eq SUNDAY_UNPRIVILEGED
157
+ end
158
+
159
+ it 'suppressed fictive solemnity is transferred' do
160
+ san = Sanctorale.new
161
+
162
+ d = Temporale.new(2014).good_friday
163
+ st_none = Celebration.new('St. None, abbot, founder of the Order of Programmers (OProg)', SOLEMNITY_PROPER)
164
+ san.add d.month, d.day, st_none
165
+
166
+ c = Calendar.new 2014, san
167
+
168
+ # Good Friday suppresses the solemnity
169
+ celebs = c.day(d).celebrations
170
+ expect(celebs.size).to eq 1
171
+ expect(celebs[0].rank).to eq TRIDUUM
172
+ expect(celebs[0].title).to eq 'Friday of the Passion of the Lord'
173
+
174
+ # it is transferred on a day after the Easter octave
175
+ d = c.temporale.easter_sunday + 8
176
+ celebs = c.day(d).celebrations
177
+ expect(celebs.size).to eq 1
178
+ expect(celebs[0]).to eq st_none
179
+ end
180
+
181
+ it 'transfer of suppressed Annunciation (real world example)' do
182
+ c = Calendar.new 2015, @s
183
+
184
+ d = Date.new(2016, 3, 25)
185
+
186
+ # Good Friday suppresses the solemnity
187
+ celebs = c.day(d).celebrations
188
+ expect(celebs.size).to eq 1
189
+ expect(celebs[0].rank).to eq TRIDUUM
190
+ expect(celebs[0].title).to eq 'Friday of the Passion of the Lord'
191
+
192
+ # it is transferred on a day after the Easter octave
193
+ d = c.temporale.easter_sunday + 8
194
+ celebs = c.day(d).celebrations
195
+ expect(celebs.size).to eq 1
196
+ expect(celebs[0].title).to eq 'Annunciation of the Lord'
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#pred' do
202
+ it 'returns a calendar for the previous year' do
203
+ new_cal = @c.pred
204
+ expect(new_cal.year).to eq(@c.year - 1)
205
+ expect(new_cal.sanctorale).to eq (@c.sanctorale)
206
+ end
207
+ end
208
+
209
+ describe '#succ' do
210
+ it 'returns a calendar for the subsequent year' do
211
+ new_cal = @c.succ
212
+ expect(new_cal.year).to eq(@c.year + 1)
213
+ expect(new_cal.sanctorale).to eq (@c.sanctorale)
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,10 @@
1
+ # expectations concerning the Ruby implementation
2
+ describe Date do
3
+ describe 'substraction' do
4
+ it 'returns Rational' do
5
+ date_diff = Date.new(2013,5,5) - Date.new(2013,5,1)
6
+ expect(date_diff).to be_a Rational
7
+ expect(date_diff.numerator).to eq 4
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Rank do
4
+ describe 'comparison' do
5
+ it 'memorial x ferial' do
6
+ expect(MEMORIAL_GENERAL).to be > FERIAL
7
+ end
8
+ end
9
+
10
+ describe '[]' do
11
+ it 'has all existing instances indexed by rank number' do
12
+ expect(Ranks[1.1]).to eq Ranks::TRIDUUM
13
+ end
14
+ end
15
+
16
+ describe '#<' do
17
+ it { expect(Ranks[1.2]).to be < Ranks[1.1] }
18
+ it { expect(Ranks[1.1]).not_to be < Ranks[1.2] }
19
+ end
20
+
21
+ describe '#>' do
22
+ it { expect(Ranks[1.1]).to be > Ranks[1.2] }
23
+ it { expect(Ranks[1.2]).not_to be > Ranks[1.1] }
24
+ end
25
+
26
+ describe '#==' do
27
+ it { expect(Ranks[1.2]).to be == Ranks[1.2] }
28
+ it { expect(Ranks[1.2]).not_to be == Ranks[1.1] }
29
+ end
30
+ end
@@ -0,0 +1,144 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sanctorale do
4
+ before :each do
5
+ @s = Sanctorale.new
6
+ end
7
+
8
+ describe '#get' do
9
+ describe 'for empty day' do
10
+ it 'returns an Array' do
11
+ expect(@s.get(1,3)).to be_an Array
12
+ end
13
+ end
14
+
15
+ describe 'for unempty day' do
16
+ before :each do
17
+ @c = Celebration.new('S. Antonii, abbatis', Ranks::MEMORIAL_GENERAL)
18
+ @s.add 1, 17, @c
19
+ end
20
+
21
+ it 'get by month, day' do
22
+ expect(@s.get(1, 17)).to eq [@c]
23
+ end
24
+
25
+ it 'get by Date' do
26
+ expect(@s.get(Date.new(2014, 1, 17))).to eq [@c]
27
+ end
28
+
29
+ it 'may have more Celebrations for a day' do
30
+ [
31
+ 'S. Fabiani, papae et martyris',
32
+ 'S. Sebastiani, martyris'
33
+ ].each {|t| @s.add 1, 20, Celebration.new(t) }
34
+ expect(@s.get(1, 20).size).to eq 2
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '#add' do
40
+ it 'adds a Celebration to one month only' do
41
+ @s.add 1, 17, Celebration.new('S. Antonii, abbatis', Ranks::MEMORIAL_GENERAL)
42
+ expect(@s.get(2, 17)).to be_empty
43
+ end
44
+
45
+ it 'does not allow month 0' do
46
+ expect { @s.add 0, 1, Celebration.new('S. Nullius') }.to raise_exception RangeError
47
+ end
48
+
49
+ it 'does not allow month higher than 12' do
50
+ expect { @s.add 13, 1, Celebration.new('S. Nullius') }.to raise_exception RangeError
51
+ end
52
+
53
+ it 'adds solemnity to a dedicated container' do
54
+ expect { @s.add 1, 13, Celebration.new('S. Nullius', Ranks::SOLEMNITY_PROPER) }.to change { @s.solemnities.size }.by 1
55
+ end
56
+
57
+ it 'does not add non-solemnity to solemnities' do
58
+ expect { @s.add 1, 13, Celebration.new('S. Nullius') }.not_to change { @s.solemnities.size }
59
+ end
60
+ end
61
+
62
+ describe '#replace' do
63
+ it 'replaces the original celebration(s)' do
64
+ nemo = Celebration.new('S. Nullius')
65
+ nonus = Celebration.new('S. Noni', Ranks::SOLEMNITY_PROPER)
66
+
67
+ @s.add 1, 13, nemo
68
+ @s.replace 1, 13, [nonus]
69
+
70
+ expect(@s.get(1, 13)).to eq [nonus]
71
+ end
72
+
73
+ it 'adds solemnity to a dedicated container' do
74
+ nonus = Celebration.new('S. Noni', Ranks::SOLEMNITY_PROPER)
75
+ expect do
76
+ @s.replace 1, 13, [nonus]
77
+ end.to change { @s.solemnities.size }.by 1
78
+ end
79
+
80
+ it 'removes solemnity' do
81
+ nemo = Celebration.new('S. Nullius', Ranks::SOLEMNITY_PROPER)
82
+ nonus = Celebration.new('S. Noni')
83
+
84
+ @s.add 1, 13, nemo
85
+ expect do
86
+ @s.replace 1, 13, [nonus]
87
+ end.to change { @s.solemnities.size }.by -1
88
+ end
89
+ end
90
+
91
+ describe '#update' do
92
+ before :each do
93
+ @s2 = Sanctorale.new
94
+ end
95
+
96
+ it 'adds entries from the argument to receiver' do
97
+ @s2.add 1, 17, Celebration.new('S. Antonii, abbatis', Ranks::MEMORIAL_GENERAL)
98
+
99
+ expect(@s).to be_empty
100
+ @s.update @s2
101
+ expect(@s.size).to eq 1
102
+ end
103
+
104
+ it 'overwrites eventual previous content of the day' do
105
+ @s.add 1, 17, Celebration.new('S. Antonii, abbatis', Ranks::MEMORIAL_GENERAL)
106
+ cele = Celebration.new('S. Nulius, monachi')
107
+ @s2.add 1, 17, cele
108
+
109
+ @s.update @s2
110
+ expect(@s.get(1, 17)).to eq [cele]
111
+ end
112
+ end
113
+
114
+ describe '#size' do
115
+ it 'knows when the Sanctorale is empty' do
116
+ expect(@s.size).to eq 0
117
+ end
118
+
119
+ it 'knows when there is something' do
120
+ @s.add 1, 17, Celebration.new('S. Antonii, abbatis', Ranks::MEMORIAL_GENERAL)
121
+ expect(@s.size).to eq 1
122
+ end
123
+ end
124
+
125
+ describe '#empty?' do
126
+ it 'is empty at the beginning' do
127
+ expect(@s).to be_empty
128
+ end
129
+
130
+ it 'is never more empty once a record is entered' do
131
+ @s.add 1, 17, Celebration.new('S. Antonii, abbatis', Ranks::MEMORIAL_GENERAL)
132
+ expect(@s).not_to be_empty
133
+ end
134
+ end
135
+
136
+ describe '#each_day' do
137
+ it 'yields each date and corresponding Celebrations' do
138
+ cele = Celebration.new('S. Antonii, abbatis', Ranks::MEMORIAL_GENERAL)
139
+ @s.add 1, 17, cele
140
+
141
+ expect {|block| @s.each_day(&block) }.to yield_with_args(AbstractDate.new(1, 17), [cele])
142
+ end
143
+ end
144
+ end