calendarium-romanum 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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