calendarium-romanum 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  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/calendarium-romanum.gemspec +26 -0
  13. data/doc/data_readme.md +2 -0
  14. data/doc/images/class_diagram.png +0 -0
  15. data/doc/images/class_diagram.puml +44 -0
  16. data/doc/yard_readme.rdoc +76 -0
  17. data/lib/calendarium-romanum/sanctorale.rb +29 -3
  18. data/lib/calendarium-romanum/sanctorale_factory.rb +2 -2
  19. data/lib/calendarium-romanum/sanctorale_loader.rb +3 -3
  20. data/lib/calendarium-romanum/version.rb +2 -2
  21. metadata +17 -26
  22. data/spec/abstract_date_spec.rb +0 -70
  23. data/spec/calendar_spec.rb +0 -756
  24. data/spec/celebration_factory_spec.rb +0 -40
  25. data/spec/celebration_spec.rb +0 -67
  26. data/spec/cli_spec.rb +0 -169
  27. data/spec/colour_spec.rb +0 -22
  28. data/spec/data_spec.rb +0 -46
  29. data/spec/date_parser_spec.rb +0 -68
  30. data/spec/date_spec.rb +0 -61
  31. data/spec/dates_spec.rb +0 -73
  32. data/spec/day_spec.rb +0 -151
  33. data/spec/enum_spec.rb +0 -51
  34. data/spec/i18n_spec.rb +0 -68
  35. data/spec/ordinalizer_spec.rb +0 -44
  36. data/spec/perpetual_calendar_spec.rb +0 -125
  37. data/spec/rank_spec.rb +0 -77
  38. data/spec/readme_spec.rb +0 -58
  39. data/spec/sanctorale_factory_spec.rb +0 -146
  40. data/spec/sanctorale_loader_spec.rb +0 -229
  41. data/spec/sanctorale_spec.rb +0 -281
  42. data/spec/season_spec.rb +0 -22
  43. data/spec/spec_helper.rb +0 -46
  44. data/spec/temporale_spec.rb +0 -693
  45. data/spec/year_spec.rb +0 -25
@@ -0,0 +1,9 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new(:spec)
3
+
4
+ desc 'Run specs with each fully supported locale'
5
+ task :spec_all_locales do
6
+ %w(en la cs fr it).each do |locale|
7
+ sh "LOCALE=#{locale} rake spec"
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ require_relative 'lib/calendarium-romanum/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'calendarium-romanum'
6
+ s.version = CalendariumRomanum::VERSION
7
+ s.date = CalendariumRomanum::RELEASE_DATE.to_s
8
+ s.summary = 'Roman Catholic liturgical calendar computations'
9
+
10
+ s.description = 'calendar computations according to the Roman Catholic liturgical calendar as instituted by MP Mysterii Paschalis of Paul VI (1969).'
11
+
12
+ s.authors = ['Jakub Pavlík']
13
+ s.email = 'jkb.pavlik@gmail.com'
14
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
15
+ f.match(%r{^(test|spec|features)/})
16
+ end
17
+ s.executables = %w(calendariumrom)
18
+ s.homepage = 'http://github.com/igneus/calendarium-romanum'
19
+ s.licenses = ['LGPL-3.0', 'MIT']
20
+
21
+ s.add_dependency 'thor', '~> 0.18'
22
+ s.add_dependency 'i18n', '~> 0.6'
23
+ s.add_dependency 'roman-numerals', '~> 0.3'
24
+ s.add_development_dependency 'rake', '~> 12.0'
25
+ s.add_development_dependency 'rspec', '~> 3.5'
26
+ end
@@ -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}.
@@ -87,10 +87,13 @@ module CalendariumRomanum
87
87
  # @param month [Fixnum]
88
88
  # @param day [Fixnum]
89
89
  # @param celebrations [Array<Celebration>]
90
+ # @param symbol_uniqueness [true|false]
91
+ # allows disabling symbol uniqueness check.
92
+ # Internal feature, not intended for use by client code.
90
93
  # @return [void]
91
94
  # @raise [ArgumentError]
92
95
  # when performing the operation would break the object's invariant
93
- def replace(month, day, celebrations)
96
+ def replace(month, day, celebrations, symbol_uniqueness: true)
94
97
  date = AbstractDate.new(month, day)
95
98
 
96
99
  symbols_without_day = @symbols
@@ -101,7 +104,7 @@ module CalendariumRomanum
101
104
 
102
105
  new_symbols = celebrations.collect(&:symbol).compact
103
106
  duplicate = symbols_without_day.intersection new_symbols
104
- unless duplicate.empty?
107
+ if symbol_uniqueness && !duplicate.empty?
105
108
  raise ArgumentError.new("Attempted to add Celebrations with duplicate symbols #{duplicate.to_a.inspect}")
106
109
  end
107
110
 
@@ -127,8 +130,9 @@ module CalendariumRomanum
127
130
  # @raise (see #replace)
128
131
  def update(other)
129
132
  other.each_day do |date, celebrations|
130
- replace date.month, date.day, celebrations
133
+ replace date.month, date.day, celebrations, symbol_uniqueness: false
131
134
  end
135
+ rebuild_symbols
132
136
  end
133
137
 
134
138
  # Retrieves {Celebration}s for the given date
@@ -205,5 +209,27 @@ module CalendariumRomanum
205
209
  protected
206
210
 
207
211
  attr_reader :days
212
+
213
+ # Builds the registry of celebration symbols anew,
214
+ # raises error if any duplicates are found.
215
+ def rebuild_symbols
216
+ @symbols = Set.new
217
+ duplicates = []
218
+
219
+ @days.each_pair do |date,celebrations|
220
+ celebrations.each do |celebration|
221
+ if @symbols.include?(celebration.symbol) &&
222
+ !duplicates.include?(celebration.symbol)
223
+ duplicates << celebration.symbol
224
+ end
225
+
226
+ @symbols << celebration.symbol
227
+ end
228
+ end
229
+
230
+ unless duplicates.empty?
231
+ raise ArgumentError.new("Duplicate celebration symbols: #{duplicates.inspect}")
232
+ end
233
+ end
208
234
  end
209
235
  end
@@ -5,7 +5,7 @@ module CalendariumRomanum
5
5
  class SanctoraleFactory
6
6
  class << self
7
7
  # Takes several {Sanctorale} instances, returns a new one,
8
- # resulting by merging them all together
8
+ # created by merging them all together
9
9
  # (using {Sanctorale#update})
10
10
  #
11
11
  # @return [Sanctorale]
@@ -75,7 +75,7 @@ module CalendariumRomanum
75
75
 
76
76
  def load_parent_hierarchy(path, loader)
77
77
  main = loader.load_from_file path
78
- return [main] unless main.metadata.has_key? 'extends'
78
+ return [main] unless main.metadata.is_a?(Hash) && main.metadata.has_key?('extends')
79
79
 
80
80
  to_merge = [main]
81
81
  parents = main.metadata['extends']
@@ -6,9 +6,9 @@ module CalendariumRomanum
6
6
  # and knows how to transform it to {Celebration}s
7
7
  # and fill them in a {Sanctorale}.
8
8
  #
9
- # For specification of the data format see {file:data/README.md},
10
- # for a complete example see the file describing General Roman Calendar:
11
- # {file:data/universal-en.txt}
9
+ # For specification of the data format see {file:data/README.md}
10
+ # of the data directory, For a complete example see e.g.
11
+ # {file:universal-en.txt the file describing General Roman Calendar}.
12
12
  class SanctoraleLoader
13
13
 
14
14
  # @api private
@@ -1,7 +1,7 @@
1
1
  require 'date'
2
2
 
3
3
  module CalendariumRomanum
4
- VERSION = '0.7.0'.freeze
4
+ VERSION = '0.7.1'.freeze
5
5
 
6
- RELEASE_DATE = Date.new(2020, 6, 21).freeze
6
+ RELEASE_DATE = Date.new(2020, 6, 28).freeze
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calendarium-romanum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Pavlík
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-21 00:00:00.000000000 Z
11
+ date: 2020-06-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -88,7 +88,18 @@ executables:
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".rubocop.yml"
94
+ - ".travis.yml"
95
+ - ".yardopts"
96
+ - CHANGELOG.md
97
+ - Gemfile
98
+ - Gemfile.lock
99
+ - README.md
100
+ - Rakefile
91
101
  - bin/calendariumrom
102
+ - calendarium-romanum.gemspec
92
103
  - config/locales/cs.yml
93
104
  - config/locales/en.yml
94
105
  - config/locales/es.yml
@@ -112,6 +123,10 @@ files:
112
123
  - data/universal-fr.txt
113
124
  - data/universal-it.txt
114
125
  - data/universal-la.txt
126
+ - doc/data_readme.md
127
+ - doc/images/class_diagram.png
128
+ - doc/images/class_diagram.puml
129
+ - doc/yard_readme.rdoc
115
130
  - lib/calendarium-romanum.rb
116
131
  - lib/calendarium-romanum/abstract_date.rb
117
132
  - lib/calendarium-romanum/calendar.rb
@@ -136,30 +151,6 @@ files:
136
151
  - lib/calendarium-romanum/transfers.rb
137
152
  - lib/calendarium-romanum/util.rb
138
153
  - lib/calendarium-romanum/version.rb
139
- - spec/abstract_date_spec.rb
140
- - spec/calendar_spec.rb
141
- - spec/celebration_factory_spec.rb
142
- - spec/celebration_spec.rb
143
- - spec/cli_spec.rb
144
- - spec/colour_spec.rb
145
- - spec/data_spec.rb
146
- - spec/date_parser_spec.rb
147
- - spec/date_spec.rb
148
- - spec/dates_spec.rb
149
- - spec/day_spec.rb
150
- - spec/enum_spec.rb
151
- - spec/i18n_spec.rb
152
- - spec/ordinalizer_spec.rb
153
- - spec/perpetual_calendar_spec.rb
154
- - spec/rank_spec.rb
155
- - spec/readme_spec.rb
156
- - spec/sanctorale_factory_spec.rb
157
- - spec/sanctorale_loader_spec.rb
158
- - spec/sanctorale_spec.rb
159
- - spec/season_spec.rb
160
- - spec/spec_helper.rb
161
- - spec/temporale_spec.rb
162
- - spec/year_spec.rb
163
154
  homepage: http://github.com/igneus/calendarium-romanum
164
155
  licenses:
165
156
  - LGPL-3.0
@@ -1,70 +0,0 @@
1
- require_relative 'spec_helper'
2
-
3
- describe CR::AbstractDate do
4
- AD = CR::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
- # test .new through .from_date on a complete leap year
29
- describe '.from_date' do
30
- YEAR = 2000
31
-
32
- it 'the test year is leap' do
33
- expect(Date.new(YEAR)).to be_leap
34
- end
35
-
36
- CR::Util::Year.new(YEAR).each do |date|
37
- it date.to_s do
38
- expect do
39
- AD.from_date date
40
- end.not_to raise_exception
41
- end
42
- end
43
- end
44
-
45
- describe '#<' do
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
57
- end
58
-
59
- describe '#==' do
60
- it { expect(AD.new(1, 1)).to be == AD.new(1, 1) }
61
- it { expect(AD.new(1, 1)).not_to be == AD.new(1, 2) }
62
- end
63
-
64
- describe 'as a Hash key' do
65
- it 'different objects with the same values are considered same key' do
66
- h = {AD.new(1, 1) => 1}
67
- expect(h).to have_key AD.new(1, 1)
68
- end
69
- end
70
- end
@@ -1,756 +0,0 @@
1
- require_relative 'spec_helper'
2
-
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
25
-
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)
29
- end
30
-
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
42
- end
43
- end
44
-
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)
49
- end
50
- end
51
-
52
- describe 'invalid arguments' do
53
- it 'only two arguments' do
54
- expect { described_class.mk_date(1, 2) }
55
- .to raise_exception TypeError
56
- end
57
- end
58
- end
59
-
60
- describe '#each' do
61
- let (:day_count) {(@c.temporale.start_date..@c.temporale.end_date).count}
62
-
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)
94
- end
95
- end
96
-
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)
103
- end
104
-
105
- it 'different' do
106
- expect(a == b).to be false
107
- end
108
- end
109
-
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
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
196
-
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)
203
- end
204
- end
205
-
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)
211
- end
212
- end
213
-
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')
220
- end
221
- end
222
-
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')
228
- end
229
- end
230
- end
231
- end
232
- end
233
-
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
237
- end
238
- end
239
-
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
259
- end
260
- end
261
-
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
265
- end
266
-
267
- it 'first Sunday starts week 1' do
268
- expect(@c.day(2013, 12, 29).season_week).to eq 1
269
- end
270
- end
271
-
272
- describe 'Lent' do
273
- it 'Ash Wednesday is week 0' do
274
- expect(@c.day(2014, 3, 5).season_week).to eq 0
275
- end
276
- end
277
-
278
- describe 'Easter' do
279
- it 'Easter Sunday opens week 1' do
280
- expect(@c.day(2014, 4, 20).season_week).to eq 1
281
- end
282
- end
283
-
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
287
- end
288
-
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
293
- end
294
-
295
- it '2015' do
296
- c = described_class.new(2014)
297
- expect(c.day(2015, 5, 25).season_week).to eq 8
298
- end
299
-
300
- it '2016' do
301
- c = described_class.new(2015)
302
- expect(c.day(2016, 5, 16).season_week).to eq 7
303
- end
304
-
305
- it '2017' do
306
- c = described_class.new(2016)
307
- expect(c.day(2017, 6, 5).season_week).to eq 9
308
- end
309
-
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
315
- end
316
- end
317
- end
318
-
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
323
- end
324
- end
325
- end
326
-
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
331
- end
332
- end
333
- end
334
-
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
339
- end
340
- end
341
- end
342
- end
343
- end
344
- end
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) }
468
-
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])
505
- end
506
-
507
- it 'offers Saturday memorial' do
508
- expect(celebrations)
509
- .to include(an_object_eq_to(celfactory.saturday_memorial_bvm))
510
- end
511
- end
512
-
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])
519
- end
520
-
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) }
529
-
530
- before :each do
531
- allow(sanctorale).to receive(:[]).and_return([])
532
- end
533
-
534
- it 'does not offer Saturday memorial' do
535
- expect(celebrations)
536
- .not_to include(an_object_eq_to(celfactory.saturday_memorial_bvm))
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
552
-
553
- describe 'opted in by constructor argument' do
554
- let(:calendar) { described_class.new(year, nil, nil, vespers: true) }
555
-
556
- it 'fills Vespers' do
557
- day = calendar.day saturday
558
- expect(day.vespers).to be_a CR::Celebration
559
- end
560
-
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
569
-
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
576
-
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) }
580
-
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
586
- end
587
-
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
602
-
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])
608
-
609
- day = calendar.day(sunday - 1)
610
- expect(day.vespers.symbol).to eq :test
611
- end
612
- end
613
-
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])
618
-
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
628
- end
629
-
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
636
-
637
- day = calendar.day(presentation - 1)
638
- expect(day.vespers).to be nil
639
- end
640
- end
641
-
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
646
-
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
728
- end
729
- end
730
- end
731
- end
732
-
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
739
- end
740
-
741
- def [](date)
742
- [] # no celebrations
743
- end
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
755
- end
756
- end