holidays 2.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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