holidays 9.1.0 → 9.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +2 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile +5 -0
- data/Makefile +14 -3
- data/README.md +1 -2
- data/Rakefile +19 -0
- data/doc/MAINTAINERS.md +2 -4
- data/holidays.gemspec +3 -3
- data/lib/generated_definitions/bg.rb +3 -2
- data/lib/generated_definitions/cy.rb +2 -1
- data/lib/generated_definitions/europe.rb +2 -1
- data/lib/holidays/definition/context/function_processor.rb +1 -1
- data/lib/holidays/definition/context/merger.rb +20 -4
- data/lib/holidays/definition/repository/custom_methods.rb +37 -9
- data/lib/holidays/finder/context/search.rb +43 -24
- data/lib/holidays/version.rb +1 -1
- data/lib/holidays.rb +9 -4
- data/test/coverage_report.rb +3 -5
- data/test/data/test_date_transform_conflict_region_1.yaml +14 -0
- data/test/data/test_date_transform_conflict_region_2.yaml +14 -0
- data/test/e2e/README.md +52 -0
- data/test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml +38 -0
- data/test/{integration → e2e}/test_holidays.rb +3 -2
- data/test/e2e/test_multiple_regions_with_conflict.rb +228 -0
- data/test/holidays/definition/context/test_function_processor.rb +2 -2
- data/test/holidays/definition/context/test_merger.rb +1 -1
- data/test/holidays/finder/context/test_search.rb +58 -0
- data/test/integration/README.md +45 -6
- data/test/integration/test_custom_holidays.rb +1 -1
- data/test/integration/test_custom_informal_holidays.rb +1 -1
- data/test/smoke/README.md +31 -0
- data/test/{integration → smoke}/test_available_regions.rb +0 -5
- data/test/smoke/test_smoke.rb +74 -0
- metadata +46 -34
- data/test/integration/test_multiple_regions_with_conflict.rb +0 -29
- /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_1.yaml +0 -0
- /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_2.yaml +0 -0
- /data/test/{integration → e2e}/test_all_regions.rb +0 -0
- /data/test/{integration → e2e}/test_any_holidays_during_work_week.rb +0 -0
- /data/test/{integration → e2e}/test_holidays_between.rb +0 -0
- /data/test/{integration → e2e}/test_multiple_regions.rb +0 -0
- /data/test/{integration → e2e}/test_nonstandard_regions.rb +0 -0
- /data/test/{data → integration/data}/test_custom_govt_holiday_defs.yaml +0 -0
- /data/test/{data → integration/data}/test_custom_informal_holidays_defs.yaml +0 -0
- /data/test/{data → integration/data}/test_multiple_custom_holiday_defs.yaml +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f0f84e8d40a176b0172092f8884b09567b06a4cd870a082920fbb274acb6b43f
|
|
4
|
+
data.tar.gz: bde696d82c96c50a14e5defad021c14aa541456c2eee281580b9e0fe5f1bee07
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 90aa255cd3e08574b8b796fee989123b473a6dcb45ad522c053272a6299e03c8b84b0852ec7c9a1c8c3a2db1420f23ce963403b7439239cbe6b4f5a7b6fa35d9
|
|
7
|
+
data.tar.gz: 0e1bc79739ba0a46b6ebd80b2eb3453dde2eed2ab18db95c0088d42dba7505a01587052f7cb38fbc0f546e363ce2bde23a2ae09ae67b06d8280fcf6bfc4728e2
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -11,7 +11,7 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
strategy:
|
|
13
13
|
matrix:
|
|
14
|
-
ruby-version: ['3.2', '3.3', '3.4', 'ruby-head', '4.0']
|
|
14
|
+
ruby-version: ['3.2', '3.3', '3.4', '4.0', 'ruby-head', 'jruby-10.0.4.0']
|
|
15
15
|
env:
|
|
16
16
|
BUNDLER_NO_OLD_RUBYGEMS_WARNING: true
|
|
17
17
|
steps:
|
|
@@ -20,6 +20,7 @@ jobs:
|
|
|
20
20
|
uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 #v1.299.0
|
|
21
21
|
with:
|
|
22
22
|
ruby-version: ${{ matrix.ruby-version }}
|
|
23
|
+
bundler: latest
|
|
23
24
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
24
25
|
- name: Run tests
|
|
25
26
|
run: make test
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Ruby Holidays Gem CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 9.1.2
|
|
4
|
+
|
|
5
|
+
* Fix bug ([#344](https://github.com/holidays/holidays/issues/344)) where holidays with the same name and function but different implementations across regions would return incorrect results depending on region load order. When multiple regions are queried simultaneously, each region's function implementation is now evaluated independently and all matching results are returned.
|
|
6
|
+
|
|
7
|
+
## 9.1.1
|
|
8
|
+
|
|
9
|
+
* Update to [v6.0.1 definitions](https://github.com/holidays/definitions/releases/tag/v6.0.1). Please see the changelog for the definition details.
|
|
10
|
+
|
|
3
11
|
## 9.1.0
|
|
4
12
|
|
|
5
13
|
* Update to [v6.0.0 definitions](https://github.com/holidays/definitions/releases/tag/v6.0.0). Please see the changelog for the definition details.
|
data/Gemfile
CHANGED
data/Makefile
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
default: test
|
|
2
1
|
|
|
3
2
|
setup: update-defs
|
|
4
3
|
bundle install
|
|
@@ -9,6 +8,15 @@ generate:
|
|
|
9
8
|
test:
|
|
10
9
|
bundle exec rake test
|
|
11
10
|
|
|
11
|
+
test-smoke:
|
|
12
|
+
bundle exec rake test:smoke
|
|
13
|
+
|
|
14
|
+
test-integration:
|
|
15
|
+
bundle exec rake test:integration
|
|
16
|
+
|
|
17
|
+
test-e2e:
|
|
18
|
+
bundle exec rake test:e2e
|
|
19
|
+
|
|
12
20
|
console:
|
|
13
21
|
bundle exec rake console
|
|
14
22
|
|
|
@@ -27,11 +35,14 @@ update-defs: definitions/
|
|
|
27
35
|
definitions: point-to-defs-master
|
|
28
36
|
|
|
29
37
|
point-to-defs-branch:
|
|
30
|
-
git submodule add -b $(BRANCH)
|
|
38
|
+
git submodule add -b $(BRANCH) https://github.com/$(USER)/definitions.git ./definitions/
|
|
31
39
|
|
|
32
40
|
point-to-defs-master:
|
|
33
41
|
git submodule add https://github.com/holidays/definitions definitions/
|
|
34
42
|
|
|
43
|
+
reset-defs-to-master:
|
|
44
|
+
git -C definitions checkout $$(git ls-tree origin/master -- definitions | awk '{print $$3}')
|
|
45
|
+
|
|
35
46
|
clean-defs:
|
|
36
47
|
git rm -f definitions
|
|
37
48
|
rm -rf .git/modules/definitions
|
|
@@ -42,4 +53,4 @@ clean:
|
|
|
42
53
|
rm -rf reports
|
|
43
54
|
rm -rf coverage
|
|
44
55
|
|
|
45
|
-
.PHONY: setup test generate console build push update-defs test-region clean-defs point-to-defs-master point-to-defs-branch clean definitions
|
|
56
|
+
.PHONY: setup test test-smoke test-integration test-e2e generate console build push update-defs test-region reset-defs-to-master clean-defs point-to-defs-master point-to-defs-branch clean definitions
|
data/README.md
CHANGED
data/Rakefile
CHANGED
|
@@ -15,6 +15,25 @@ Rake::TestTask.new(:test) do |t|
|
|
|
15
15
|
t.test_files = FileList['test/**/test_*.rb']
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
task 'test:smoke' do
|
|
19
|
+
ENV['SMOKE_TEST'] = '1'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Rake::TestTask.new('test:smoke') do |t|
|
|
23
|
+
t.libs << 'test'
|
|
24
|
+
t.test_files = FileList['test/smoke/test_*.rb']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Rake::TestTask.new('test:integration') do |t|
|
|
28
|
+
t.libs << 'test'
|
|
29
|
+
t.test_files = FileList['test/integration/test_*.rb']
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Rake::TestTask.new('test:e2e') do |t|
|
|
33
|
+
t.libs << 'test'
|
|
34
|
+
t.test_files = FileList['test/e2e/test_*.rb']
|
|
35
|
+
end
|
|
36
|
+
|
|
18
37
|
task :default => :test
|
|
19
38
|
|
|
20
39
|
desc "Run tests for only a single region. Do not provide sub regions. Example (without quotes): 'rake test_region jp'"
|
data/doc/MAINTAINERS.md
CHANGED
|
@@ -55,15 +55,13 @@ sections below.
|
|
|
55
55
|
* If all of the tests pass, update the `lib/holidays/version.rb` file to the new version. Reference the above [semver](http://semver.org/) rules for how to update versions.
|
|
56
56
|
* Make a branch on your fork and update the [CHANGELOG](../CHANGELOG.md) to reflect the latest changes. You do not need to put in all of the definition changes in this update, you can simply reference the other repository. See other entries in the CHANGELOG for examples.
|
|
57
57
|
* Open a PR against the new branch and merge it (another maintainer will need to review before you can merge)
|
|
58
|
-
* Once the branch is merged,
|
|
59
|
-
* If the build was successful then you can run the following to push up to rubygems.org: `GEM=<gem> make push`. Example: `GEM=holidays-6.2.0.gem make push`
|
|
58
|
+
* Once the branch is merged, the release GitHub Actions workflow will automatically build the gem and push it to rubygems.org. No local steps are required.
|
|
60
59
|
* Does this update require functionality additions or bug fixes? If YES, then:
|
|
61
60
|
* Run `make test` and ensure that all of the tests pass. If any tests fail then do *not* merge and contact a [core member](https://github.com/orgs/holidays/teams/core/members) for assistance.
|
|
62
61
|
* If all of the tests pass, make a branch on your fork and update the [CHANGELOG](../CHANGELOG.md) to reflect the latest changes.
|
|
63
62
|
* Update the `lib/holidays/version.rb` file to the new version. Reference the above [semver](http://semver.org/) rules for how to update versions.
|
|
64
63
|
* Open a PR against the new branch and merge it (another maintainer will need to review before you can merge)
|
|
65
|
-
* Once the branch is merged,
|
|
66
|
-
* If the build was successful then you can run the following to push up to rubygems.org: `GEM=<gem> make push`. Example: `GEM=holidays-6.2.0.gem make push`
|
|
64
|
+
* Once the branch is merged, the release GitHub Actions workflow will automatically build the gem and push it to rubygems.org. No local steps are required.
|
|
67
65
|
|
|
68
66
|
You are done! The latest version should be uploaded to rubygems.org. You can go to view the [holidays page](https://rubygems.org/gems/holidays) to verify that the latest version is available.
|
|
69
67
|
|
data/holidays.gemspec
CHANGED
|
@@ -22,10 +22,10 @@ Gem::Specification.new do |gem|
|
|
|
22
22
|
gem.require_paths = ['lib']
|
|
23
23
|
gem.licenses = ['MIT']
|
|
24
24
|
gem.required_ruby_version = '>= 3.2'
|
|
25
|
-
gem.add_development_dependency 'bundler', '>=
|
|
25
|
+
gem.add_development_dependency 'bundler', '>= 4'
|
|
26
26
|
gem.add_development_dependency 'rake', '~> 13'
|
|
27
27
|
gem.add_development_dependency 'simplecov', '~> 0.16'
|
|
28
28
|
gem.add_development_dependency 'test-unit', '~> 3'
|
|
29
|
-
gem.add_development_dependency 'mocha', '~>
|
|
30
|
-
gem.add_development_dependency 'pry', '~> 0.
|
|
29
|
+
gem.add_development_dependency 'mocha', '~> 3'
|
|
30
|
+
gem.add_development_dependency 'pry', '~> 0.16'
|
|
31
31
|
end
|
|
@@ -17,8 +17,9 @@ module Holidays
|
|
|
17
17
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => -1, :name => "Holy Saturday", :regions => [:bg_en]},
|
|
18
18
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => -1, :name => "Велика събота", :regions => [:bg_bg]},
|
|
19
19
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :name => "Easter Sunday", :regions => [:bg_en]},
|
|
20
|
-
{:function => "orthodox_easter(year)", :function_arguments => [:year], :name => "Възкресение Христово. Великден", :regions => [:bg_bg
|
|
21
|
-
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => 1, :name => "Easter Monday", :regions => [:bg_en]}
|
|
20
|
+
{:function => "orthodox_easter(year)", :function_arguments => [:year], :name => "Възкресение Христово. Великден", :regions => [:bg_bg]},
|
|
21
|
+
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => 1, :name => "Easter Monday", :regions => [:bg_en]},
|
|
22
|
+
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => 1, :name => "Великденски понеделник", :regions => [:bg_bg]}],
|
|
22
23
|
1 => [{:mday => 1, :name => "New Year's Day", :regions => [:bg_en]},
|
|
23
24
|
{:mday => 1, :name => "Нова година", :regions => [:bg_bg]}],
|
|
24
25
|
3 => [{:mday => 3, :name => "Liberation Day", :regions => [:bg_en]},
|
|
@@ -25,7 +25,8 @@ module Holidays
|
|
|
25
25
|
4 => [{:mday => 1, :name => "Εθνική Ημέρα Κύπρου", :regions => [:cy]}],
|
|
26
26
|
5 => [{:mday => 1, :name => "Πρωτομαγιά", :regions => [:cy]}],
|
|
27
27
|
8 => [{:mday => 15, :name => "Κοίμηση της Θεοτόκου", :regions => [:cy]}],
|
|
28
|
-
10 => [{:mday =>
|
|
28
|
+
10 => [{:mday => 1, :name => "Ημέρα Ανεξαρτησίας της Κύπρου", :regions => [:cy]},
|
|
29
|
+
{:mday => 28, :name => "Επέτειος του Όχι", :regions => [:cy]}],
|
|
29
30
|
12 => [{:mday => 25, :name => "Χριστούγεννα", :regions => [:cy]},
|
|
30
31
|
{:mday => 26, :name => "Δεύτερη ημέρα των Χριστουγέννων", :regions => [:cy]}]
|
|
31
32
|
}
|
|
@@ -154,8 +154,9 @@ module Holidays
|
|
|
154
154
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => -1, :name => "Holy Saturday", :regions => [:bg_en]},
|
|
155
155
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => -1, :name => "Велика събота", :regions => [:bg_bg]},
|
|
156
156
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :name => "Easter Sunday", :regions => [:bg_en]},
|
|
157
|
-
{:function => "orthodox_easter(year)", :function_arguments => [:year], :name => "Възкресение Христово. Великден", :regions => [:bg_bg
|
|
157
|
+
{:function => "orthodox_easter(year)", :function_arguments => [:year], :name => "Възкресение Христово. Великден", :regions => [:bg_bg]},
|
|
158
158
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => 1, :name => "Easter Monday", :regions => [:bg_en]},
|
|
159
|
+
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => 1, :name => "Великденски понеделник", :regions => [:bg_bg]},
|
|
159
160
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :observed => "to_monday_if_weekend(date)", :observed_arguments => [:date], :name => "Великдень", :regions => [:ua]},
|
|
160
161
|
{:function => "orthodox_easter(year)", :function_arguments => [:year], :function_modifier => 49, :observed => "to_monday_if_weekend(date)", :observed_arguments => [:date], :name => "Трійця", :regions => [:ua]}],
|
|
161
162
|
1 => [{:mday => 1, :name => "Neujahrstag", :regions => [:at]},
|
|
@@ -12,7 +12,7 @@ module Holidays
|
|
|
12
12
|
def call(input, func_id, desired_func_args, func_modifier = nil)
|
|
13
13
|
validate!(input, func_id, desired_func_args)
|
|
14
14
|
|
|
15
|
-
function = @custom_methods_repo.find(func_id)
|
|
15
|
+
function = @custom_methods_repo.find(func_id, input[:region])
|
|
16
16
|
raise Holidays::FunctionNotFound.new("Unable to find function with id '#{func_id}'") if function.nil?
|
|
17
17
|
|
|
18
18
|
calculate(input, function, parse_arguments(input, desired_func_args), func_modifier)
|
|
@@ -9,12 +9,28 @@ module Holidays
|
|
|
9
9
|
@custom_methods_repo = custom_methods_repo
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def call(target_regions, target_holidays, target_custom_methods)
|
|
13
|
-
#FIXME Does this need to come in this exact order? God I hope not.
|
|
14
|
-
# If not then we should swap the order so it matches the init.
|
|
12
|
+
def call(target_regions, target_holidays, target_custom_methods, target_custom_method_sources = {})
|
|
15
13
|
@regions_repo.add(target_regions)
|
|
16
14
|
@holidays_repo.add(target_holidays)
|
|
17
|
-
@custom_methods_repo.add(
|
|
15
|
+
@custom_methods_repo.add(
|
|
16
|
+
target_custom_methods,
|
|
17
|
+
target_custom_method_sources,
|
|
18
|
+
derive_function_regions(target_holidays),
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# Builds a map of {func_id => [regions]} from the holiday definitions
|
|
25
|
+
# so the custom_methods repo knows which regions each function belongs to.
|
|
26
|
+
def derive_function_regions(holidays_by_month)
|
|
27
|
+
holidays_by_month.each_with_object({}) do |(_, definitions), result|
|
|
28
|
+
definitions.each do |defn|
|
|
29
|
+
next unless defn[:function]
|
|
30
|
+
result[defn[:function]] ||= []
|
|
31
|
+
result[defn[:function]] |= Array(defn[:regions])
|
|
32
|
+
end
|
|
33
|
+
end
|
|
18
34
|
end
|
|
19
35
|
end
|
|
20
36
|
end
|
|
@@ -4,21 +4,49 @@ module Holidays
|
|
|
4
4
|
class CustomMethods
|
|
5
5
|
def initialize
|
|
6
6
|
@custom_methods = {}
|
|
7
|
+
@custom_method_sources = {}
|
|
8
|
+
@regional_overrides = {}
|
|
7
9
|
end
|
|
8
10
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
# FIXME: this should probably return an error if a method with the
|
|
14
|
-
# same ID already exists.
|
|
15
|
-
def add(new_custom_methods)
|
|
11
|
+
# When a conflict is detected the method is stored as a regional override keyed by its regions.
|
|
12
|
+
# find() then resolves the right implementation at lookup time using the queried region.
|
|
13
|
+
def add(new_custom_methods, new_sources = {}, function_regions = {})
|
|
16
14
|
raise ArgumentError if new_custom_methods.nil?
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
new_custom_methods.each do |key, method|
|
|
17
|
+
new_source = new_sources[key]
|
|
18
|
+
|
|
19
|
+
if @custom_methods.key?(key)
|
|
20
|
+
existing_source = @custom_method_sources[key]
|
|
21
|
+
|
|
22
|
+
if new_source && existing_source && new_source != existing_source
|
|
23
|
+
regions = function_regions[key] || []
|
|
24
|
+
@regional_overrides[key] ||= []
|
|
25
|
+
@regional_overrides[key] << { regions: regions, proc: method }
|
|
26
|
+
else
|
|
27
|
+
@custom_methods[key] = method
|
|
28
|
+
@custom_method_sources[key] = new_source if new_source
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
@custom_methods[key] = method
|
|
32
|
+
@custom_method_sources[key] = new_source if new_source
|
|
33
|
+
end
|
|
34
|
+
end
|
|
18
35
|
end
|
|
19
36
|
|
|
20
|
-
|
|
37
|
+
# Returns the proc for the given method_id.
|
|
38
|
+
#
|
|
39
|
+
# When a region is supplied, regional overrides are checked first so
|
|
40
|
+
# that conflicting methods with the same name but different logic each
|
|
41
|
+
# resolve to their own implementation.
|
|
42
|
+
def find(method_id, region = nil)
|
|
21
43
|
raise ArgumentError if method_id.nil? || method_id.empty?
|
|
44
|
+
|
|
45
|
+
if region && @regional_overrides[method_id]
|
|
46
|
+
override = @regional_overrides[method_id].find { |o| o[:regions].include?(region) }
|
|
47
|
+
return override[:proc] if override
|
|
48
|
+
end
|
|
49
|
+
|
|
22
50
|
@custom_methods[method_id]
|
|
23
51
|
end
|
|
24
52
|
end
|
|
@@ -24,14 +24,19 @@ module Holidays
|
|
|
24
24
|
next unless @rules[:year_range].call(year, h[:year_ranges])
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
date
|
|
27
|
+
dates = if h[:function]
|
|
28
|
+
custom_holidays(year, month, h, regions)
|
|
29
|
+
else
|
|
30
|
+
date = build_date(year, month, h, regions)
|
|
31
|
+
date ? [date] : []
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
dates.each do |d|
|
|
35
|
+
if observed_set?(options) && h[:observed]
|
|
36
|
+
d = build_observed_date(d, regions, h)
|
|
37
|
+
end
|
|
38
|
+
holidays << {:date => d, :name => h[:name], :regions => h[:regions]}
|
|
39
|
+
end
|
|
35
40
|
end
|
|
36
41
|
end
|
|
37
42
|
end
|
|
@@ -68,36 +73,50 @@ module Holidays
|
|
|
68
73
|
options && options.include?(:observed) == true
|
|
69
74
|
end
|
|
70
75
|
|
|
71
|
-
def build_date(year, month, h)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
#FIXME The result should always be present, see https://github.com/holidays/holidays/issues/204 for more information
|
|
75
|
-
current_month = holiday&.month
|
|
76
|
-
current_day = holiday&.mday
|
|
77
|
-
else
|
|
78
|
-
current_month = month
|
|
79
|
-
current_day = h[:mday] || @day_of_month_calculator.call(year, month, h[:week], h[:wday])
|
|
80
|
-
end
|
|
76
|
+
def build_date(year, month, h, queried_regions)
|
|
77
|
+
current_month = month
|
|
78
|
+
current_day = h[:mday] || @day_of_month_calculator.call(year, month, h[:week], h[:wday])
|
|
81
79
|
|
|
82
80
|
# Silently skip bad mdays
|
|
83
81
|
#TODO Should we be doing something different here? We have no concept of logging right now. Maybe we should add it?
|
|
84
82
|
Date.civil(year, current_month, current_day) rescue nil
|
|
85
83
|
end
|
|
86
84
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
def custom_holidays(year, month, h, queried_regions)
|
|
86
|
+
effective_regions = queried_regions & h[:regions]
|
|
87
|
+
effective_regions = [queried_regions.first] if effective_regions.empty?
|
|
88
|
+
|
|
89
|
+
effective_regions.each_with_object([]) do |region, dates|
|
|
90
|
+
result = @custom_method_processor.call(
|
|
91
|
+
{ year: year, month: month, day: h[:mday], region: region },
|
|
92
|
+
h[:function], h[:function_arguments], h[:function_modifier],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
#FIXME The result should always be present, see https://github.com/holidays/holidays/issues/204 for more information
|
|
96
|
+
next if result.nil?
|
|
97
|
+
|
|
98
|
+
date = Date.civil(year, result.month, result.mday) rescue nil
|
|
99
|
+
dates << date if date
|
|
100
|
+
end.uniq
|
|
93
101
|
end
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
# When the queried region is :any (or no holiday_regions are provided), fall back
|
|
104
|
+
# to the holiday's own first region. Otherwise use the first queried region that
|
|
105
|
+
# also appears in the holiday's region list so that region-specific function
|
|
106
|
+
# implementations resolve correctly even when a holiday definition is shared across
|
|
107
|
+
# multiple regions.
|
|
108
|
+
def build_custom_method_input(year, month, day, queried_regions, holiday_regions = nil)
|
|
109
|
+
effective_region = if holiday_regions.nil? || queried_regions.include?(:any)
|
|
110
|
+
queried_regions.first
|
|
111
|
+
else
|
|
112
|
+
(queried_regions & holiday_regions).first || holiday_regions.first
|
|
113
|
+
end
|
|
114
|
+
|
|
96
115
|
{
|
|
97
116
|
year: year,
|
|
98
117
|
month: month,
|
|
99
118
|
day: day,
|
|
100
|
-
region:
|
|
119
|
+
region: effective_region,
|
|
101
120
|
}
|
|
102
121
|
end
|
|
103
122
|
|
data/lib/holidays/version.rb
CHANGED
data/lib/holidays.rb
CHANGED
|
@@ -42,9 +42,8 @@ module Holidays
|
|
|
42
42
|
|
|
43
43
|
raise ArgumentError if end_date < start_date
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
45
|
+
cached_holidays = Factory::Definition.cache_repository.find(start_date, end_date, options)
|
|
46
|
+
return cached_holidays unless cached_holidays.nil?
|
|
48
47
|
|
|
49
48
|
Factory::Finder.between.call(start_date, end_date, options)
|
|
50
49
|
end
|
|
@@ -93,11 +92,17 @@ module Holidays
|
|
|
93
92
|
def load_custom(*files)
|
|
94
93
|
regions, rules_by_month, custom_methods, _ = Factory::Definition.file_parser.parse_definition_files(files)
|
|
95
94
|
|
|
95
|
+
# Capture source code before converting entities to Procs so the merger
|
|
96
|
+
# can detect genuine conflicts (same name, different logic).
|
|
97
|
+
method_sources = custom_methods.each_with_object({}) do |(key, entity), h|
|
|
98
|
+
h[key] = entity.source
|
|
99
|
+
end
|
|
100
|
+
|
|
96
101
|
custom_methods.each do |method_key, method_entity|
|
|
97
102
|
custom_methods[method_key] = Factory::Definition.custom_method_proc_decorator.call(method_entity)
|
|
98
103
|
end
|
|
99
104
|
|
|
100
|
-
Factory::Definition.merger.call(regions, rules_by_month, custom_methods)
|
|
105
|
+
Factory::Definition.merger.call(regions, rules_by_month, custom_methods, method_sources)
|
|
101
106
|
|
|
102
107
|
rules_by_month
|
|
103
108
|
end
|
data/test/coverage_report.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
require 'simplecov'
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
# We set the floor based on jruby so that all automated tests pass on Travis CI.
|
|
7
|
-
SimpleCov.minimum_coverage 89
|
|
3
|
+
# JRuby coverage reporting is inaccurate without --debug mode, resulting in
|
|
4
|
+
# artificially low numbers. Skip the minimum coverage check under JRuby.
|
|
5
|
+
SimpleCov.minimum_coverage 89 unless RUBY_PLATFORM == 'java' || ENV['SMOKE_TEST']
|
|
8
6
|
|
|
9
7
|
SimpleCov.add_filter [
|
|
10
8
|
# Apparently simplecov doesn't automatically filter 'spec' or 'test' so we
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
months:
|
|
2
|
+
1:
|
|
3
|
+
- name: Weekend Holiday
|
|
4
|
+
regions: [date_transform_conflict_1]
|
|
5
|
+
mday: 1
|
|
6
|
+
function: to_nearest_upcoming_weekend_day(date)
|
|
7
|
+
|
|
8
|
+
methods:
|
|
9
|
+
to_nearest_upcoming_weekend_day:
|
|
10
|
+
arguments: date
|
|
11
|
+
ruby: |
|
|
12
|
+
days_until = (6 - date.wday) % 7
|
|
13
|
+
days_until = 7 if days_until == 0
|
|
14
|
+
date + days_until
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
months:
|
|
2
|
+
1:
|
|
3
|
+
- name: Weekend Holiday
|
|
4
|
+
regions: [date_transform_conflict_2]
|
|
5
|
+
mday: 1
|
|
6
|
+
function: to_nearest_upcoming_weekend_day(date)
|
|
7
|
+
|
|
8
|
+
methods:
|
|
9
|
+
to_nearest_upcoming_weekend_day:
|
|
10
|
+
arguments: date
|
|
11
|
+
ruby: |
|
|
12
|
+
days_until = (7 - date.wday) % 7
|
|
13
|
+
days_until = 7 if days_until == 0
|
|
14
|
+
date + days_until
|
data/test/e2e/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# End-to-end tests
|
|
2
|
+
|
|
3
|
+
End-user behavioral checks against the **real upstream definitions** in
|
|
4
|
+
`/definitions`. These tests load actual region data and assert specific
|
|
5
|
+
holiday names, dates, counts, and observed-date behavior, the kinds of
|
|
6
|
+
expectations a consumer of the gem would care about.
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
These tests verify that the full pipeline, from upstream YAML through
|
|
11
|
+
generation through gem processing to the public API, produces the results
|
|
12
|
+
an end user expects for real-world regions. They complement fixture-based
|
|
13
|
+
integration tests by catching content regressions in the gem's interaction
|
|
14
|
+
with real definitions.
|
|
15
|
+
|
|
16
|
+
## E2E tests vs. integration vs. smoke
|
|
17
|
+
|
|
18
|
+
- **Smoke** (`test/smoke/`): only asserts nothing crashes. Run from the
|
|
19
|
+
upstream `holidays/definitions` CI.
|
|
20
|
+
- **Integration** (`test/integration/`): verifies gem-wide behavior using
|
|
21
|
+
controlled fixture YAMLs. Stable across definitions changes.
|
|
22
|
+
- **E2E** (this directory): verifies end-user-perceived behavior using real
|
|
23
|
+
upstream definitions. Expected to break when upstream definitions change,
|
|
24
|
+
on purpose.
|
|
25
|
+
|
|
26
|
+
## Important
|
|
27
|
+
|
|
28
|
+
These tests are tightly coupled to the contents of `/definitions`. When you
|
|
29
|
+
update the definitions submodule and an upstream change shifts a holiday name,
|
|
30
|
+
date, or count, the corresponding e2e test must be updated to match. That is
|
|
31
|
+
working as intended: the test is the gem's record of what end users see, so
|
|
32
|
+
updating it is part of accepting the upstream change.
|
|
33
|
+
|
|
34
|
+
This directory is **not** run by the upstream `holidays/definitions` CI. Doing
|
|
35
|
+
so would create a dependency loop where the definitions repo cannot ship a
|
|
36
|
+
legitimate content change without also updating tests in this repo first.
|
|
37
|
+
|
|
38
|
+
## What belongs here
|
|
39
|
+
|
|
40
|
+
- Tests that assert specific holidays exist on specific dates for specific
|
|
41
|
+
regions using real definitions (e.g. `:ca` returns "Labour Day" on Sept 1, 2008).
|
|
42
|
+
- Tests of observed-date behavior against real region rules.
|
|
43
|
+
- Tests of sub-region inheritance, wildcards (`:ca_`), `:any`, and cross-region
|
|
44
|
+
conflicts using real data.
|
|
45
|
+
- Region-count or holiday-count assertions for specific year/region combinations.
|
|
46
|
+
|
|
47
|
+
## What does NOT belong here
|
|
48
|
+
|
|
49
|
+
- Tests that don't depend on real definitions content. Those belong in
|
|
50
|
+
`integration/` (with fixtures) or `smoke/` (structural only).
|
|
51
|
+
- Tests of a single file or class in isolation. Those are unit tests and
|
|
52
|
+
belong next to the code they cover.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
months:
|
|
2
|
+
0:
|
|
3
|
+
- name: With Function Modifier
|
|
4
|
+
regions: [multiple_with_conflict_3]
|
|
5
|
+
function: easter(year)
|
|
6
|
+
function_modifier: 70
|
|
7
|
+
- name: With Function Only Different Function Name
|
|
8
|
+
regions: [multiple_with_conflict_3]
|
|
9
|
+
function: conflict_custom_method_3(year)
|
|
10
|
+
- name: With Function Only Same Function Name
|
|
11
|
+
regions: [multiple_with_conflict_3]
|
|
12
|
+
function: conflict_custom_method_identical_name_between_regions(year)
|
|
13
|
+
- name: With Function Only Same Function Name - Region 3
|
|
14
|
+
regions: [multiple_with_conflict_3]
|
|
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_3]
|
|
19
|
+
mday: 1
|
|
20
|
+
observed: to_monday_if_weekend(date)
|
|
21
|
+
10:
|
|
22
|
+
- name: Testing Conflict Month 10
|
|
23
|
+
regions: [multiple_with_conflict_3]
|
|
24
|
+
mday: 9
|
|
25
|
+
|
|
26
|
+
methods:
|
|
27
|
+
conflict_custom_method_3:
|
|
28
|
+
arguments: year
|
|
29
|
+
ruby: |
|
|
30
|
+
Date.civil(year, 4, 1)
|
|
31
|
+
conflict_custom_method_identical_name_between_regions:
|
|
32
|
+
arguments: year
|
|
33
|
+
ruby: |
|
|
34
|
+
Date.civil(year, 3, 1)
|
|
35
|
+
conflict_custom_method_identical_name_between_regions_but_different_holiday_names:
|
|
36
|
+
arguments: year
|
|
37
|
+
ruby: |
|
|
38
|
+
Date.civil(year, 3, 15)
|