holidays 6.6.1 → 8.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +25 -0
  3. data/CHANGELOG.md +72 -0
  4. data/README.md +10 -6
  5. data/doc/CONTRIBUTING.md +2 -1
  6. data/holidays.gemspec +5 -5
  7. data/lib/generated_definitions/MANIFEST +11 -5
  8. data/lib/generated_definitions/REGIONS.rb +2 -2
  9. data/lib/generated_definitions/ar.rb +28 -9
  10. data/lib/generated_definitions/at.rb +2 -2
  11. data/lib/generated_definitions/au.rb +47 -10
  12. data/lib/generated_definitions/be_fr.rb +2 -2
  13. data/lib/generated_definitions/be_nl.rb +2 -2
  14. data/lib/generated_definitions/bg.rb +2 -2
  15. data/lib/generated_definitions/br.rb +2 -2
  16. data/lib/generated_definitions/ca.rb +25 -17
  17. data/lib/generated_definitions/ch.rb +15 -3
  18. data/lib/generated_definitions/cl.rb +7 -7
  19. data/lib/generated_definitions/co.rb +2 -2
  20. data/lib/generated_definitions/cr.rb +2 -2
  21. data/lib/generated_definitions/cz.rb +2 -2
  22. data/lib/generated_definitions/de.rb +11 -7
  23. data/lib/generated_definitions/dk.rb +3 -3
  24. data/lib/generated_definitions/{ecb_target.rb → ecbtarget.rb} +10 -10
  25. data/lib/generated_definitions/ee.rb +2 -2
  26. data/lib/generated_definitions/el.rb +2 -2
  27. data/lib/generated_definitions/es.rb +6 -4
  28. data/lib/generated_definitions/europe.rb +119 -32
  29. data/lib/generated_definitions/{federal_reserve.rb → federalreserve.rb} +15 -14
  30. data/lib/generated_definitions/federalreservebanks.rb +35 -0
  31. data/lib/generated_definitions/fedex.rb +2 -2
  32. data/lib/generated_definitions/fi.rb +2 -2
  33. data/lib/generated_definitions/fr.rb +4 -4
  34. data/lib/generated_definitions/gb.rb +15 -7
  35. data/lib/generated_definitions/ge.rb +2 -2
  36. data/lib/generated_definitions/hk.rb +2 -2
  37. data/lib/generated_definitions/hr.rb +10 -8
  38. data/lib/generated_definitions/hu.rb +4 -3
  39. data/lib/generated_definitions/ie.rb +2 -2
  40. data/lib/generated_definitions/is.rb +2 -2
  41. data/lib/generated_definitions/it.rb +16 -7
  42. data/lib/generated_definitions/jp.rb +31 -16
  43. data/lib/generated_definitions/kr.rb +2 -2
  44. data/lib/generated_definitions/kz.rb +38 -0
  45. data/lib/generated_definitions/li.rb +2 -2
  46. data/lib/generated_definitions/lt.rb +2 -2
  47. data/lib/generated_definitions/lu.rb +4 -2
  48. data/lib/generated_definitions/lv.rb +56 -0
  49. data/lib/generated_definitions/ma.rb +2 -2
  50. data/lib/generated_definitions/mt_en.rb +2 -2
  51. data/lib/generated_definitions/mt_mt.rb +2 -2
  52. data/lib/generated_definitions/mx.rb +7 -7
  53. data/lib/generated_definitions/my.rb +2 -2
  54. data/lib/generated_definitions/nerc.rb +2 -2
  55. data/lib/generated_definitions/ng.rb +33 -0
  56. data/lib/generated_definitions/nl.rb +2 -2
  57. data/lib/generated_definitions/no.rb +2 -2
  58. data/lib/generated_definitions/{north_america.rb → northamerica.rb} +34 -26
  59. data/lib/generated_definitions/nyse.rb +3 -2
  60. data/lib/generated_definitions/nz.rb +41 -3
  61. data/lib/generated_definitions/pe.rb +2 -2
  62. data/lib/generated_definitions/ph.rb +2 -2
  63. data/lib/generated_definitions/pl.rb +2 -2
  64. data/lib/generated_definitions/pt.rb +2 -2
  65. data/lib/generated_definitions/ro.rb +6 -3
  66. data/lib/generated_definitions/rs_cyrl.rb +3 -3
  67. data/lib/generated_definitions/rs_la.rb +3 -3
  68. data/lib/generated_definitions/ru.rb +2 -2
  69. data/lib/generated_definitions/scandinavia.rb +3 -3
  70. data/lib/generated_definitions/se.rb +2 -2
  71. data/lib/generated_definitions/sg.rb +2 -2
  72. data/lib/generated_definitions/si.rb +4 -3
  73. data/lib/generated_definitions/sk.rb +2 -2
  74. data/lib/generated_definitions/{south_america.rb → southamerica.rb} +32 -13
  75. data/lib/generated_definitions/th.rb +36 -0
  76. data/lib/generated_definitions/tn.rb +2 -2
  77. data/lib/generated_definitions/tr.rb +7 -5
  78. data/lib/generated_definitions/ua.rb +37 -0
  79. data/lib/generated_definitions/{united_nations.rb → unitednations.rb} +61 -61
  80. data/lib/generated_definitions/ups.rb +2 -2
  81. data/lib/generated_definitions/us.rb +9 -9
  82. data/lib/generated_definitions/ve.rb +2 -2
  83. data/lib/generated_definitions/vi.rb +2 -2
  84. data/lib/generated_definitions/za.rb +3 -3
  85. data/lib/holidays/definition/context/generator.rb +23 -70
  86. data/lib/holidays/definition/generator/module.rb +54 -0
  87. data/lib/holidays/definition/generator/regions.rb +1 -1
  88. data/lib/holidays/definition/repository/holidays_by_month.rb +9 -1
  89. data/lib/holidays/factory/definition.rb +7 -0
  90. data/lib/holidays/finder/context/search.rb +35 -31
  91. data/lib/holidays/finder/rules/year_range.rb +30 -54
  92. data/lib/holidays/version.rb +1 -1
  93. data/lib/holidays.rb +2 -0
  94. data/test/coverage_report.rb +23 -5
  95. data/test/data/test_custom_year_range_holiday_defs.yaml +6 -10
  96. data/test/data/test_multiple_regions_with_conflicts_region_1.yaml +38 -0
  97. data/test/data/test_multiple_regions_with_conflicts_region_2.yaml +38 -0
  98. data/test/defs/test_defs_ar.rb +20 -4
  99. data/test/defs/test_defs_au.rb +22 -0
  100. data/test/defs/test_defs_ca.rb +71 -8
  101. data/test/defs/test_defs_ch.rb +4 -0
  102. data/test/defs/test_defs_co.rb +3 -3
  103. data/test/defs/test_defs_de.rb +8 -0
  104. data/test/defs/test_defs_dk.rb +4 -0
  105. data/test/defs/{test_defs_ecb_target.rb → test_defs_ecbtarget.rb} +11 -11
  106. data/test/defs/test_defs_es.rb +2 -0
  107. data/test/defs/test_defs_europe.rb +233 -14
  108. data/test/defs/test_defs_federalreserve.rb +119 -0
  109. data/test/defs/test_defs_federalreservebanks.rb +251 -0
  110. data/test/defs/test_defs_fr.rb +3 -3
  111. data/test/defs/test_defs_gb.rb +42 -0
  112. data/test/defs/test_defs_hr.rb +6 -6
  113. data/test/defs/test_defs_hu.rb +12 -4
  114. data/test/defs/test_defs_it.rb +20 -0
  115. data/test/defs/test_defs_jp.rb +22 -2
  116. data/test/defs/test_defs_kz.rb +39 -0
  117. data/test/defs/test_defs_lu.rb +6 -0
  118. data/test/defs/test_defs_lv.rb +98 -0
  119. data/test/defs/test_defs_mx.rb +3 -1
  120. data/test/defs/test_defs_ng.rb +29 -0
  121. data/test/defs/{test_defs_north_america.rb → test_defs_northamerica.rb} +85 -20
  122. data/test/defs/test_defs_nyse.rb +7 -0
  123. data/test/defs/test_defs_nz.rb +4 -0
  124. data/test/defs/test_defs_ro.rb +14 -0
  125. data/test/defs/test_defs_rs_cyrl.rb +1 -1
  126. data/test/defs/test_defs_rs_la.rb +1 -1
  127. data/test/defs/test_defs_scandinavia.rb +4 -0
  128. data/test/defs/{test_defs_south_america.rb → test_defs_southamerica.rb} +25 -9
  129. data/test/defs/test_defs_th.rb +33 -0
  130. data/test/defs/test_defs_tr.rb +7 -0
  131. data/test/defs/test_defs_ua.rb +41 -0
  132. data/test/defs/{test_defs_united_nations.rb → test_defs_unitednations.rb} +3 -3
  133. data/test/defs/test_defs_us.rb +11 -11
  134. data/test/holidays/core_extensions/test_date.rb +3 -2
  135. data/test/holidays/definition/context/test_generator.rb +17 -20
  136. data/test/holidays/definition/generator/test_module.rb +268 -0
  137. data/test/holidays/definition/repository/test_holidays_by_month.rb +121 -1
  138. data/test/holidays/finder/rules/test_year_range.rb +43 -47
  139. data/test/integration/test_available_regions.rb +1 -1
  140. data/test/integration/test_custom_year_range_holidays.rb +0 -7
  141. data/test/integration/test_holidays.rb +2 -36
  142. data/test/integration/test_holidays_between.rb +11 -1
  143. data/test/integration/test_multiple_regions_with_conflict.rb +29 -0
  144. data/test/integration/test_nonstandard_regions.rb +25 -0
  145. metadata +61 -32
  146. data/test/defs/test_defs_federal_reserve.rb +0 -113
@@ -11,12 +11,13 @@ module Holidays
11
11
  module Definition
12
12
  module Context
13
13
  class Generator
14
- def initialize(custom_method_parser, custom_method_source_decorator, custom_methods_repository, test_parser, test_source_generator)
14
+ def initialize(custom_method_parser, custom_method_source_decorator, custom_methods_repository, test_parser, test_source_generator, module_source_generator)
15
15
  @custom_method_parser = custom_method_parser
16
16
  @custom_method_source_decorator = custom_method_source_decorator
17
17
  @custom_methods_repository = custom_methods_repository
18
18
  @test_parser = test_parser
19
19
  @test_source_generator = test_source_generator
20
+ @module_source_generator = module_source_generator
20
21
  end
21
22
 
22
23
  def parse_definition_files(files)
@@ -66,7 +67,7 @@ module Holidays
66
67
  custom_method_string << @custom_method_source_decorator.call(code) + ",\n\n"
67
68
  end
68
69
 
69
- module_src = generate_module_src(module_name, files, regions, month_strings, custom_method_string)
70
+ module_src = @module_source_generator.call(module_name, files, regions, month_strings, custom_method_string)
70
71
  test_src = @test_source_generator.call(module_name, files, tests)
71
72
 
72
73
  return module_src, test_src || ''
@@ -86,16 +87,27 @@ module Holidays
86
87
  rule = {}
87
88
 
88
89
  definition.each do |key, val|
90
+ # Ruby 2.4 doesn't have the `transform_keys` method. Once we drop 2.4 support we can
91
+ # use `val.transform_keys!(&:to_sym) if val.is_a?(Hash)` instead of this `if` statement.
92
+ if val.is_a?(Hash)
93
+ val = val.keys.each_with_object({}) do |k, result|
94
+ result[k.to_sym] = val[k]
95
+ end
96
+ end
97
+
89
98
  rule[key.to_sym] = val
90
99
  end
91
100
 
92
- rule[:regions] = rule[:regions].collect { |r| r.to_sym }
93
- regions << rule[:regions]
101
+ if rule[:year_ranges] && rule[:year_ranges].key?(:between)
102
+ start_year = rule[:year_ranges][:between]["start"].to_i
103
+ end_year = rule[:year_ranges][:between]["end"].to_i
94
104
 
95
- if rule[:year_ranges]
96
- rule[:year_ranges] = clean_year_ranges(rule[:year_ranges])
105
+ rule[:year_ranges][:between] = Range.new(start_year, end_year)
97
106
  end
98
107
 
108
+ rule[:regions] = rule[:regions].collect { |r| r.to_sym }
109
+ regions << rule[:regions]
110
+
99
111
  exists = false
100
112
  rules_by_month[month].each do |ex|
101
113
  if ex[:name] == rule[:name] and ex[:wday] == rule[:wday] and ex[:mday] == rule[:mday] and ex[:week] == rule[:week] and ex[:type] == rule[:type] and ex[:function] == rule[:function] and ex[:observed] == rule[:observed] and ex[:year_ranges] == rule[:year_ranges]
@@ -120,23 +132,6 @@ module Holidays
120
132
  [regions, rules_by_month]
121
133
  end
122
134
 
123
- # In this case we end up parsing a range as "2006..2008" a string. This is codifying
124
- # what we already do...today we parse as a string but when writing out to our final
125
- # generated files it comes out as a range that Ruby interprets. This just puts it in stone
126
- # what we want to do.
127
- def clean_year_ranges(year_ranges)
128
- year_ranges.collect do |year_range|
129
- if year_range["between"]
130
- range = year_range["between"]
131
- if range.is_a?(String)
132
- year_range["between"] = Range.new(*range.split("..").map(&:to_i))
133
- end
134
- end
135
-
136
- year_range
137
- end
138
- end
139
-
140
135
  #FIXME This should really be split out and tested with its own unit tests.
141
136
  def generate_month_definition_strings(rules_by_month, parsed_custom_methods)
142
137
  month_strings = []
@@ -169,19 +164,11 @@ module Holidays
169
164
  string << ":wday => #{rule[:wday]}, :week => #{rule[:week]}, "
170
165
  end
171
166
 
172
- #FIXME I think this should be split out into its own file.
173
- if rule[:year_ranges] && rule[:year_ranges].kind_of?(Array)
174
- year_string = " :year_ranges => ["
175
- len = rule[:year_ranges].length
176
- rule[:year_ranges].each_with_index do |year,index|
177
- year_string << "{:#{year.keys.first} => #{year.values.first}}"
178
- if len == index + 1
179
- year_string << "],"
180
- else
181
- year_string << ","
182
- end
183
- end
184
- string << year_string
167
+ if rule[:year_ranges] && rule[:year_ranges].is_a?(Hash)
168
+ selector = rule[:year_ranges].keys.first
169
+ value = rule[:year_ranges][selector]
170
+
171
+ string << ":year_ranges => { :#{selector} => #{value} },"
185
172
  end
186
173
 
187
174
  if rule[:observed]
@@ -216,40 +203,6 @@ module Holidays
216
203
  method.arguments.collect { |arg| arg.to_sym }
217
204
  end
218
205
  end
219
-
220
- def generate_module_src(module_name, files, regions, month_strings, custom_methods)
221
- module_src = ""
222
-
223
- module_src =<<-EOM
224
- # encoding: utf-8
225
- module Holidays
226
- # This file is generated by the Ruby Holidays gem.
227
- #
228
- # Definitions loaded: #{files.join(', ')}
229
- #
230
- # All the definitions are available at https://github.com/holidays/holidays
231
- module #{module_name.to_s.upcase} # :nodoc:
232
- def self.defined_regions
233
- [:#{regions.join(', :')}]
234
- end
235
-
236
- def self.holidays_by_month
237
- {
238
- #{month_strings.join(",\n")}
239
- }
240
- end
241
-
242
- def self.custom_methods
243
- {
244
- #{custom_methods}
245
- }
246
- end
247
- end
248
- end
249
- EOM
250
-
251
- return module_src
252
- end
253
206
  end
254
207
  end
255
208
  end
@@ -0,0 +1,54 @@
1
+ require 'holidays/errors'
2
+
3
+ module Holidays
4
+ module Definition
5
+ module Generator
6
+ class Module
7
+ def call(module_name, files, regions, month_strings, custom_methods)
8
+ raise ArgumentError.new("module name cannot be nil") if module_name.nil?
9
+ raise ArgumentError.new("module name cannot be blank") if module_name.empty?
10
+
11
+ raise ArgumentError.new("files cannot be nil") if files.nil?
12
+ raise ArgumentError.new("files cannot be empty") if files.empty?
13
+ raise ArgumentError.new("files must all be strings") unless files.all? { |f| f.is_a?(String) }
14
+
15
+ raise ArgumentError.new("regions cannot be nil") if regions.nil?
16
+ raise ArgumentError.new("regions cannot be empty") if regions.empty?
17
+
18
+ raise ArgumentError.new("month strings cannot be nil") if month_strings.nil?
19
+ raise ArgumentError.new("month strings cannot be empty") if month_strings.empty?
20
+
21
+ module_src =<<-EOM
22
+ # encoding: utf-8
23
+ module Holidays
24
+ # This file is generated by the Ruby Holidays gem.
25
+ #
26
+ # Definitions loaded: #{files.join(', ')}
27
+ #
28
+ # All the definitions are available at https://github.com/holidays/holidays
29
+ module #{module_name.to_s.upcase} # :nodoc:
30
+ def self.defined_regions
31
+ [:#{regions.join(', :')}]
32
+ end
33
+
34
+ def self.holidays_by_month
35
+ {
36
+ #{month_strings.join(",\n")}
37
+ }
38
+ end
39
+
40
+ def self.custom_methods
41
+ {
42
+ #{custom_methods}
43
+ }
44
+ end
45
+ end
46
+ end
47
+ EOM
48
+
49
+ module_src
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -2,7 +2,7 @@ module Holidays
2
2
  module Definition
3
3
  module Generator
4
4
  class Regions
5
- # The "ca", "mx", and "us" holiday definitions include the "north_america_informal"
5
+ # The "ca", "mx", and "us" holiday definitions include the "northamericainformal"
6
6
  # holiday definitions, but that does not make these countries subregions of one another.
7
7
  NORTH_AMERICA_REGIONS = %i[ca mx us].freeze
8
8
 
@@ -41,7 +41,15 @@ module Holidays
41
41
  private
42
42
 
43
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] == target_def[:function] && existing_def[:type] == target_def[:type] && existing_def[:observed] == target_def[:observed] && existing_def[:year_ranges] == target_def[:year_ranges]
44
+ existing_def[:name] == target_def[:name] &&
45
+ existing_def[:wday] == target_def[:wday] &&
46
+ existing_def[:mday] == target_def[:mday] &&
47
+ existing_def[:week] == target_def[:week] &&
48
+ existing_def[:function] == target_def[:function] &&
49
+ existing_def[:function_modifier] == target_def[:function_modifier] &&
50
+ existing_def[:type] == target_def[:type] &&
51
+ existing_def[:observed] == target_def[:observed] &&
52
+ existing_def[:year_ranges] == target_def[:year_ranges]
45
53
  end
46
54
  end
47
55
  end
@@ -5,6 +5,7 @@ require 'holidays/definition/context/load'
5
5
  require 'holidays/definition/decorator/custom_method_proc'
6
6
  require 'holidays/definition/decorator/custom_method_source'
7
7
  require 'holidays/definition/decorator/test'
8
+ require 'holidays/definition/generator/module'
8
9
  require 'holidays/definition/generator/regions'
9
10
  require 'holidays/definition/generator/test'
10
11
  require 'holidays/definition/parser/custom_method'
@@ -29,6 +30,7 @@ module Holidays
29
30
  custom_methods_repository,
30
31
  test_parser,
31
32
  test_generator,
33
+ module_generator,
32
34
  )
33
35
  end
34
36
 
@@ -39,6 +41,7 @@ module Holidays
39
41
  custom_methods_repository,
40
42
  test_parser,
41
43
  test_generator,
44
+ module_generator,
42
45
  )
43
46
  end
44
47
 
@@ -115,6 +118,10 @@ module Holidays
115
118
  )
116
119
  end
117
120
 
121
+ def module_generator
122
+ Holidays::Definition::Generator::Module.new
123
+ end
124
+
118
125
  def test_generator
119
126
  Holidays::Definition::Generator::Test.new(
120
127
  test_decorator,
@@ -24,39 +24,11 @@ module Holidays
24
24
  next unless @rules[:year_range].call(year, h[:year_ranges])
25
25
  end
26
26
 
27
- current_month = month
28
- current_day = h[:mday]
29
-
30
- if h[:function]
31
- result = @custom_method_processor.call(
32
- build_custom_method_input(year, current_month, current_day, h[:regions]),
33
- h[:function], h[:function_arguments], h[:function_modifier],
34
- )
35
-
36
- #FIXME The result should always be present, see https://github.com/holidays/holidays/issues/204 for more information
37
- if result
38
- current_month = result.month
39
- current_day = result.mday
40
- else
41
- current_month = nil
42
- current_day = nil
43
- end
44
- else
45
- current_day = h[:mday] || @day_of_month_calculator.call(year, current_month, h[:week], h[:wday])
46
- end
47
-
48
- # Silently skip bad mdays
49
- #TODO Should we be doing something different here? We have no concept of logging right now. Maybe we should add it?
50
- begin
51
- date = Date.civil(year, current_month, current_day)
52
- rescue; next; end
27
+ date = build_date(year, month, h)
28
+ next unless date
53
29
 
54
30
  if observed_set?(options) && h[:observed]
55
- date = @custom_method_processor.call(
56
- build_custom_method_input(date.year, date.month, date.day, regions),
57
- h[:observed],
58
- [:date],
59
- )
31
+ date = build_observed_date(date, regions, h)
60
32
  end
61
33
 
62
34
  holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
@@ -93,6 +65,30 @@ module Holidays
93
65
  options && options.include?(:observed) == true
94
66
  end
95
67
 
68
+ def build_date(year, month, h)
69
+ if h[:function]
70
+ holiday = custom_holiday(year, month, h)
71
+ #FIXME The result should always be present, see https://github.com/holidays/holidays/issues/204 for more information
72
+ current_month = holiday&.month
73
+ current_day = holiday&.mday
74
+ else
75
+ current_month = month
76
+ current_day = h[:mday] || @day_of_month_calculator.call(year, month, h[:week], h[:wday])
77
+ end
78
+
79
+ # Silently skip bad mdays
80
+ #TODO Should we be doing something different here? We have no concept of logging right now. Maybe we should add it?
81
+ Date.civil(year, current_month, current_day) rescue nil
82
+ end
83
+
84
+ def custom_holiday(year, month, h)
85
+ @custom_method_processor.call(
86
+ #FIXME This seems like a bug, we seem to expect the day in here in the au defs?
87
+ build_custom_method_input(year, month, h[:mday], h[:regions]),
88
+ h[:function], h[:function_arguments], h[:function_modifier],
89
+ )
90
+ end
91
+
96
92
  def build_custom_method_input(year, month, day, regions)
97
93
  {
98
94
  year: year,
@@ -101,6 +97,14 @@ module Holidays
101
97
  region: regions.first, #FIXME This isn't ideal but will work for our current use case...
102
98
  }
103
99
  end
100
+
101
+ def build_observed_date(date, regions, h)
102
+ @custom_method_processor.call(
103
+ build_custom_method_input(date.year, date.month, date.day, regions),
104
+ h[:observed],
105
+ [:date],
106
+ )
107
+ end
104
108
  end
105
109
  end
106
110
  end
@@ -1,49 +1,30 @@
1
- # Please note that only one condition needs to match in order for `call` to return `true.
2
- # See the test file for this class for specific examples.
3
1
  module Holidays
4
2
  module Finder
5
3
  module Rules
6
4
  class YearRange
7
5
  class << self
8
- BEFORE = :before
9
- AFTER = :after
6
+ UNTIL = :until
7
+ FROM = :from
10
8
  LIMITED = :limited
11
9
  BETWEEN = :between
12
10
 
13
- #TODO Can we just accept symbols here? Why accept strings?
14
- VALID_OPERATORS = [
15
- BEFORE, BEFORE.to_s,
16
- AFTER, AFTER.to_s,
17
- LIMITED, LIMITED.to_s,
18
- BETWEEN, BETWEEN.to_s
19
- ]
20
-
21
- def call(target_year, year_range_definitions)
22
- validate!(target_year, year_range_definitions)
23
-
24
- matched = false
25
- year_range_definitions.each do |range_defs|
26
- next unless range_defs.is_a?(Hash) && range_defs.length == 1
27
-
28
- operator = range_defs.keys.first
29
- year_range = range_defs.values.first
30
-
31
- case operator
32
- when BEFORE, BEFORE.to_s
33
- matched = target_year <= year_range
34
- when AFTER, AFTER.to_s
35
- matched = target_year >= year_range
36
- when LIMITED, LIMITED.to_s
37
- if year_range.is_a?(Array)
38
- matched = year_range.include?(target_year)
39
- else
40
- matched = year_range == target_year
41
- end
42
- when BETWEEN, BETWEEN.to_s
43
- matched = year_range.cover?(target_year)
44
- end
45
-
46
- break if matched == true
11
+ def call(target_year, year_range_defs)
12
+ validate!(target_year, year_range_defs)
13
+
14
+ operator = year_range_defs.keys.first
15
+ rule_value = year_range_defs[operator]
16
+
17
+ case operator
18
+ when UNTIL
19
+ matched = target_year <= rule_value
20
+ when FROM
21
+ matched = target_year >= rule_value
22
+ when LIMITED
23
+ matched = rule_value.include?(target_year)
24
+ when BETWEEN
25
+ matched = rule_value.cover?(target_year)
26
+ else
27
+ matched = false
47
28
  end
48
29
 
49
30
  matched
@@ -54,25 +35,20 @@ module Holidays
54
35
  def validate!(target_year, year_ranges)
55
36
  raise ArgumentError.new("target_year must be a number") unless target_year.is_a?(Integer)
56
37
  raise ArgumentError.new("year_ranges cannot be missing") if year_ranges.nil? || year_ranges.empty?
38
+ raise ArgumentError.new("year_ranges must contain a hash with a single operator") unless year_ranges.is_a?(Hash) && year_ranges.size == 1
57
39
 
58
- year_ranges.each do |range|
59
- raise ArgumentError.new("year_ranges must include only hashes") unless range.is_a?(Hash)
60
- raise ArgumentError.new("year_ranges cannot include empty hashes") if range.empty?
61
- raise ArgumentError.new("year_ranges entries can only include one operator") unless range.count == 1
62
-
63
- operator = range.keys.first
64
- range = range.values.first
40
+ operator = year_ranges.keys.first
41
+ value = year_ranges[operator]
65
42
 
66
- raise ArgumentError.new("Invalid operator found: '#{operator}'") unless VALID_OPERATORS.include?(operator)
43
+ raise ArgumentError.new("Invalid operator found: '#{operator}'") unless [UNTIL, FROM, LIMITED, BETWEEN].include?(operator)
67
44
 
68
- case operator
69
- when BEFORE, BEFORE.to_s, AFTER, AFTER.to_s
70
- raise ArgumentError.new(":before and :after operator value must be a number, received: '#{range}'") unless range.is_a?(Integer)
71
- when LIMITED, LIMITED.to_s
72
- raise ArgumentError.new(":limited operator value must be an array, received: '#{range}'") unless range.is_a?(Array) || range.is_a?(Integer)
73
- when BETWEEN, BETWEEN.to_s
74
- raise ArgumentError.new(":between operator value must be a range, received: '#{range}'") unless range.is_a?(Range)
75
- end
45
+ case operator
46
+ when UNTIL, FROM
47
+ raise ArgumentError.new("#{UNTIL} and #{FROM} operator value must be a number, received: '#{value}'") unless value.is_a?(Integer)
48
+ when LIMITED
49
+ raise ArgumentError.new(":limited operator value must be an array containing at least one integer value, received: '#{value}'") unless value.is_a?(Array) && value.size >= 1 && value.all? { |v| v.is_a?(Integer) }
50
+ when BETWEEN
51
+ raise ArgumentError.new(":between operator value must be a range, received: '#{value}'") unless value.is_a?(Range)
76
52
  end
77
53
  end
78
54
  end
@@ -1,3 +1,3 @@
1
1
  module Holidays
2
- VERSION = '6.6.1'
2
+ VERSION = '8.7.1'
3
3
  end
data/lib/holidays.rb CHANGED
@@ -40,6 +40,8 @@ module Holidays
40
40
 
41
41
  start_date, end_date = get_date(start_date), get_date(end_date)
42
42
 
43
+ raise ArgumentError if end_date < start_date
44
+
43
45
  if cached_holidays = Factory::Definition.cache_repository.find(start_date, end_date, options)
44
46
  return cached_holidays
45
47
  end
@@ -1,8 +1,26 @@
1
1
  require 'simplecov'
2
2
 
3
- SimpleCov.minimum_coverage 99
3
+ # For reasons I don't understand jruby implementations report lower coverage
4
+ # than other ruby versions. Ruby 2.5.3, for instance, is at 92%.
5
+ #
6
+ # We set the floor based on jruby so that all automated tests pass on Travis CI.
7
+ SimpleCov.minimum_coverage 89
8
+
9
+ SimpleCov.add_filter [
10
+ # Apparently simplecov doesn't automatically filter 'spec' or 'test' so we
11
+ # have to do it manually.
12
+ 'test',
13
+
14
+ # Only filtered because I tend to not see value in testing factories.
15
+ 'lib/holidays/factory/',
16
+
17
+ # jruby coverage flips out here and doesn't count much of the large date
18
+ # arrays used by this class. This results in an extremely low reported
19
+ # coverage for this specific file but only in jruby, not other ruby versions.
20
+ # Since it obliterates coverage percentages I'll filter it until I can come
21
+ # up with a solution.
22
+ 'lib/holidays/date_calculator/lunar_date.rb',
23
+ ]
24
+
4
25
  SimpleCov.coverage_dir 'reports/coverage'
5
- SimpleCov.start do
6
- add_filter 'lib/generated_definitions/'
7
- add_filter 'lib/holidays/factory/'
8
- end
26
+ SimpleCov.start
@@ -4,28 +4,24 @@ months:
4
4
  regions: [custom_year_range_file]
5
5
  mday: 1
6
6
  year_ranges:
7
- - after: 2016
7
+ from: 2016
8
8
  - name: before_year
9
9
  regions: [custom_year_range_file]
10
10
  mday: 2
11
11
  year_ranges:
12
- - before: 2017
12
+ until: 2017
13
13
  - name: between_year
14
14
  regions: [custom_year_range_file]
15
15
  mday: 3
16
16
  year_ranges:
17
- - between: 2016..2018
17
+ between:
18
+ start: 2016
19
+ end: 2018
18
20
  - name: limited_year
19
21
  regions: [custom_year_range_file]
20
22
  mday: 4
21
23
  year_ranges:
22
- - limited: [2016,2018,2019]
23
- - name: multiple_conditions
24
- regions: [custom_year_range_file]
25
- mday: 5
26
- year_ranges:
27
- - before: 2015
28
- - after: 2017
24
+ limited: [2016,2018,2019]
29
25
 
30
26
  tests:
31
27
  - given:
@@ -0,0 +1,38 @@
1
+ months:
2
+ 0:
3
+ - name: With Function Modifier
4
+ regions: [multiple_with_conflict_1]
5
+ function: easter(year)
6
+ function_modifier: 60
7
+ - name: With Function Only Different Function Name
8
+ regions: [multiple_with_conflict_1]
9
+ function: conflict_custom_method_1(year)
10
+ - name: With Function Only Same Function Name
11
+ regions: [multiple_with_conflict_1]
12
+ function: conflict_custom_method_identical_name_between_regions(year)
13
+ - name: With Function Only Same Function Name - Region 1
14
+ regions: [multiple_with_conflict_1]
15
+ function: conflict_custom_method_identical_name_between_regions_but_different_holiday_names(year)
16
+ 1:
17
+ - name: New Year's Day
18
+ regions: [multiple_with_conflict_1]
19
+ mday: 1
20
+ observed: to_monday_if_weekend(date)
21
+ 10:
22
+ - name: Testing Conflict Month 10
23
+ regions: [multiple_with_conflict_1]
24
+ mday: 5
25
+
26
+ methods:
27
+ conflict_custom_method_1:
28
+ arguments: year
29
+ ruby: |
30
+ Date.civil(year, 8, 1)
31
+ conflict_custom_method_identical_name_between_regions:
32
+ arguments: year
33
+ ruby: |
34
+ Date.civil(year, 9, 1)
35
+ conflict_custom_method_identical_name_between_regions_but_different_holiday_names:
36
+ arguments: year
37
+ ruby: |
38
+ Date.civil(year, 9, 15)
@@ -0,0 +1,38 @@
1
+ months:
2
+ 0:
3
+ - name: With Function Modifier
4
+ regions: [multiple_with_conflict_2]
5
+ function: easter(year)
6
+ function_modifier: 64
7
+ - name: With Function Only Different Function Name
8
+ regions: [multiple_with_conflict_2]
9
+ function: conflict_custom_method_2(year)
10
+ - name: With Function Only Same Function Name
11
+ regions: [multiple_with_conflict_2]
12
+ function: conflict_custom_method_identical_name_between_regions(year)
13
+ - name: With Function Only Same Function Name - Region 2
14
+ regions: [multiple_with_conflict_2]
15
+ function: conflict_custom_method_identical_name_between_regions_but_different_holiday_names(year)
16
+ 1:
17
+ - name: New Year's Day
18
+ regions: [multiple_with_conflict_2]
19
+ mday: 1
20
+ observed: to_tuesday_if_sunday_or_monday_if_saturday(date)
21
+ 10:
22
+ - name: Testing Conflict Month 10
23
+ regions: [multiple_with_conflict_2]
24
+ mday: 7
25
+
26
+ methods:
27
+ conflict_custom_method_2:
28
+ arguments: year
29
+ ruby: |
30
+ Date.civil(year, 12, 1)
31
+ conflict_custom_method_identical_name_between_regions:
32
+ arguments: year
33
+ ruby: |
34
+ Date.civil(year, 11, 1)
35
+ conflict_custom_method_identical_name_between_regions_but_different_holiday_names:
36
+ arguments: year
37
+ ruby: |
38
+ Date.civil(year, 11, 15)
@@ -31,21 +31,37 @@ class ArDefinitionTests < Test::Unit::TestCase # :nodoc:
31
31
 
32
32
  assert_equal "Día de la Revolución de Mayo", (Holidays.on(Date.civil(2016, 5, 25), [:ar], [:informal])[0] || {})[:name]
33
33
 
34
- assert_equal "Día de la Bandera", (Holidays.on(Date.civil(2016, 6, 20), [:ar], [:informal])[0] || {})[:name]
34
+ assert_equal "Paso a la Inmortalidad del General Martín Miguel de Güemes", (Holidays.on(Date.civil(2020, 6, 15), [:ar], [:informal])[0] || {})[:name]
35
+ assert_equal "Paso a la Inmortalidad del General Martín Miguel de Güemes", (Holidays.on(Date.civil(2021, 6, 21), [:ar], [:informal])[0] || {})[:name]
35
36
 
36
- assert_equal "Feriado puente turístico", (Holidays.on(Date.civil(2016, 7, 8), [:ar], [:informal])[0] || {})[:name]
37
+ assert_nil (Holidays.on(Date.civil(2020, 6, 17), [:ar], [:informal])[0] || {})[:name]
38
+ assert_nil (Holidays.on(Date.civil(2021, 6, 17), [:ar], [:informal])[0] || {})[:name]
39
+
40
+ assert_equal "Paso a la Inmortalidad del General Martín Miguel de Güemes", (Holidays.on(Date.civil(2016, 6, 20), [:ar], [:informal])[0] || {})[:name]
37
41
 
38
42
  assert_equal "Día de la Independencia", (Holidays.on(Date.civil(2016, 7, 9), [:ar], [:informal])[0] || {})[:name]
39
43
 
40
44
  assert_equal "Paso a la Inmortalidad del General José de San Martín", (Holidays.on(Date.civil(2016, 8, 15), [:ar], [:informal])[0] || {})[:name]
45
+ assert_equal "Paso a la Inmortalidad del General José de San Martín", (Holidays.on(Date.civil(2020, 8, 17), [:ar], [:informal])[0] || {})[:name]
46
+ assert_equal "Paso a la Inmortalidad del General José de San Martín", (Holidays.on(Date.civil(2021, 8, 16), [:ar], [:informal])[0] || {})[:name]
47
+
48
+ assert_nil (Holidays.on(Date.civil(2016, 8, 17), [:ar], [:informal])[0] || {})[:name]
49
+ assert_nil (Holidays.on(Date.civil(2021, 8, 17), [:ar], [:informal])[0] || {})[:name]
50
+
51
+ assert_equal "Día del Respeto a la Diversidad Cultural", (Holidays.on(Date.civil(2016, 10, 10), [:ar], [:informal])[0] || {})[:name]
52
+ assert_equal "Día del Respeto a la Diversidad Cultural", (Holidays.on(Date.civil(2021, 10, 11), [:ar], [:informal])[0] || {})[:name]
41
53
 
42
- assert_equal "Día del Respeto a la Diversidad Cultural", (Holidays.on(Date.civil(2016, 10, 12), [:ar], [:informal])[0] || {})[:name]
54
+ assert_nil (Holidays.on(Date.civil(2021, 10, 12), [:ar], [:informal])[0] || {})[:name]
43
55
 
44
56
  assert_equal "Día de la Soberanía Nacional", (Holidays.on(Date.civil(2016, 11, 20), [:ar], [:informal])[0] || {})[:name]
45
57
 
46
58
  assert_equal "Inmaculada Concepción de María", (Holidays.on(Date.civil(2016, 12, 8), [:ar], [:informal])[0] || {})[:name]
47
59
 
48
- assert_equal "Feriado puente turístico", (Holidays.on(Date.civil(2016, 12, 9), [:ar], [:informal])[0] || {})[:name]
60
+ assert_equal "Feriado con fines turísticos", (Holidays.on(Date.civil(2016, 7, 8), [:ar], [:informal])[0] || {})[:name]
61
+ assert_equal "Feriado con fines turísticos", (Holidays.on(Date.civil(2016, 12, 9), [:ar], [:informal])[0] || {})[:name]
62
+ assert_equal "Feriado con fines turísticos", (Holidays.on(Date.civil(2021, 5, 24), [:ar], [:informal])[0] || {})[:name]
63
+ assert_equal "Feriado con fines turísticos", (Holidays.on(Date.civil(2021, 10, 8), [:ar], [:informal])[0] || {})[:name]
64
+ assert_equal "Feriado con fines turísticos", (Holidays.on(Date.civil(2021, 11, 22), [:ar], [:informal])[0] || {})[:name]
49
65
 
50
66
  assert_equal "Navidad", (Holidays.on(Date.civil(2016, 12, 25), [:ar], [:informal])[0] || {})[:name]
51
67