holidays 3.3.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/CONTRIBUTING.md +37 -0
  4. data/README.md +16 -25
  5. data/REFERENCES +4 -1
  6. data/Rakefile +38 -8
  7. data/benchmark.rb +8 -0
  8. data/definitions/README.md +187 -8
  9. data/definitions/ar.yaml +2 -1
  10. data/definitions/at.yaml +17 -13
  11. data/definitions/au.yaml +65 -60
  12. data/definitions/be_fr.yaml +14 -10
  13. data/definitions/be_nl.yaml +8 -4
  14. data/definitions/bg.yaml +12 -6
  15. data/definitions/br.yaml +6 -3
  16. data/definitions/ca.yaml +8 -6
  17. data/definitions/ch.yaml +21 -15
  18. data/definitions/cl.yaml +4 -2
  19. data/definitions/cr.yaml +5 -3
  20. data/definitions/cz.yaml +4 -2
  21. data/definitions/de.yaml +25 -14
  22. data/definitions/dk.yaml +26 -17
  23. data/definitions/ecb_target.yaml +4 -2
  24. data/definitions/el.yaml +23 -18
  25. data/definitions/es.yaml +31 -28
  26. data/definitions/federal_reserve.yaml +12 -12
  27. data/definitions/fedex.yaml +6 -6
  28. data/definitions/fi.yaml +26 -25
  29. data/definitions/fr.yaml +8 -4
  30. data/definitions/gb.yaml +9 -7
  31. data/definitions/hr.yaml +8 -6
  32. data/definitions/hu.yaml +8 -6
  33. data/definitions/ie.yaml +17 -16
  34. data/definitions/index.yaml +1 -1
  35. data/definitions/is.yaml +29 -19
  36. data/definitions/it.yaml +10 -9
  37. data/definitions/jp.yaml +92 -44
  38. data/definitions/li.yaml +25 -20
  39. data/definitions/lt.yaml +2 -1
  40. data/definitions/ma.yaml +7 -7
  41. data/definitions/mx.yaml +11 -11
  42. data/definitions/nerc.yaml +6 -6
  43. data/definitions/nl.yaml +22 -18
  44. data/definitions/no.yaml +19 -11
  45. data/definitions/north_america_informal.yaml +6 -6
  46. data/definitions/nyse.yaml +9 -8
  47. data/definitions/nz.yaml +33 -29
  48. data/definitions/ph.yaml +15 -8
  49. data/definitions/pl.yaml +27 -17
  50. data/definitions/pt.yaml +4 -2
  51. data/definitions/ro.yaml +21 -18
  52. data/definitions/se.yaml +24 -18
  53. data/definitions/sg.yaml +10 -9
  54. data/definitions/si.yaml +4 -2
  55. data/definitions/sk.yaml +4 -2
  56. data/definitions/united_nations.yaml +12 -12
  57. data/definitions/ups.yaml +6 -6
  58. data/definitions/us.yaml +12 -11
  59. data/definitions/ve.yaml +8 -4
  60. data/definitions/vi.yaml +6 -6
  61. data/definitions/za.yaml +26 -24
  62. data/holidays.gemspec +3 -1
  63. data/lib/generated_definitions/MANIFEST +1 -1
  64. data/lib/generated_definitions/ar.rb +8 -6
  65. data/lib/generated_definitions/at.rb +11 -9
  66. data/lib/generated_definitions/au.rb +75 -99
  67. data/lib/generated_definitions/be.rb +12 -10
  68. data/lib/generated_definitions/be_fr.rb +12 -10
  69. data/lib/generated_definitions/be_nl.rb +12 -10
  70. data/lib/generated_definitions/bg.rb +14 -13
  71. data/lib/generated_definitions/br.rb +11 -9
  72. data/lib/generated_definitions/ca.rb +20 -20
  73. data/lib/generated_definitions/ch.rb +41 -44
  74. data/lib/generated_definitions/cl.rb +9 -7
  75. data/lib/generated_definitions/cr.rb +9 -7
  76. data/lib/generated_definitions/cz.rb +9 -6
  77. data/lib/generated_definitions/de.rb +25 -25
  78. data/lib/generated_definitions/dk.rb +17 -15
  79. data/lib/generated_definitions/ecb_target.rb +9 -7
  80. data/lib/generated_definitions/el.rb +13 -11
  81. data/lib/generated_definitions/es.rb +35 -33
  82. data/lib/generated_definitions/europe.rb +234 -247
  83. data/lib/generated_definitions/federal_reserve.rb +11 -9
  84. data/lib/generated_definitions/fedex.rb +42 -0
  85. data/lib/generated_definitions/fi.rb +32 -36
  86. data/lib/generated_definitions/fr.rb +12 -10
  87. data/lib/generated_definitions/gb.rb +15 -13
  88. data/lib/generated_definitions/hr.rb +10 -8
  89. data/lib/generated_definitions/hu.rb +9 -7
  90. data/lib/generated_definitions/ie.rb +17 -17
  91. data/lib/generated_definitions/is.rb +26 -26
  92. data/lib/generated_definitions/it.rb +9 -7
  93. data/lib/generated_definitions/jp.rb +112 -89
  94. data/lib/generated_definitions/li.rb +14 -12
  95. data/lib/generated_definitions/lt.rb +9 -7
  96. data/lib/generated_definitions/ma.rb +7 -5
  97. data/lib/generated_definitions/mx.rb +7 -5
  98. data/lib/generated_definitions/nerc.rb +10 -8
  99. data/lib/generated_definitions/nl.rb +15 -13
  100. data/lib/generated_definitions/no.rb +16 -14
  101. data/lib/generated_definitions/north_america.rb +34 -37
  102. data/lib/generated_definitions/nyse.rb +10 -8
  103. data/lib/generated_definitions/nz.rb +40 -40
  104. data/lib/generated_definitions/ph.rb +17 -13
  105. data/lib/generated_definitions/pl.rb +25 -27
  106. data/lib/generated_definitions/pt.rb +10 -8
  107. data/lib/generated_definitions/ro.rb +11 -9
  108. data/lib/generated_definitions/scandinavia.rb +92 -102
  109. data/lib/generated_definitions/se.rb +25 -27
  110. data/lib/generated_definitions/sg.rb +11 -9
  111. data/lib/generated_definitions/si.rb +10 -8
  112. data/lib/generated_definitions/sk.rb +9 -7
  113. data/lib/generated_definitions/united_nations.rb +7 -5
  114. data/lib/generated_definitions/ups.rb +13 -12
  115. data/lib/generated_definitions/us.rb +20 -21
  116. data/lib/generated_definitions/ve.rb +11 -9
  117. data/lib/generated_definitions/vi.rb +7 -5
  118. data/lib/generated_definitions/za.rb +19 -17
  119. data/lib/holidays.rb +20 -83
  120. data/lib/holidays/date_calculator/weekend_modifier.rb +22 -5
  121. data/lib/holidays/definition/context/generator.rb +67 -29
  122. data/lib/holidays/definition/context/merger.rb +8 -8
  123. data/lib/holidays/definition/decorator/custom_method_proc.rb +28 -0
  124. data/lib/holidays/definition/decorator/custom_method_source.rb +30 -0
  125. data/lib/holidays/definition/entity/custom_method.rb +11 -0
  126. data/lib/holidays/definition/parser/custom_method.rb +69 -0
  127. data/lib/holidays/definition/repository/custom_methods.rb +27 -0
  128. data/lib/holidays/definition/repository/holidays_by_month.rb +1 -1
  129. data/lib/holidays/definition/repository/{proc_cache.rb → proc_result_cache.rb} +19 -4
  130. data/lib/holidays/definition/validator/custom_method.rb +31 -0
  131. data/lib/holidays/definition_factory.rb +42 -6
  132. data/lib/holidays/errors.rb +6 -0
  133. data/lib/holidays/load_all_definitions.rb +57 -0
  134. data/lib/holidays/option/context/parse_options.rb +26 -16
  135. data/lib/holidays/option_factory.rb +1 -0
  136. data/lib/holidays/use_case/context/between.rb +41 -14
  137. data/lib/holidays/use_case_factory.rb +2 -1
  138. data/lib/holidays/version.rb +1 -1
  139. data/test/data/test_single_custom_holiday_with_custom_procs.yaml +24 -0
  140. data/test/defs/test_defs_at.rb +1 -1
  141. data/test/defs/test_defs_au.rb +3 -2
  142. data/test/defs/test_defs_cr.rb +1 -0
  143. data/test/defs/test_defs_cz.rb +1 -0
  144. data/test/defs/test_defs_dk.rb +2 -2
  145. data/test/defs/test_defs_el.rb +7 -6
  146. data/test/defs/test_defs_europe.rb +40 -33
  147. data/test/defs/test_defs_fedex.rb +24 -0
  148. data/test/defs/test_defs_fi.rb +4 -3
  149. data/test/defs/test_defs_hr.rb +2 -2
  150. data/test/defs/test_defs_hu.rb +2 -2
  151. data/test/defs/test_defs_is.rb +2 -1
  152. data/test/defs/test_defs_it.rb +2 -1
  153. data/test/defs/test_defs_jp.rb +1 -1
  154. data/test/defs/test_defs_li.rb +1 -1
  155. data/test/defs/test_defs_ma.rb +2 -1
  156. data/test/defs/test_defs_mx.rb +4 -3
  157. data/test/defs/test_defs_nl.rb +7 -6
  158. data/test/defs/test_defs_no.rb +1 -0
  159. data/test/defs/test_defs_north_america.rb +4 -3
  160. data/test/defs/test_defs_nyse.rb +2 -1
  161. data/test/defs/test_defs_pl.rb +1 -0
  162. data/test/defs/test_defs_ro.rb +11 -11
  163. data/test/defs/test_defs_scandinavia.rb +12 -8
  164. data/test/defs/test_defs_se.rb +3 -2
  165. data/test/defs/test_defs_sg.rb +2 -1
  166. data/test/defs/test_defs_vi.rb +1 -1
  167. data/test/defs/test_defs_za.rb +3 -2
  168. data/test/holidays/date_calculator/test_weekend_modifier.rb +11 -0
  169. data/test/holidays/definition/context/test_generator.rb +64 -5
  170. data/test/holidays/definition/context/test_merger.rb +5 -2
  171. data/test/holidays/definition/decorator/test_custom_method_proc.rb +113 -0
  172. data/test/holidays/definition/decorator/test_custom_method_source.rb +96 -0
  173. data/test/holidays/definition/parser/test_custom_method.rb +79 -0
  174. data/test/holidays/definition/repository/test_custom_methods.rb +43 -0
  175. data/test/holidays/definition/repository/test_holidays_by_month.rb +0 -32
  176. data/test/holidays/definition/repository/test_proc_result_cache.rb +84 -0
  177. data/test/holidays/definition/validator/test_custom_method.rb +89 -0
  178. data/test/holidays/option/context/test_parse_options.rb +5 -0
  179. data/test/holidays/test_definition_factory.rb +17 -2
  180. data/test/holidays/use_case/context/test_between.rb +2 -0
  181. data/test/test_all_regions.rb +7 -49
  182. data/test/test_custom_holidays.rb +8 -2
  183. data/test/test_helper.rb +9 -2
  184. data/test/test_holidays.rb +9 -29
  185. metadata +46 -11
  186. data/lib/generated_definitions/fed_ex.rb +0 -41
  187. data/test/holidays/definition/repository/test_proc_cache.rb +0 -29
  188. data/test/test_parse_definitions.rb +0 -30
@@ -20,16 +20,31 @@ module Holidays
20
20
  # 0100 0.125000 0.000000 0.125000 ( 0.125000)
21
21
  # 1000 1.234000 0.000000 1.234000 ( 1.234000)
22
22
  # 5000 6.094000 0.031000 6.125000 ( 6.141000)
23
- class ProcCache
23
+ class ProcResultCache
24
24
  def initialize
25
25
  @proc_cache = {}
26
26
  end
27
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]
28
+ def lookup(function, *function_arguments)
29
+ validate!(function, function_arguments)
30
+
31
+ proc_key = build_proc_key(function, function_arguments)
32
+ @proc_cache[proc_key] = function.call(*function_arguments) unless @proc_cache[proc_key]
31
33
  @proc_cache[proc_key]
32
34
  end
35
+
36
+ private
37
+
38
+ def validate!(function, function_arguments)
39
+ raise ArgumentError.new("function must be a proc") unless function.is_a?(Proc)
40
+ function_arguments.each do |arg|
41
+ raise ArgumentError.new("function arguments '#{function_arguments}' must contain either integers or dates") unless arg.is_a?(Integer) || arg.is_a?(Date)
42
+ end
43
+ end
44
+
45
+ def build_proc_key(function, function_arguments)
46
+ Digest::MD5.hexdigest("#{function.to_s}_#{function_arguments.join('_')}")
47
+ end
33
48
  end
34
49
  end
35
50
  end
@@ -0,0 +1,31 @@
1
+ module Holidays
2
+ module Definition
3
+ module Validator
4
+ class CustomMethod
5
+ VALID_ARGUMENTS = ["date", "year", "month", "day"]
6
+
7
+ def valid?(m)
8
+ valid_name?(m[:name]) &&
9
+ valid_arguments?(m[:arguments]) &&
10
+ valid_source?(m[:source])
11
+ end
12
+
13
+ private
14
+
15
+ def valid_name?(name)
16
+ !name.nil? && !name.empty?
17
+ end
18
+
19
+ def valid_arguments?(arguments)
20
+ arguments.split(",").all? { |arg|
21
+ arg == arg.chomp && VALID_ARGUMENTS.include?(arg.strip)
22
+ }
23
+ end
24
+
25
+ def valid_source?(source)
26
+ !source.nil? && !source.empty?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,35 +1,67 @@
1
1
  require 'holidays/definition/context/generator'
2
2
  require 'holidays/definition/context/merger'
3
+ require 'holidays/definition/decorator/custom_method_proc'
4
+ require 'holidays/definition/decorator/custom_method_source'
5
+ require 'holidays/definition/parser/custom_method'
3
6
  require 'holidays/definition/repository/holidays_by_month'
4
7
  require 'holidays/definition/repository/regions'
5
8
  require 'holidays/definition/repository/cache'
6
- require 'holidays/definition/repository/proc_cache'
9
+ require 'holidays/definition/repository/proc_result_cache'
10
+ require 'holidays/definition/repository/custom_methods'
11
+ require 'holidays/definition/validator/custom_method'
7
12
  require 'holidays/definition/validator/region'
8
13
 
9
14
  module Holidays
10
15
  module DefinitionFactory
11
16
  class << self
12
17
  def file_parser
13
- Definition::Context::Generator.new
18
+ Definition::Context::Generator.new(
19
+ custom_method_parser,
20
+ custom_method_source_decorator,
21
+ custom_methods_repository,
22
+ )
14
23
  end
15
24
 
16
25
  def source_generator
17
- Definition::Context::Generator.new
26
+ Definition::Context::Generator.new(
27
+ custom_method_parser,
28
+ custom_method_source_decorator,
29
+ custom_methods_repository,
30
+ )
18
31
  end
19
32
 
20
33
  def merger
21
34
  Definition::Context::Merger.new(
22
35
  holidays_by_month_repository,
23
- regions_repository
36
+ regions_repository,
37
+ custom_methods_repository,
24
38
  )
25
39
  end
26
40
 
41
+ def custom_method_parser
42
+ Definition::Parser::CustomMethod.new(
43
+ custom_method_validator,
44
+ )
45
+ end
46
+
47
+ def custom_method_proc_decorator
48
+ Definition::Decorator::CustomMethodProc.new
49
+ end
50
+
51
+ def custom_method_source_decorator
52
+ Definition::Decorator::CustomMethodSource.new
53
+ end
54
+
27
55
  def region_validator
28
56
  Definition::Validator::Region.new(
29
57
  regions_repository
30
58
  )
31
59
  end
32
60
 
61
+ def custom_method_validator
62
+ Definition::Validator::CustomMethod.new
63
+ end
64
+
33
65
  def holidays_by_month_repository
34
66
  @holidays_repo ||= Definition::Repository::HolidaysByMonth.new
35
67
  end
@@ -42,8 +74,12 @@ module Holidays
42
74
  @cache_repo ||= Definition::Repository::Cache.new
43
75
  end
44
76
 
45
- def proc_cache_repository
46
- @proc_cache_repo ||= Definition::Repository::ProcCache.new
77
+ def proc_result_cache_repository
78
+ @proc_result_cache_repo ||= Definition::Repository::ProcResultCache.new
79
+ end
80
+
81
+ def custom_methods_repository
82
+ @custom_methods_repository ||= Definition::Repository::CustomMethods.new
47
83
  end
48
84
  end
49
85
  end
@@ -0,0 +1,6 @@
1
+ module Holidays
2
+ class Error < StandardError; end
3
+
4
+ class FunctionNotFound < Error; end
5
+ class UnknownRegionError < Error ; end
6
+ end
@@ -0,0 +1,57 @@
1
+ module Holidays
2
+ class LoadAllDefinitions
3
+ class << self
4
+ def call
5
+ path = File.expand_path(File.dirname(__FILE__)) + "/../#{Holidays::DEFINITIONS_PATH}/"
6
+
7
+ Dir.foreach(path) do |item|
8
+ next if item == '.' or item == '..'
9
+
10
+ target = path+item
11
+ next if File.extname(target) != '.rb'
12
+
13
+ require target
14
+ end
15
+
16
+ #FIXME I need a better way to do this. I'm thinking of putting these 'common' methods
17
+ # into some kind of definition file so it can be loaded automatically but I'm afraid
18
+ # of making that big of a breaking API change since these are public. For the time
19
+ # being I'll load them manually like this.
20
+ global_methods = {
21
+ "easter(year)" => gregorian_easter.method(:calculate_easter_for).to_proc,
22
+ "orthodox_easter(year)" => gregorian_easter.method(:calculate_orthodox_easter_for).to_proc,
23
+ "orthodox_easter_julian(year)" => julian_easter.method(:calculate_orthodox_easter_for).to_proc,
24
+ "to_monday_if_sunday(date)" => weekend_modifier.method(:to_monday_if_sunday).to_proc,
25
+ "to_monday_if_weekend(date)" => weekend_modifier.method(:to_monday_if_weekend).to_proc,
26
+ "to_weekday_if_boxing_weekend(date)" => weekend_modifier.method(:to_weekday_if_boxing_weekend).to_proc,
27
+ "to_weekday_if_boxing_weekend_from_year(year)" => weekend_modifier.method(:to_weekday_if_boxing_weekend_from_year).to_proc,
28
+ "to_weekday_if_weekend(date)" => weekend_modifier.method(:to_weekday_if_weekend).to_proc,
29
+ "calculate_day_of_month(year, month, day, wday)" => day_of_month_calculator.method(:call).to_proc,
30
+ "to_weekday_if_boxing_weekend_from_year_or_to_tuesday_if_monday(year)" => weekend_modifier.method(:to_weekday_if_boxing_weekend_from_year_or_to_tuesday_if_monday).to_proc,
31
+ "xmas_to_weekday_if_weekend(year)" => weekend_modifier.method(:xmas_to_weekday_if_weekend).to_proc,
32
+ "to_tuesday_if_sunday_or_monday_if_saturday(date)" => weekend_modifier.method(:to_tuesday_if_sunday_or_monday_if_saturday).to_proc,
33
+ }
34
+
35
+ Holidays::DefinitionFactory.custom_methods_repository.add(global_methods)
36
+ end
37
+
38
+ private
39
+
40
+ def gregorian_easter
41
+ DateCalculatorFactory::Easter::Gregorian.easter_calculator
42
+ end
43
+
44
+ def julian_easter
45
+ DateCalculatorFactory::Easter::Julian.easter_calculator
46
+ end
47
+
48
+ def weekend_modifier
49
+ DateCalculatorFactory.weekend_modifier
50
+ end
51
+
52
+ def day_of_month_calculator
53
+ DateCalculatorFactory.day_of_month_calculator
54
+ end
55
+ end
56
+ end
57
+ end
@@ -2,9 +2,10 @@ module Holidays
2
2
  module Option
3
3
  module Context
4
4
  class ParseOptions
5
- def initialize(regions_repo, region_validator)
5
+ def initialize(regions_repo, region_validator, definition_merger)
6
6
  @regions_repo = regions_repo
7
7
  @region_validator = region_validator
8
+ @definition_merger = definition_merger
8
9
  end
9
10
 
10
11
  # Returns [(arr)regions, (bool)observed, (bool)informal]
@@ -23,12 +24,12 @@ module Holidays
23
24
 
24
25
  private
25
26
 
26
- attr_reader :regions_repo, :region_validator
27
+ attr_reader :regions_repo, :region_validator, :definition_merger
27
28
 
28
29
  # Check regions against list of supported regions and return an array of
29
30
  # symbols.
30
31
  #
31
- # If a wildcard region is found (e.g. <tt>:ca_</tt>) it is expanded into all
32
+ # If a wildcard region is found (e.g. :ca_) it is expanded into all
32
33
  # of its available sub regions.
33
34
  def parse_regions!(regions)
34
35
  regions = [regions] unless regions.kind_of?(Array)
@@ -49,20 +50,19 @@ module Holidays
49
50
 
50
51
  regions.flatten!
51
52
 
52
- require "#{DEFINITIONS_PATH}/north_america" if regions.include?(:us) # special case for north_america/US cross-linking
53
+ load_definition_data("north_america") if regions.include?(:us) # special case for north_america/US cross-linking
53
54
 
54
55
  regions.each do |region|
55
- unless region == :any or regions_repo.exists?(region)
56
- region_definition = "#{DEFINITIONS_PATH}/#{region.to_s}"
56
+ unless region == :any || regions_repo.exists?(region)
57
57
  begin
58
- require region_definition #FIXME This is unacceptable, we can't be loading external files while parsing options
59
- rescue LoadError
58
+ load_definition_data(region.to_s)
59
+ rescue NameError => e
60
60
  # This could be a sub region that does not have any holiday
61
61
  # definitions of its own; try to load the containing region instead.
62
62
  if region.to_s =~ /_/
63
63
  load_containing_region(region.to_s)
64
64
  else
65
- raise UnknownRegionError, "Could not load #{region_definition}"
65
+ raise UnknownRegionError.new(e), "Could not load #{region.to_s}"
66
66
  end
67
67
  end
68
68
  end
@@ -81,15 +81,25 @@ module Holidays
81
81
  # and load its definition. (Common code factored out from parse_regions)
82
82
  def load_containing_region(sub_reg)
83
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
84
+
85
+ return if regions_repo.exists?(prefix.to_sym)
86
+
87
+ begin
88
+ load_definition_data(prefix)
89
+ rescue NameError => e
90
+ raise UnknownRegionError.new(e), "Could not load region prefix: #{prefix.to_s}, original subregion: #{sub_reg.to_s}"
91
91
  end
92
92
  end
93
+
94
+ def load_definition_data(region)
95
+ target_region_module = Module.const_get("Holidays").const_get(region.upcase)
96
+
97
+ definition_merger.call(
98
+ target_region_module.defined_regions,
99
+ target_region_module.holidays_by_month,
100
+ target_region_module.custom_methods,
101
+ )
102
+ end
93
103
  end
94
104
  end
95
105
  end
@@ -7,6 +7,7 @@ module Holidays
7
7
  Option::Context::ParseOptions.new(
8
8
  DefinitionFactory.regions_repository,
9
9
  DefinitionFactory.region_validator,
10
+ DefinitionFactory.merger,
10
11
  )
11
12
  end
12
13
  end
@@ -2,10 +2,11 @@ module Holidays
2
2
  module UseCase
3
3
  module Context
4
4
  class Between
5
- def initialize(holidays_by_month_repo, day_of_month_calculator, proc_cache_repo)
5
+ def initialize(holidays_by_month_repo, day_of_month_calculator, custom_methods_repo, proc_result_cache_repo)
6
6
  @holidays_by_month_repo = holidays_by_month_repo
7
7
  @day_of_month_calculator = day_of_month_calculator
8
- @proc_cache_repo = proc_cache_repo
8
+ @custom_methods_repo = custom_methods_repo
9
+ @proc_result_cache_repo = proc_result_cache_repo
9
10
  end
10
11
 
11
12
  def call(start_date, end_date, dates_driver, regions, observed, informal)
@@ -18,9 +19,7 @@ module Holidays
18
19
  next unless hbm = holidays_by_month_repo.find_by_month(month)
19
20
  hbm.each do |h|
20
21
  next unless in_region?(regions, h[:regions])
21
-
22
- # Skip informal holidays unless they have been requested
23
- next if h[:type] == :informal and not informal
22
+ next if h[:type] == :informal && !informal
24
23
 
25
24
  # range check feature.
26
25
  if h[:year_ranges]
@@ -45,18 +44,42 @@ module Holidays
45
44
  next unless valid_range_year
46
45
  end
47
46
 
47
+ #FIXME I don't like this entire if/else. If it's a function, do something, else do some
48
+ # weird mday logic? Bollocks. I think this should be a refactor target.
48
49
  if h[:function]
49
- # Holiday definition requires a calculation
50
- result = call_proc(h[:function], year)
50
+ function_arguments = []
51
+
52
+ #FIXME This is a refactor target. We should also allow 'date'. Right now these are the only
53
+ # three things that we allow in. I think having a more testable, robust approach here is vital.
54
+ if h[:function_arguments].include?(:year)
55
+ function_arguments << year
56
+ end
57
+
58
+ if h[:function_arguments].include?(:month)
59
+ function_arguments << month
60
+ end
61
+
62
+ if h[:function_arguments].include?(:day)
63
+ function_arguments << h[:mday]
64
+ end
65
+
66
+ result = call_proc(h[:function], *function_arguments)
67
+
68
+ #FIXME This is a dangerous assumption. We should raise an error or something
69
+ # if these procs return something unexpected.
70
+ #
51
71
  # Procs may return either Date or an integer representing mday
52
72
  if result.kind_of?(Date)
73
+ if h[:function_modifier]
74
+ result = result + h[:function_modifier] # NOTE: This could be a positive OR negative number.
75
+ end
76
+
53
77
  month = result.month
54
78
  mday = result.mday
55
79
  else
56
80
  mday = result
57
81
  end
58
82
  else
59
- # Calculate the mday
60
83
  mday = h[:mday] || day_of_month_calculator.call(year, month, h[:week], h[:wday])
61
84
  end
62
85
 
@@ -65,9 +88,10 @@ module Holidays
65
88
  date = Date.civil(year, month, mday)
66
89
  rescue; next; end
67
90
 
68
- # If the :observed option is set, calculate the date when the holiday
69
- # is observed.
70
- if observed and h[:observed]
91
+ #FIXME We should be checking the function arguments and passing in what is specified.
92
+ # Right now all 'observed' functions require 'date' but that is by convention. Nothing
93
+ # is requiring that. We should be more explicit.
94
+ if observed && h[:observed]
71
95
  date = call_proc(h[:observed], date)
72
96
  end
73
97
 
@@ -83,7 +107,7 @@ module Holidays
83
107
 
84
108
  private
85
109
 
86
- attr_reader :holidays_by_month_repo, :day_of_month_calculator, :proc_cache_repo
110
+ attr_reader :holidays_by_month_repo, :day_of_month_calculator, :custom_methods_repo, :proc_result_cache_repo
87
111
 
88
112
  def validate!(start_date, end_date, dates_driver, regions)
89
113
  raise ArgumentError unless start_date
@@ -98,8 +122,11 @@ module Holidays
98
122
  raise ArgumentError if regions.nil? || regions.empty?
99
123
  end
100
124
 
101
- def call_proc(function, year)
102
- proc_cache_repo.lookup(function, year)
125
+ def call_proc(function_id, *arguments)
126
+ function = custom_methods_repo.find(function_id)
127
+ raise Holidays::FunctionNotFound.new("Unable to find function with id '#{function_id}'") if function.nil?
128
+
129
+ proc_result_cache_repo.lookup(function, *arguments)
103
130
  end
104
131
 
105
132
  # Check sub regions.
@@ -8,7 +8,8 @@ module Holidays
8
8
  UseCase::Context::Between.new(
9
9
  DefinitionFactory.holidays_by_month_repository,
10
10
  DateCalculatorFactory.day_of_month_calculator,
11
- DefinitionFactory.proc_cache_repository,
11
+ DefinitionFactory.custom_methods_repository,
12
+ DefinitionFactory.proc_result_cache_repository,
12
13
  )
13
14
  end
14
15
 
@@ -1,3 +1,3 @@
1
1
  module Holidays
2
- VERSION = '3.3.0'
2
+ VERSION = '4.0.0'
3
3
  end
@@ -0,0 +1,24 @@
1
+ months:
2
+ 0:
3
+ - name: Custom Holiday
4
+ regions: [custom_single_file]
5
+ function: custom_method(year, month)
6
+ function_modifier: 5
7
+ 6:
8
+ - name: Company Founding
9
+ regions: [custom_single_file]
10
+ mday: 20
11
+ methods:
12
+ custom_method:
13
+ arguments: year, month
14
+ source: |
15
+ d = Date.civil(year, month, 1)
16
+ d + 2
17
+ tests: |
18
+ {Date.civil(2013,6,20) => 'Company Founding'}.each do |date, name|
19
+ assert_equal name, (Holidays.on(date, :custom_single_file)[0] || {})[:name]
20
+ end
21
+
22
+ {Date.civil(2015, 1, 1) => 'Custom Holiday'}.each do |date, name|
23
+ assert_equal name, (Holidays.on(date, :custom_single_file)[0] || {}[:name]
24
+ end