holidays 6.6.1 → 8.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +25 -0
- data/CHANGELOG.md +72 -0
- data/README.md +10 -6
- data/doc/CONTRIBUTING.md +2 -1
- data/holidays.gemspec +5 -5
- data/lib/generated_definitions/MANIFEST +11 -5
- data/lib/generated_definitions/REGIONS.rb +2 -2
- data/lib/generated_definitions/ar.rb +28 -9
- data/lib/generated_definitions/at.rb +2 -2
- data/lib/generated_definitions/au.rb +47 -10
- data/lib/generated_definitions/be_fr.rb +2 -2
- data/lib/generated_definitions/be_nl.rb +2 -2
- data/lib/generated_definitions/bg.rb +2 -2
- data/lib/generated_definitions/br.rb +2 -2
- data/lib/generated_definitions/ca.rb +25 -17
- data/lib/generated_definitions/ch.rb +15 -3
- data/lib/generated_definitions/cl.rb +7 -7
- data/lib/generated_definitions/co.rb +2 -2
- data/lib/generated_definitions/cr.rb +2 -2
- data/lib/generated_definitions/cz.rb +2 -2
- data/lib/generated_definitions/de.rb +11 -7
- data/lib/generated_definitions/dk.rb +3 -3
- data/lib/generated_definitions/{ecb_target.rb → ecbtarget.rb} +10 -10
- data/lib/generated_definitions/ee.rb +2 -2
- data/lib/generated_definitions/el.rb +2 -2
- data/lib/generated_definitions/es.rb +6 -4
- data/lib/generated_definitions/europe.rb +119 -32
- data/lib/generated_definitions/{federal_reserve.rb → federalreserve.rb} +15 -14
- data/lib/generated_definitions/federalreservebanks.rb +35 -0
- data/lib/generated_definitions/fedex.rb +2 -2
- data/lib/generated_definitions/fi.rb +2 -2
- data/lib/generated_definitions/fr.rb +4 -4
- data/lib/generated_definitions/gb.rb +15 -7
- data/lib/generated_definitions/ge.rb +2 -2
- data/lib/generated_definitions/hk.rb +2 -2
- data/lib/generated_definitions/hr.rb +10 -8
- data/lib/generated_definitions/hu.rb +4 -3
- data/lib/generated_definitions/ie.rb +2 -2
- data/lib/generated_definitions/is.rb +2 -2
- data/lib/generated_definitions/it.rb +16 -7
- data/lib/generated_definitions/jp.rb +31 -16
- data/lib/generated_definitions/kr.rb +2 -2
- data/lib/generated_definitions/kz.rb +38 -0
- data/lib/generated_definitions/li.rb +2 -2
- data/lib/generated_definitions/lt.rb +2 -2
- data/lib/generated_definitions/lu.rb +4 -2
- data/lib/generated_definitions/lv.rb +56 -0
- data/lib/generated_definitions/ma.rb +2 -2
- data/lib/generated_definitions/mt_en.rb +2 -2
- data/lib/generated_definitions/mt_mt.rb +2 -2
- data/lib/generated_definitions/mx.rb +7 -7
- data/lib/generated_definitions/my.rb +2 -2
- data/lib/generated_definitions/nerc.rb +2 -2
- data/lib/generated_definitions/ng.rb +33 -0
- data/lib/generated_definitions/nl.rb +2 -2
- data/lib/generated_definitions/no.rb +2 -2
- data/lib/generated_definitions/{north_america.rb → northamerica.rb} +34 -26
- data/lib/generated_definitions/nyse.rb +3 -2
- data/lib/generated_definitions/nz.rb +41 -3
- data/lib/generated_definitions/pe.rb +2 -2
- data/lib/generated_definitions/ph.rb +2 -2
- data/lib/generated_definitions/pl.rb +2 -2
- data/lib/generated_definitions/pt.rb +2 -2
- data/lib/generated_definitions/ro.rb +6 -3
- data/lib/generated_definitions/rs_cyrl.rb +3 -3
- data/lib/generated_definitions/rs_la.rb +3 -3
- data/lib/generated_definitions/ru.rb +2 -2
- data/lib/generated_definitions/scandinavia.rb +3 -3
- data/lib/generated_definitions/se.rb +2 -2
- data/lib/generated_definitions/sg.rb +2 -2
- data/lib/generated_definitions/si.rb +4 -3
- data/lib/generated_definitions/sk.rb +2 -2
- data/lib/generated_definitions/{south_america.rb → southamerica.rb} +32 -13
- data/lib/generated_definitions/th.rb +36 -0
- data/lib/generated_definitions/tn.rb +2 -2
- data/lib/generated_definitions/tr.rb +7 -5
- data/lib/generated_definitions/ua.rb +37 -0
- data/lib/generated_definitions/{united_nations.rb → unitednations.rb} +61 -61
- data/lib/generated_definitions/ups.rb +2 -2
- data/lib/generated_definitions/us.rb +9 -9
- data/lib/generated_definitions/ve.rb +2 -2
- data/lib/generated_definitions/vi.rb +2 -2
- data/lib/generated_definitions/za.rb +3 -3
- data/lib/holidays/definition/context/generator.rb +23 -70
- data/lib/holidays/definition/generator/module.rb +54 -0
- data/lib/holidays/definition/generator/regions.rb +1 -1
- data/lib/holidays/definition/repository/holidays_by_month.rb +9 -1
- data/lib/holidays/factory/definition.rb +7 -0
- data/lib/holidays/finder/context/search.rb +35 -31
- data/lib/holidays/finder/rules/year_range.rb +30 -54
- data/lib/holidays/version.rb +1 -1
- data/lib/holidays.rb +2 -0
- data/test/coverage_report.rb +23 -5
- data/test/data/test_custom_year_range_holiday_defs.yaml +6 -10
- data/test/data/test_multiple_regions_with_conflicts_region_1.yaml +38 -0
- data/test/data/test_multiple_regions_with_conflicts_region_2.yaml +38 -0
- data/test/defs/test_defs_ar.rb +20 -4
- data/test/defs/test_defs_au.rb +22 -0
- data/test/defs/test_defs_ca.rb +71 -8
- data/test/defs/test_defs_ch.rb +4 -0
- data/test/defs/test_defs_co.rb +3 -3
- data/test/defs/test_defs_de.rb +8 -0
- data/test/defs/test_defs_dk.rb +4 -0
- data/test/defs/{test_defs_ecb_target.rb → test_defs_ecbtarget.rb} +11 -11
- data/test/defs/test_defs_es.rb +2 -0
- data/test/defs/test_defs_europe.rb +233 -14
- data/test/defs/test_defs_federalreserve.rb +119 -0
- data/test/defs/test_defs_federalreservebanks.rb +251 -0
- data/test/defs/test_defs_fr.rb +3 -3
- data/test/defs/test_defs_gb.rb +42 -0
- data/test/defs/test_defs_hr.rb +6 -6
- data/test/defs/test_defs_hu.rb +12 -4
- data/test/defs/test_defs_it.rb +20 -0
- data/test/defs/test_defs_jp.rb +22 -2
- data/test/defs/test_defs_kz.rb +39 -0
- data/test/defs/test_defs_lu.rb +6 -0
- data/test/defs/test_defs_lv.rb +98 -0
- data/test/defs/test_defs_mx.rb +3 -1
- data/test/defs/test_defs_ng.rb +29 -0
- data/test/defs/{test_defs_north_america.rb → test_defs_northamerica.rb} +85 -20
- data/test/defs/test_defs_nyse.rb +7 -0
- data/test/defs/test_defs_nz.rb +4 -0
- data/test/defs/test_defs_ro.rb +14 -0
- data/test/defs/test_defs_rs_cyrl.rb +1 -1
- data/test/defs/test_defs_rs_la.rb +1 -1
- data/test/defs/test_defs_scandinavia.rb +4 -0
- data/test/defs/{test_defs_south_america.rb → test_defs_southamerica.rb} +25 -9
- data/test/defs/test_defs_th.rb +33 -0
- data/test/defs/test_defs_tr.rb +7 -0
- data/test/defs/test_defs_ua.rb +41 -0
- data/test/defs/{test_defs_united_nations.rb → test_defs_unitednations.rb} +3 -3
- data/test/defs/test_defs_us.rb +11 -11
- data/test/holidays/core_extensions/test_date.rb +3 -2
- data/test/holidays/definition/context/test_generator.rb +17 -20
- data/test/holidays/definition/generator/test_module.rb +268 -0
- data/test/holidays/definition/repository/test_holidays_by_month.rb +121 -1
- data/test/holidays/finder/rules/test_year_range.rb +43 -47
- data/test/integration/test_available_regions.rb +1 -1
- data/test/integration/test_custom_year_range_holidays.rb +0 -7
- data/test/integration/test_holidays.rb +2 -36
- data/test/integration/test_holidays_between.rb +11 -1
- data/test/integration/test_multiple_regions_with_conflict.rb +29 -0
- data/test/integration/test_nonstandard_regions.rb +25 -0
- metadata +61 -32
- 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 =
|
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[:
|
93
|
-
|
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
|
-
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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 "
|
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] &&
|
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
|
-
|
28
|
-
|
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 =
|
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
|
-
|
9
|
-
|
6
|
+
UNTIL = :until
|
7
|
+
FROM = :from
|
10
8
|
LIMITED = :limited
|
11
9
|
BETWEEN = :between
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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.
|
59
|
-
|
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
|
-
|
43
|
+
raise ArgumentError.new("Invalid operator found: '#{operator}'") unless [UNTIL, FROM, LIMITED, BETWEEN].include?(operator)
|
67
44
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
data/lib/holidays/version.rb
CHANGED
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
|
data/test/coverage_report.rb
CHANGED
@@ -1,8 +1,26 @@
|
|
1
1
|
require 'simplecov'
|
2
2
|
|
3
|
-
|
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
|
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
|
-
|
7
|
+
from: 2016
|
8
8
|
- name: before_year
|
9
9
|
regions: [custom_year_range_file]
|
10
10
|
mday: 2
|
11
11
|
year_ranges:
|
12
|
-
|
12
|
+
until: 2017
|
13
13
|
- name: between_year
|
14
14
|
regions: [custom_year_range_file]
|
15
15
|
mday: 3
|
16
16
|
year_ranges:
|
17
|
-
|
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
|
-
|
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)
|
data/test/defs/test_defs_ar.rb
CHANGED
@@ -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 "
|
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
|
-
|
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
|
-
|
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
|
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
|
|