holidays 2.2.0 → 3.0.0

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.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +22 -8
  4. data/Rakefile +26 -8
  5. data/bin/console +7 -0
  6. data/bin/setup +5 -0
  7. data/{data → definitions}/SYNTAX.rdoc +0 -0
  8. data/{data → definitions}/ar.yaml +0 -0
  9. data/{data → definitions}/at.yaml +1 -1
  10. data/{data → definitions}/au.yaml +80 -51
  11. data/{data → definitions}/be.yaml +0 -0
  12. data/{data → definitions}/br.yaml +0 -0
  13. data/{data → definitions}/ca.yaml +0 -0
  14. data/{data → definitions}/ch.yaml +0 -0
  15. data/{data → definitions}/cl.yaml +0 -0
  16. data/{data → definitions}/cr.yaml +0 -0
  17. data/{data → definitions}/cz.yaml +0 -0
  18. data/{data → definitions}/de.yaml +25 -34
  19. data/{data → definitions}/dk.yaml +0 -0
  20. data/{data → definitions}/ecb_target.yaml +2 -2
  21. data/{data → definitions}/el.yaml +0 -0
  22. data/{data → definitions}/es.yaml +37 -26
  23. data/{data → definitions}/federal_reserve.yaml +0 -0
  24. data/{data → definitions}/fedex.yaml +9 -3
  25. data/{data → definitions}/fi.yaml +0 -0
  26. data/{data → definitions}/fr.yaml +0 -0
  27. data/{data → definitions}/gb.yaml +19 -19
  28. data/{data → definitions}/hr.yaml +0 -0
  29. data/{data → definitions}/hu.yaml +1 -1
  30. data/{data → definitions}/ie.yaml +0 -0
  31. data/{data → definitions}/index.yaml +0 -0
  32. data/{data → definitions}/is.yaml +0 -0
  33. data/{data → definitions}/it.yaml +0 -0
  34. data/{data → definitions}/jp.yaml +3 -3
  35. data/{data → definitions}/li.yaml +3 -3
  36. data/{data → definitions}/lt.yaml +0 -0
  37. data/{data → definitions}/ma.yaml +0 -0
  38. data/{data → definitions}/mx.yaml +0 -0
  39. data/{data → definitions}/nerc.yaml +0 -0
  40. data/{data → definitions}/nl.yaml +0 -0
  41. data/{data → definitions}/no.yaml +0 -0
  42. data/{data → definitions}/north_america_informal.yaml +0 -0
  43. data/{data → definitions}/nyse.yaml +0 -0
  44. data/{data → definitions}/nz.yaml +21 -10
  45. data/{data → definitions}/ph.yaml +0 -0
  46. data/{data → definitions}/pl.yaml +0 -0
  47. data/{data → definitions}/pt.yaml +0 -0
  48. data/{data → definitions}/ro.yaml +1 -2
  49. data/{data → definitions}/se.yaml +0 -0
  50. data/{data → definitions}/sg.yaml +0 -0
  51. data/{data → definitions}/si.yaml +6 -6
  52. data/{data → definitions}/sk.yaml +0 -0
  53. data/{data → definitions}/united_nations.yaml +0 -0
  54. data/{data → definitions}/ups.yaml +17 -11
  55. data/{data → definitions}/us.yaml +20 -15
  56. data/{data → definitions}/ve.yaml +0 -0
  57. data/{data → definitions}/vi.yaml +0 -0
  58. data/{data → definitions}/za.yaml +0 -0
  59. data/holidays.gemspec +2 -1
  60. data/lib/generated_definitions/MANIFEST +56 -0
  61. data/lib/generated_definitions/REGIONS.rb +4 -0
  62. data/lib/{holidays → generated_definitions}/ar.rb +2 -2
  63. data/lib/{holidays → generated_definitions}/at.rb +2 -2
  64. data/lib/{holidays → generated_definitions}/au.rb +19 -10
  65. data/lib/{holidays → generated_definitions}/be.rb +2 -2
  66. data/lib/{holidays → generated_definitions}/br.rb +2 -2
  67. data/lib/{holidays → generated_definitions}/ca.rb +2 -2
  68. data/lib/{holidays → generated_definitions}/ch.rb +2 -2
  69. data/lib/{holidays → generated_definitions}/cl.rb +2 -2
  70. data/lib/{holidays → generated_definitions}/cr.rb +2 -2
  71. data/lib/{holidays → generated_definitions}/cz.rb +2 -2
  72. data/lib/{holidays → generated_definitions}/de.rb +5 -4
  73. data/lib/{holidays → generated_definitions}/dk.rb +2 -2
  74. data/lib/{holidays → generated_definitions}/ecb_target.rb +2 -2
  75. data/lib/{holidays → generated_definitions}/el.rb +2 -2
  76. data/lib/{holidays → generated_definitions}/es.rb +6 -6
  77. data/lib/{holidays → generated_definitions}/europe.rb +7 -6
  78. data/lib/{holidays → generated_definitions}/fed_ex.rb +8 -3
  79. data/lib/{holidays → generated_definitions}/federal_reserve.rb +2 -2
  80. data/lib/{holidays → generated_definitions}/fi.rb +2 -2
  81. data/lib/{holidays → generated_definitions}/fr.rb +2 -2
  82. data/lib/{holidays → generated_definitions}/gb.rb +2 -2
  83. data/lib/{holidays → generated_definitions}/hr.rb +2 -2
  84. data/lib/{holidays → generated_definitions}/hu.rb +2 -2
  85. data/lib/{holidays → generated_definitions}/ie.rb +2 -2
  86. data/lib/{holidays → generated_definitions}/is.rb +2 -2
  87. data/lib/{holidays → generated_definitions}/it.rb +2 -2
  88. data/lib/{holidays → generated_definitions}/jp.rb +5 -5
  89. data/lib/{holidays → generated_definitions}/li.rb +2 -2
  90. data/lib/{holidays → generated_definitions}/lt.rb +2 -2
  91. data/lib/{holidays → generated_definitions}/ma.rb +2 -2
  92. data/lib/{holidays → generated_definitions}/mx.rb +2 -2
  93. data/lib/{holidays → generated_definitions}/nerc.rb +2 -2
  94. data/lib/{holidays → generated_definitions}/nl.rb +2 -2
  95. data/lib/{holidays → generated_definitions}/no.rb +2 -2
  96. data/lib/{holidays → generated_definitions}/north_america.rb +8 -3
  97. data/lib/{holidays → generated_definitions}/nyse.rb +2 -2
  98. data/lib/{holidays → generated_definitions}/nz.rb +5 -5
  99. data/lib/{holidays → generated_definitions}/ph.rb +2 -2
  100. data/lib/{holidays → generated_definitions}/pl.rb +2 -2
  101. data/lib/{holidays → generated_definitions}/pt.rb +2 -2
  102. data/lib/{holidays → generated_definitions}/ro.rb +2 -2
  103. data/lib/{holidays → generated_definitions}/scandinavia.rb +2 -2
  104. data/lib/{holidays → generated_definitions}/se.rb +2 -2
  105. data/lib/{holidays → generated_definitions}/sg.rb +2 -2
  106. data/lib/{holidays → generated_definitions}/si.rb +2 -2
  107. data/lib/{holidays → generated_definitions}/sk.rb +2 -2
  108. data/lib/{holidays → generated_definitions}/united_nations.rb +2 -2
  109. data/lib/{holidays → generated_definitions}/ups.rb +8 -3
  110. data/lib/{holidays → generated_definitions}/us.rb +8 -3
  111. data/lib/{holidays → generated_definitions}/ve.rb +2 -2
  112. data/lib/{holidays → generated_definitions}/vi.rb +2 -2
  113. data/lib/{holidays → generated_definitions}/za.rb +2 -2
  114. data/lib/holidays.rb +120 -665
  115. data/lib/holidays/core_extensions/date.rb +39 -0
  116. data/lib/holidays/date_calculator/day_of_month.rb +68 -0
  117. data/lib/holidays/date_calculator/easter.rb +58 -0
  118. data/lib/holidays/date_calculator/weekend_modifier.rb +49 -0
  119. data/lib/holidays/date_calculator_factory.rb +21 -0
  120. data/lib/holidays/definition/context/generator.rb +216 -0
  121. data/lib/holidays/definition/context/merger.rb +26 -0
  122. data/lib/holidays/definition/repository/cache.rb +33 -0
  123. data/lib/holidays/definition/repository/holidays_by_month.rb +49 -0
  124. data/lib/holidays/definition/repository/proc_cache.rb +36 -0
  125. data/lib/holidays/definition/repository/regions.rb +36 -0
  126. data/lib/holidays/definition/validator/region.rb +45 -0
  127. data/lib/holidays/definition_factory.rb +50 -0
  128. data/lib/holidays/option/context/parse_options.rb +96 -0
  129. data/lib/holidays/option_factory.rb +14 -0
  130. data/lib/holidays/use_case/context/between.rb +105 -0
  131. data/lib/holidays/use_case/context/dates_driver_builder.rb +64 -0
  132. data/lib/holidays/use_case_factory.rb +20 -0
  133. data/lib/holidays/version.rb +1 -1
  134. data/test/defs/test_defs_ar.rb +1 -1
  135. data/test/defs/test_defs_at.rb +2 -2
  136. data/test/defs/test_defs_au.rb +61 -43
  137. data/test/defs/test_defs_be.rb +1 -1
  138. data/test/defs/test_defs_br.rb +1 -1
  139. data/test/defs/test_defs_ca.rb +1 -1
  140. data/test/defs/test_defs_ch.rb +1 -1
  141. data/test/defs/test_defs_cl.rb +1 -1
  142. data/test/defs/test_defs_cr.rb +1 -1
  143. data/test/defs/test_defs_cz.rb +1 -1
  144. data/test/defs/test_defs_de.rb +18 -30
  145. data/test/defs/test_defs_dk.rb +1 -1
  146. data/test/defs/test_defs_ecb_target.rb +3 -3
  147. data/test/defs/test_defs_el.rb +1 -1
  148. data/test/defs/test_defs_es.rb +36 -25
  149. data/test/defs/test_defs_europe.rb +82 -84
  150. data/test/defs/test_defs_fed_ex.rb +4 -1
  151. data/test/defs/test_defs_federal_reserve.rb +1 -1
  152. data/test/defs/test_defs_fi.rb +1 -1
  153. data/test/defs/test_defs_fr.rb +1 -1
  154. data/test/defs/test_defs_gb.rb +20 -20
  155. data/test/defs/test_defs_hr.rb +1 -1
  156. data/test/defs/test_defs_hu.rb +2 -2
  157. data/test/defs/test_defs_ie.rb +1 -1
  158. data/test/defs/test_defs_is.rb +1 -1
  159. data/test/defs/test_defs_it.rb +1 -1
  160. data/test/defs/test_defs_jp.rb +1 -1
  161. data/test/defs/test_defs_li.rb +4 -4
  162. data/test/defs/test_defs_lt.rb +1 -1
  163. data/test/defs/test_defs_ma.rb +1 -1
  164. data/test/defs/test_defs_mx.rb +1 -1
  165. data/test/defs/test_defs_nerc.rb +1 -1
  166. data/test/defs/test_defs_nl.rb +1 -1
  167. data/test/defs/test_defs_no.rb +1 -1
  168. data/test/defs/test_defs_north_america.rb +5 -3
  169. data/test/defs/test_defs_nyse.rb +1 -1
  170. data/test/defs/test_defs_nz.rb +19 -9
  171. data/test/defs/test_defs_ph.rb +1 -1
  172. data/test/defs/test_defs_pl.rb +1 -1
  173. data/test/defs/test_defs_pt.rb +1 -1
  174. data/test/defs/test_defs_ro.rb +2 -3
  175. data/test/defs/test_defs_scandinavia.rb +1 -1
  176. data/test/defs/test_defs_se.rb +1 -1
  177. data/test/defs/test_defs_sg.rb +1 -1
  178. data/test/defs/test_defs_si.rb +7 -7
  179. data/test/defs/test_defs_sk.rb +1 -1
  180. data/test/defs/test_defs_united_nations.rb +1 -1
  181. data/test/defs/test_defs_ups.rb +5 -2
  182. data/test/defs/test_defs_us.rb +5 -3
  183. data/test/defs/test_defs_ve.rb +1 -1
  184. data/test/defs/test_defs_vi.rb +1 -1
  185. data/test/defs/test_defs_za.rb +1 -1
  186. data/test/{test_date.rb → holidays/core_extensions/test_date.rb} +8 -2
  187. data/test/holidays/date_calculator/test_day_of_month.rb +27 -0
  188. data/test/holidays/date_calculator/test_easter.rb +29 -0
  189. data/test/holidays/date_calculator/test_weekend_modifier.rb +33 -0
  190. data/test/holidays/definition/context/test_generator.rb +84 -0
  191. data/test/holidays/definition/context/test_merger.rb +22 -0
  192. data/test/holidays/definition/repository/test_cache.rb +82 -0
  193. data/test/holidays/definition/repository/test_holidays_by_month.rb +187 -0
  194. data/test/holidays/definition/repository/test_proc_cache.rb +29 -0
  195. data/test/holidays/definition/repository/test_regions.rb +86 -0
  196. data/test/holidays/definition/validator/test_region.rb +50 -0
  197. data/test/holidays/option/context/test_parse_options.rb +69 -0
  198. data/test/holidays/test_date_calculator_factory.rb +21 -0
  199. data/test/holidays/test_definition_factory.rb +34 -0
  200. data/test/holidays/test_option_factory.rb +9 -0
  201. data/test/holidays/test_use_case_factory.rb +13 -0
  202. data/test/holidays/use_case/context/test_between.rb +75 -0
  203. data/test/holidays/use_case/context/test_dates_driver_builder.rb +91 -0
  204. data/test/test_all_regions.rb +14 -3
  205. data/test/test_helper.rb +2 -1
  206. data/test/test_holidays.rb +19 -24
  207. data/test/test_holidays_between.rb +85 -0
  208. data/test/test_multiple_regions.rb +2 -2
  209. data/test/test_parse_definitions.rb +10 -4
  210. metadata +181 -111
  211. data/.coveralls.yml +0 -1
  212. data/lib/holidays/MANIFEST +0 -55
@@ -0,0 +1,26 @@
1
+ module Holidays
2
+ module Definition
3
+ module Context
4
+ # Merge a new set of definitions into the Holidays module.
5
+ #
6
+ # This method is automatically called when including holiday definition
7
+ # files. This is accomplished because the Generator class generates the
8
+ # definition source with this class explicitly.
9
+ class Merger
10
+ def initialize(holidays_by_month_repo, regions_repo)
11
+ @holidays_repo = holidays_by_month_repo
12
+ @regions_repo = regions_repo
13
+ end
14
+
15
+ def call(target_regions, target_holidays)
16
+ regions_repo.add(target_regions)
17
+ holidays_repo.add(target_holidays)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :holidays_repo, :regions_repo
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ module Holidays
2
+ module Definition
3
+ module Repository
4
+ class Cache
5
+ def initialize
6
+ reset!
7
+ end
8
+
9
+ def cache_between(start_date, end_date, cache_data, *options)
10
+ raise ArgumentError unless cache_data
11
+
12
+ @cache_range[options] = start_date..end_date
13
+ @cache[options] = cache_data
14
+ end
15
+
16
+ def find(start_date, end_date, *options)
17
+ if range = @cache_range[options]
18
+ if range.begin <= start_date && range.end >= end_date
19
+ return @cache[options].select do |holiday|
20
+ holiday[:date] >= start_date && holiday[:date] <= end_date
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def reset!
27
+ @cache = {}
28
+ @cache_range = {}
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ module Holidays
2
+ module Definition
3
+ module Repository
4
+ class HolidaysByMonth
5
+ def initialize
6
+ @holidays_by_month = {}
7
+ end
8
+
9
+ def all
10
+ @holidays_by_month
11
+ end
12
+
13
+ def find_by_month(month)
14
+ raise ArgumentError unless month >= 0 && month <= 12
15
+ @holidays_by_month[month]
16
+ end
17
+
18
+ def add(new_holidays)
19
+ new_holidays.each do |month, holiday_defs|
20
+ @holidays_by_month[month] = [] unless @holidays_by_month[month]
21
+
22
+ holiday_defs.each do |holiday_def|
23
+ exists = false
24
+ @holidays_by_month[month].each do |existing_def|
25
+ if definition_exists?(existing_def, holiday_def)
26
+ # append regions
27
+ existing_def[:regions] << holiday_def[:regions]
28
+
29
+ # Should do this once we're done
30
+ existing_def[:regions].flatten!
31
+ existing_def[:regions].uniq!
32
+ exists = true
33
+ end
34
+ end
35
+
36
+ @holidays_by_month[month] << holiday_def unless exists
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def definition_exists?(existing_def, target_def)
44
+ existing_def[:name] == target_def[:name] && existing_def[:wday] == target_def[:wday] && existing_def[:mday] == target_def[:mday] && existing_def[:week] == target_def[:week] && existing_def[:function_id] == target_def[:function_id] && existing_def[:type] == target_def[:type] && existing_def[:observed_id] == target_def[:observed_id]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ module Holidays
2
+ module Definition
3
+ module Repository
4
+ # ==== Benchmarks
5
+ #
6
+ # Lookup Easter Sunday, with caching, by number of iterations:
7
+ #
8
+ # user system total real
9
+ # 0001 0.000000 0.000000 0.000000 ( 0.000000)
10
+ # 0010 0.000000 0.000000 0.000000 ( 0.000000)
11
+ # 0100 0.078000 0.000000 0.078000 ( 0.078000)
12
+ # 1000 0.641000 0.000000 0.641000 ( 0.641000)
13
+ # 5000 3.172000 0.015000 3.187000 ( 3.219000)
14
+ #
15
+ # Lookup Easter Sunday, without caching, by number of iterations:
16
+ #
17
+ # user system total real
18
+ # 0001 0.000000 0.000000 0.000000 ( 0.000000)
19
+ # 0010 0.016000 0.000000 0.016000 ( 0.016000)
20
+ # 0100 0.125000 0.000000 0.125000 ( 0.125000)
21
+ # 1000 1.234000 0.000000 1.234000 ( 1.234000)
22
+ # 5000 6.094000 0.031000 6.125000 ( 6.141000)
23
+ class ProcCache
24
+ def initialize
25
+ @proc_cache = {}
26
+ end
27
+
28
+ def lookup(function, year)
29
+ proc_key = Digest::MD5.hexdigest("#{function.to_s}_#{year.to_s}")
30
+ @proc_cache[proc_key] = function.call(year) unless @proc_cache[proc_key]
31
+ @proc_cache[proc_key]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Holidays
2
+ module Definition
3
+ module Repository
4
+ class Regions
5
+ def initialize
6
+ @regions = []
7
+ end
8
+
9
+ def all
10
+ @regions
11
+ end
12
+
13
+ def add(regions)
14
+ regions = [regions] unless regions.is_a?(Array)
15
+
16
+ regions.each do |region|
17
+ raise ArgumentError unless region.is_a?(Symbol)
18
+ end
19
+
20
+ @regions = @regions | regions
21
+ @regions.uniq!
22
+ end
23
+
24
+ def exists?(region)
25
+ raise ArgumentError unless region.is_a?(Symbol)
26
+ @regions.include?(region)
27
+ end
28
+
29
+ def search(prefix)
30
+ raise ArgumentError unless prefix.is_a?(String)
31
+ @regions.select { |region| region.to_s =~ Regexp.new("^#{prefix}") }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ module Holidays
2
+ module Definition
3
+ module Validator
4
+ class Region
5
+ def initialize(regions_repo)
6
+ @regions_repo = regions_repo
7
+ end
8
+
9
+ def valid?(r)
10
+ return false unless r.is_a?(Symbol)
11
+
12
+ region = find_wildcard_base(r)
13
+
14
+ (region == :any ||
15
+ regions_repo.exists?(region) ||
16
+ region_in_static_definitions?(region))
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :regions_repo
22
+
23
+ # Ex: :gb_ transformed to :gb
24
+ def find_wildcard_base(region)
25
+ r = region.to_s
26
+
27
+ if r =~ /_$/
28
+ base = r.split('_').first
29
+ else
30
+ base = r
31
+ end
32
+
33
+ base.to_sym
34
+ end
35
+
36
+ def region_in_static_definitions?(region)
37
+ static_regions_definition = "#{DEFINITIONS_PATH}/REGIONS.rb"
38
+ require static_regions_definition
39
+
40
+ Holidays::REGIONS.include?(region)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ require 'holidays/definition/context/generator'
2
+ require 'holidays/definition/context/merger'
3
+ require 'holidays/definition/repository/holidays_by_month'
4
+ require 'holidays/definition/repository/regions'
5
+ require 'holidays/definition/repository/cache'
6
+ require 'holidays/definition/repository/proc_cache'
7
+ require 'holidays/definition/validator/region'
8
+
9
+ module Holidays
10
+ module DefinitionFactory
11
+ class << self
12
+ def file_parser
13
+ Definition::Context::Generator.new
14
+ end
15
+
16
+ def source_generator
17
+ Definition::Context::Generator.new
18
+ end
19
+
20
+ def merger
21
+ Definition::Context::Merger.new(
22
+ holidays_by_month_repository,
23
+ regions_repository
24
+ )
25
+ end
26
+
27
+ def region_validator
28
+ Definition::Validator::Region.new(
29
+ regions_repository
30
+ )
31
+ end
32
+
33
+ def holidays_by_month_repository
34
+ @holidays_repo ||= Definition::Repository::HolidaysByMonth.new
35
+ end
36
+
37
+ def regions_repository
38
+ @regions_repo ||= Definition::Repository::Regions.new
39
+ end
40
+
41
+ def cache_repository
42
+ @cache_repo ||= Definition::Repository::Cache.new
43
+ end
44
+
45
+ def proc_cache_repository
46
+ @proc_cache_repo ||= Definition::Repository::ProcCache.new
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,96 @@
1
+ module Holidays
2
+ module Option
3
+ module Context
4
+ class ParseOptions
5
+ def initialize(regions_repo, region_validator)
6
+ @regions_repo = regions_repo
7
+ @region_validator = region_validator
8
+ end
9
+
10
+ # Returns [(arr)regions, (bool)observed, (bool)informal]
11
+ def call(*options)
12
+ options.flatten!
13
+
14
+ #TODO This is garbage. These two deletes MUST come before the
15
+ # parse_regions call, otherwise it thinks that :observed and :informal
16
+ # are regions to parse. We should be splitting these things out.
17
+ observed = options.delete(:observed) ? true : false
18
+ informal = options.delete(:informal) ? true : false
19
+ regions = parse_regions!(options)
20
+
21
+ return regions, observed, informal
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :regions_repo, :region_validator
27
+
28
+ # Check regions against list of supported regions and return an array of
29
+ # symbols.
30
+ #
31
+ # If a wildcard region is found (e.g. <tt>:ca_</tt>) it is expanded into all
32
+ # of its available sub regions.
33
+ def parse_regions!(regions)
34
+ regions = [regions] unless regions.kind_of?(Array)
35
+ return [:any] if regions.empty?
36
+
37
+ regions = regions.collect { |r| r.to_sym }
38
+
39
+ validate!(regions)
40
+
41
+ # Found sub region wild-card
42
+ regions.delete_if do |r|
43
+ if r.to_s =~ /_$/
44
+ load_containing_region(r.to_s)
45
+ regions << regions_repo.search(r.to_s)
46
+ true
47
+ end
48
+ end
49
+
50
+ regions.flatten!
51
+
52
+ require "#{DEFINITIONS_PATH}/north_america" if regions.include?(:us) # special case for north_america/US cross-linking
53
+
54
+ regions.each do |region|
55
+ unless region == :any or regions_repo.exists?(region)
56
+ region_definition = "#{DEFINITIONS_PATH}/#{region.to_s}"
57
+ begin
58
+ require region_definition #FIXME This is unacceptable, we can't be loading external files while parsing options
59
+ rescue LoadError => e
60
+ # This could be a sub region that does not have any holiday
61
+ # definitions of its own; try to load the containing region instead.
62
+ if region.to_s =~ /_/
63
+ load_containing_region(region.to_s)
64
+ else
65
+ raise UnknownRegionError, "Could not load #{region_definition}"
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ regions
72
+ end
73
+
74
+ def validate!(regions)
75
+ regions.each do |r|
76
+ raise UnknownRegionError unless region_validator.valid?(r)
77
+ end
78
+ end
79
+
80
+ # Derive the containing region from a sub region wild-card or a sub region
81
+ # and load its definition. (Common code factored out from parse_regions)
82
+ def load_containing_region(sub_reg)
83
+ prefix = sub_reg.split('_').first
84
+ region_definition = "#{DEFINITIONS_PATH}/#{prefix}"
85
+ unless regions_repo.exists?(prefix.to_sym)
86
+ begin
87
+ require region_definition #FIXME This is not acceptable, we can't be loading external things while parsing options.
88
+ rescue LoadError
89
+ raise UnknownRegionError, "Could not load #{region_definition}"
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,14 @@
1
+ require 'holidays/option/context/parse_options'
2
+
3
+ module Holidays
4
+ module OptionFactory
5
+ class << self
6
+ def parse_options
7
+ Option::Context::ParseOptions.new(
8
+ DefinitionFactory.regions_repository,
9
+ DefinitionFactory.region_validator,
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,105 @@
1
+ module Holidays
2
+ module UseCase
3
+ module Context
4
+ class Between
5
+ def initialize(holidays_by_month_repo, day_of_month_calculator, proc_cache_repo)
6
+ @holidays_by_month_repo = holidays_by_month_repo
7
+ @day_of_month_calculator = day_of_month_calculator
8
+ @proc_cache_repo = proc_cache_repo
9
+ end
10
+
11
+ def call(start_date, end_date, dates_driver, regions, observed, informal)
12
+ validate!(start_date, end_date, dates_driver, regions)
13
+
14
+ holidays = []
15
+
16
+ dates_driver.each do |year, months|
17
+ months.each do |month|
18
+ next unless hbm = holidays_by_month_repo.find_by_month(month)
19
+
20
+ hbm.each do |h|
21
+ next unless in_region?(regions, h[:regions])
22
+
23
+ # Skip informal holidays unless they have been requested
24
+ next if h[:type] == :informal and not informal
25
+
26
+ if h[:function]
27
+ # Holiday definition requires a calculation
28
+ result = call_proc(h[:function], year)
29
+
30
+ # Procs may return either Date or an integer representing mday
31
+ if result.kind_of?(Date)
32
+ month = result.month
33
+ mday = result.mday
34
+ else
35
+ mday = result
36
+ end
37
+ else
38
+ # Calculate the mday
39
+ mday = h[:mday] || day_of_month_calculator.call(year, month, h[:week], h[:wday])
40
+ end
41
+
42
+ # Silently skip bad mdays
43
+ begin
44
+ date = Date.civil(year, month, mday)
45
+ rescue; next; end
46
+
47
+ # If the :observed option is set, calculate the date when the holiday
48
+ # is observed.
49
+ if observed and h[:observed]
50
+ date = call_proc(h[:observed], date)
51
+ end
52
+
53
+ if date.between?(start_date, end_date)
54
+ holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ holidays.sort{|a, b| a[:date] <=> b[:date] }
61
+ end
62
+
63
+ private
64
+
65
+ attr_reader :holidays_by_month_repo, :day_of_month_calculator, :proc_cache_repo
66
+
67
+ def validate!(start_date, end_date, dates_driver, regions)
68
+ raise ArgumentError unless start_date
69
+ raise ArgumentError unless end_date
70
+
71
+ raise ArgumentError if dates_driver.nil? || dates_driver.empty?
72
+
73
+ dates_driver.each do |year, months|
74
+ raise ArgumentError if months.nil? || months.empty?
75
+ end
76
+
77
+ raise ArgumentError if regions.nil? || regions.empty?
78
+ end
79
+
80
+ def call_proc(function, year)
81
+ proc_cache_repo.lookup(function, year)
82
+ end
83
+
84
+ # Check sub regions.
85
+ #
86
+ # When request :any, all holidays should be returned.
87
+ # When requesting :ca_bc, holidays in :ca or :ca_bc should be returned.
88
+ # When requesting :ca, holidays in :ca but not its subregions should be returned.
89
+ def in_region?(requested, available) # :nodoc:
90
+ return true if requested.include?(:any)
91
+
92
+ # When an underscore is encountered, derive the parent regions
93
+ # symbol and include both in the requested array.
94
+ requested = requested.collect do |r|
95
+ r.to_s =~ /_/ ? [r, r.to_s.gsub(/_[\w]*$/, '').to_sym] : r
96
+ end
97
+
98
+ requested = requested.flatten.uniq
99
+
100
+ available.any? { |avail| requested.include?(avail) }
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end