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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -1
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +5 -0
  5. data/Makefile +14 -3
  6. data/README.md +1 -2
  7. data/Rakefile +19 -0
  8. data/doc/MAINTAINERS.md +2 -4
  9. data/holidays.gemspec +3 -3
  10. data/lib/generated_definitions/bg.rb +3 -2
  11. data/lib/generated_definitions/cy.rb +2 -1
  12. data/lib/generated_definitions/europe.rb +2 -1
  13. data/lib/holidays/definition/context/function_processor.rb +1 -1
  14. data/lib/holidays/definition/context/merger.rb +20 -4
  15. data/lib/holidays/definition/repository/custom_methods.rb +37 -9
  16. data/lib/holidays/finder/context/search.rb +43 -24
  17. data/lib/holidays/version.rb +1 -1
  18. data/lib/holidays.rb +9 -4
  19. data/test/coverage_report.rb +3 -5
  20. data/test/data/test_date_transform_conflict_region_1.yaml +14 -0
  21. data/test/data/test_date_transform_conflict_region_2.yaml +14 -0
  22. data/test/e2e/README.md +52 -0
  23. data/test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml +38 -0
  24. data/test/{integration → e2e}/test_holidays.rb +3 -2
  25. data/test/e2e/test_multiple_regions_with_conflict.rb +228 -0
  26. data/test/holidays/definition/context/test_function_processor.rb +2 -2
  27. data/test/holidays/definition/context/test_merger.rb +1 -1
  28. data/test/holidays/finder/context/test_search.rb +58 -0
  29. data/test/integration/README.md +45 -6
  30. data/test/integration/test_custom_holidays.rb +1 -1
  31. data/test/integration/test_custom_informal_holidays.rb +1 -1
  32. data/test/smoke/README.md +31 -0
  33. data/test/{integration → smoke}/test_available_regions.rb +0 -5
  34. data/test/smoke/test_smoke.rb +74 -0
  35. metadata +46 -34
  36. data/test/integration/test_multiple_regions_with_conflict.rb +0 -29
  37. /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_1.yaml +0 -0
  38. /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_2.yaml +0 -0
  39. /data/test/{integration → e2e}/test_all_regions.rb +0 -0
  40. /data/test/{integration → e2e}/test_any_holidays_during_work_week.rb +0 -0
  41. /data/test/{integration → e2e}/test_holidays_between.rb +0 -0
  42. /data/test/{integration → e2e}/test_multiple_regions.rb +0 -0
  43. /data/test/{integration → e2e}/test_nonstandard_regions.rb +0 -0
  44. /data/test/{data → integration/data}/test_custom_govt_holiday_defs.yaml +0 -0
  45. /data/test/{data → integration/data}/test_custom_informal_holidays_defs.yaml +0 -0
  46. /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: c423e457490bf307e7fabdfc2744e0e8621eb6f0a783e593023f562dccd8ad68
4
- data.tar.gz: a6ce029fecfb730da6d6b6a0a75f4c7b718937d5a95d0d03fc407df58083ecb7
3
+ metadata.gz: f0f84e8d40a176b0172092f8884b09567b06a4cd870a082920fbb274acb6b43f
4
+ data.tar.gz: bde696d82c96c50a14e5defad021c14aa541456c2eee281580b9e0fe5f1bee07
5
5
  SHA512:
6
- metadata.gz: 9f2885529f2b22865d406bd38bafa8f169c94df37c4f7c6b51eef95d5aa2085cd29c0d4f9b9c8f3d2d2cb2ac887f984ba5521ecf279c548ec755b8a44ccb007a
7
- data.tar.gz: 9c1aeaf7d210fe849d1977f931e625b895e8bced4924a26643a47e3edaca75451743ca036e864e8772901df92738fb612251195bfdf62b494ad01a3935c5eb91
6
+ metadata.gz: 90aa255cd3e08574b8b796fee989123b473a6dcb45ad522c053272a6299e03c8b84b0852ec7c9a1c8c3a2db1420f23ce963403b7439239cbe6b4f5a7b6fa35d9
7
+ data.tar.gz: 0e1bc79739ba0a46b6ebd80b2eb3453dde2eed2ab18db95c0088d42dba7505a01587052f7cb38fbc0f546e363ce2bde23a2ae09ae67b06d8280fcf6bfc4728e2
@@ -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
@@ -1,3 +1,8 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ group :development do
6
+ gem 'irb'
7
+ gem 'rdoc'
8
+ end
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) git@github.com:$(USER)/definitions.git definitions/
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
@@ -18,8 +18,7 @@ This gem is tested with the following ruby versions:
18
18
  * 3.3
19
19
  * 3.4
20
20
  * 4.0
21
- * JRuby 9.2.21.0
22
- * JRuby 9.4.2.0
21
+ * JRuby 10.0.4.0
23
22
 
24
23
  ## Semver
25
24
 
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, pull down the latest master from Github and run `make build`. This will generate a new `gem` file with the new version. The new version number is pulled from the above `version.rb` update.
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, pull down the latest master from Github and run `make build`. This will generate a new `gem` file with the new version. The new version number is pulled from the above `version.rb` update.
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', '>= 2'
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', '~> 2'
30
- gem.add_development_dependency 'pry', '~> 0.12'
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, :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, :name => "Επέτειος του Όχι", :regions => [:cy]}],
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, :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(target_custom_methods)
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
- # This performs a merge that overwrites any conflicts.
10
- # While this is not ideal I'm leaving it as-is since I have no
11
- # evidence of any current definitions that will cause an issue.
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
- @custom_methods.merge!(new_custom_methods)
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
- def find(method_id)
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
- date = build_date(year, month, h)
28
- next unless date
29
-
30
- if observed_set?(options) && h[:observed]
31
- date = build_observed_date(date, regions, h)
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
- holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
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
- if h[:function]
73
- holiday = custom_holiday(year, month, h)
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 custom_holiday(year, month, h)
88
- @custom_method_processor.call(
89
- #FIXME This seems like a bug, we seem to expect the day in here in the au defs?
90
- build_custom_method_input(year, month, h[:mday], h[:regions]),
91
- h[:function], h[:function_arguments], h[:function_modifier],
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
- def build_custom_method_input(year, month, day, regions)
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: regions.first, #FIXME This isn't ideal but will work for our current use case...
119
+ region: effective_region,
101
120
  }
102
121
  end
103
122
 
@@ -1,3 +1,3 @@
1
1
  module Holidays
2
- VERSION = '9.1.0'
2
+ VERSION = '9.1.2'
3
3
  end
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
- if cached_holidays = Factory::Definition.cache_repository.find(start_date, end_date, options)
46
- return cached_holidays
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
@@ -1,10 +1,8 @@
1
1
  require 'simplecov'
2
2
 
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
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
@@ -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)
@@ -237,7 +237,8 @@ class HolidaysTests < Test::Unit::TestCase
237
237
  end
238
238
 
239
239
  def test_load_all
240
- Holidays.load_all
241
- assert_equal 286, Holidays.available_regions.count
240
+ assert_nothing_raised do
241
+ Holidays.load_all
242
+ end
242
243
  end
243
244
  end