holidays 4.6.0 → 4.7.0

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/CONTRIBUTING.md +5 -5
  4. data/Makefile +29 -0
  5. data/README.md +13 -1
  6. data/Rakefile +2 -2
  7. data/definitions/au.yaml +12 -9
  8. data/definitions/ca.yaml +17 -2
  9. data/definitions/ch.yaml +1 -1
  10. data/definitions/index.yaml +3 -0
  11. data/definitions/is.yaml +1 -1
  12. data/definitions/jp.yaml +19 -14
  13. data/definitions/kr.yaml +282 -0
  14. data/definitions/lu.yaml +56 -0
  15. data/definitions/my.yaml +51 -0
  16. data/definitions/ups.yaml +1 -1
  17. data/definitions/us.yaml +1 -1
  18. data/lib/generated_definitions/MANIFEST +3 -0
  19. data/lib/generated_definitions/REGIONS.rb +1 -1
  20. data/lib/generated_definitions/au.rb +11 -8
  21. data/lib/generated_definitions/ca.rb +1 -1
  22. data/lib/generated_definitions/ch.rb +1 -1
  23. data/lib/generated_definitions/europe.rb +2 -2
  24. data/lib/generated_definitions/is.rb +1 -1
  25. data/lib/generated_definitions/jp.rb +13 -15
  26. data/lib/generated_definitions/kr.rb +248 -0
  27. data/lib/generated_definitions/lu.rb +39 -0
  28. data/lib/generated_definitions/my.rb +36 -0
  29. data/lib/generated_definitions/north_america.rb +2 -2
  30. data/lib/generated_definitions/scandinavia.rb +1 -1
  31. data/lib/generated_definitions/ups.rb +1 -1
  32. data/lib/generated_definitions/us.rb +1 -1
  33. data/lib/holidays.rb +29 -51
  34. data/lib/holidays/core_extensions/date.rb +1 -1
  35. data/lib/holidays/definition/context/function_processor.rb +86 -0
  36. data/lib/holidays/definition/context/generator.rb +31 -6
  37. data/lib/holidays/definition/parser/custom_method.rb +1 -3
  38. data/lib/holidays/definition/repository/holidays_by_month.rb +1 -1
  39. data/lib/holidays/definition/validator/region.rb +1 -3
  40. data/lib/holidays/errors.rb +1 -0
  41. data/lib/holidays/factory/date_calculator.rb +37 -0
  42. data/lib/holidays/factory/definition.rb +96 -0
  43. data/lib/holidays/factory/finder.rb +70 -0
  44. data/lib/holidays/finder/context/between.rb +43 -0
  45. data/lib/holidays/{use_case → finder}/context/dates_driver_builder.rb +6 -4
  46. data/lib/holidays/finder/context/next_holiday.rb +57 -0
  47. data/lib/holidays/{option → finder}/context/parse_options.rb +6 -8
  48. data/lib/holidays/finder/context/search.rb +86 -0
  49. data/lib/holidays/finder/context/year_holiday.rb +57 -0
  50. data/lib/holidays/finder/rules/in_region.rb +23 -0
  51. data/lib/holidays/finder/rules/year_range.rb +82 -0
  52. data/lib/holidays/load_all_definitions.rb +7 -5
  53. data/lib/holidays/version.rb +1 -1
  54. data/test/coverage_report.rb +7 -0
  55. data/test/defs/test_defs_au.rb +1 -1
  56. data/test/defs/test_defs_ca.rb +16 -1
  57. data/test/defs/test_defs_jp.rb +4 -0
  58. data/test/defs/test_defs_kr.rb +26 -0
  59. data/test/defs/test_defs_lu.rb +24 -0
  60. data/test/defs/test_defs_my.rb +20 -0
  61. data/test/defs/test_defs_north_america.rb +16 -1
  62. data/test/holidays/core_extensions/test_date_time.rb +7 -7
  63. data/test/holidays/definition/context/test_function_processor.rb +175 -0
  64. data/test/holidays/definition/context/test_generator.rb +18 -11
  65. data/test/holidays/definition/parser/test_custom_method.rb +2 -2
  66. data/test/holidays/definition/repository/test_proc_result_cache.rb +1 -1
  67. data/test/holidays/{test_date_calculator_factory.rb → factory/test_date_calculator.rb} +3 -3
  68. data/test/holidays/factory/test_definition.rb +53 -0
  69. data/test/holidays/factory/test_finder.rb +25 -0
  70. data/test/holidays/finder/context/test_between.rb +172 -0
  71. data/test/holidays/{use_case → finder}/context/test_dates_driver_builder.rb +2 -2
  72. data/test/holidays/finder/context/test_next_holiday.rb +156 -0
  73. data/test/holidays/{option → finder}/context/test_parse_options.rb +9 -9
  74. data/test/holidays/finder/context/test_search.rb +203 -0
  75. data/test/holidays/finder/context/test_year_holiday.rb +202 -0
  76. data/test/holidays/finder/rules/test_in_region.rb +38 -0
  77. data/test/holidays/finder/rules/test_year_range.rb +170 -0
  78. data/test/integration/README.md +9 -0
  79. data/test/{test_all_regions.rb → integration/test_all_regions.rb} +16 -2
  80. data/test/{test_custom_holidays.rb → integration/test_custom_holidays.rb} +2 -2
  81. data/test/{test_custom_year_range_holidays.rb → integration/test_custom_year_range_holidays.rb} +1 -1
  82. data/test/{test_holidays.rb → integration/test_holidays.rb} +43 -32
  83. data/test/{test_holidays_between.rb → integration/test_holidays_between.rb} +8 -16
  84. data/test/{test_multiple_regions.rb → integration/test_multiple_regions.rb} +1 -1
  85. data/test/test_helper.rb +3 -4
  86. metadata +67 -39
  87. data/benchmark.rb +0 -8
  88. data/lib/holidays/date_calculator_factory.rb +0 -35
  89. data/lib/holidays/definition_factory.rb +0 -86
  90. data/lib/holidays/option_factory.rb +0 -15
  91. data/lib/holidays/use_case/context/between.rb +0 -45
  92. data/lib/holidays/use_case/context/context_common.rb +0 -123
  93. data/lib/holidays/use_case/context/next_holiday.rb +0 -54
  94. data/lib/holidays/use_case/context/year_holiday.rb +0 -51
  95. data/lib/holidays/use_case_factory.rb +0 -41
  96. data/test/holidays/test_definition_factory.rb +0 -49
  97. data/test/holidays/test_option_factory.rb +0 -9
  98. data/test/holidays/test_use_case_factory.rb +0 -13
  99. data/test/holidays/use_case/context/test_between.rb +0 -77
@@ -30,11 +30,9 @@ module Holidays
30
30
 
31
31
  private
32
32
 
33
- attr_reader :validator
34
-
35
33
  def validate!(methods)
36
34
  raise ArgumentError unless methods.all? do |name, pieces|
37
- validator.valid?(
35
+ @validator.valid?(
38
36
  {
39
37
  :name => name,
40
38
  :arguments => pieces["arguments"],
@@ -33,7 +33,7 @@ module Holidays
33
33
  end
34
34
  end
35
35
 
36
- @holidays_by_month[month] << holiday_def unless exists
36
+ @holidays_by_month[month] << holiday_def unless exists
37
37
  end
38
38
  end
39
39
  end
@@ -12,14 +12,12 @@ module Holidays
12
12
  region = find_wildcard_base(r)
13
13
 
14
14
  (region == :any ||
15
- regions_repo.exists?(region) ||
15
+ @regions_repo.exists?(region) ||
16
16
  region_in_static_definitions?(region))
17
17
  end
18
18
 
19
19
  private
20
20
 
21
- attr_reader :regions_repo
22
-
23
21
  # Ex: :gb_ transformed to :gb
24
22
  def find_wildcard_base(region)
25
23
  r = region.to_s
@@ -2,5 +2,6 @@ module Holidays
2
2
  class Error < StandardError; end
3
3
 
4
4
  class FunctionNotFound < Error; end
5
+ class InvalidFunctionResponse < Error; end
5
6
  class UnknownRegionError < Error ; end
6
7
  end
@@ -0,0 +1,37 @@
1
+ require 'holidays/date_calculator/easter'
2
+ require 'holidays/date_calculator/weekend_modifier'
3
+ require 'holidays/date_calculator/day_of_month'
4
+
5
+ module Holidays
6
+ module Factory
7
+ module DateCalculator
8
+ module Easter
9
+ module Gregorian
10
+ class << self
11
+ def easter_calculator
12
+ Holidays::DateCalculator::Easter::Gregorian.new
13
+ end
14
+ end
15
+ end
16
+
17
+ module Julian
18
+ class << self
19
+ def easter_calculator
20
+ Holidays::DateCalculator::Easter::Julian.new
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ class << self
27
+ def weekend_modifier
28
+ Holidays::DateCalculator::WeekendModifier.new
29
+ end
30
+
31
+ def day_of_month_calculator
32
+ Holidays::DateCalculator::DayOfMonth.new
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,96 @@
1
+ require 'holidays/definition/context/generator'
2
+ require 'holidays/definition/context/merger'
3
+ require 'holidays/definition/context/function_processor'
4
+ require 'holidays/definition/decorator/custom_method_proc'
5
+ require 'holidays/definition/decorator/custom_method_source'
6
+ require 'holidays/definition/parser/custom_method'
7
+ require 'holidays/definition/repository/holidays_by_month'
8
+ require 'holidays/definition/repository/regions'
9
+ require 'holidays/definition/repository/cache'
10
+ require 'holidays/definition/repository/proc_result_cache'
11
+ require 'holidays/definition/repository/custom_methods'
12
+ require 'holidays/definition/validator/custom_method'
13
+ require 'holidays/definition/validator/region'
14
+
15
+ module Holidays
16
+ module Factory
17
+ module Definition
18
+ class << self
19
+ def file_parser
20
+ Holidays::Definition::Context::Generator.new(
21
+ custom_method_parser,
22
+ custom_method_source_decorator,
23
+ custom_methods_repository,
24
+ )
25
+ end
26
+
27
+ def source_generator
28
+ Holidays::Definition::Context::Generator.new(
29
+ custom_method_parser,
30
+ custom_method_source_decorator,
31
+ custom_methods_repository,
32
+ )
33
+ end
34
+
35
+ def function_processor
36
+ Holidays::Definition::Context::FunctionProcessor.new(
37
+ custom_methods_repository,
38
+ proc_result_cache_repository,
39
+ )
40
+ end
41
+
42
+ def merger
43
+ Holidays::Definition::Context::Merger.new(
44
+ holidays_by_month_repository,
45
+ regions_repository,
46
+ custom_methods_repository,
47
+ )
48
+ end
49
+
50
+ def custom_method_parser
51
+ Holidays::Definition::Parser::CustomMethod.new(
52
+ custom_method_validator,
53
+ )
54
+ end
55
+
56
+ def custom_method_proc_decorator
57
+ Holidays::Definition::Decorator::CustomMethodProc.new
58
+ end
59
+
60
+ def custom_method_source_decorator
61
+ Holidays::Definition::Decorator::CustomMethodSource.new
62
+ end
63
+
64
+ def region_validator
65
+ Holidays::Definition::Validator::Region.new(
66
+ regions_repository
67
+ )
68
+ end
69
+
70
+ def custom_method_validator
71
+ Holidays::Definition::Validator::CustomMethod.new
72
+ end
73
+
74
+ def holidays_by_month_repository
75
+ @holidays_repo ||= Holidays::Definition::Repository::HolidaysByMonth.new
76
+ end
77
+
78
+ def regions_repository
79
+ @regions_repo ||= Holidays::Definition::Repository::Regions.new
80
+ end
81
+
82
+ def cache_repository
83
+ @cache_repo ||= Holidays::Definition::Repository::Cache.new
84
+ end
85
+
86
+ def proc_result_cache_repository
87
+ @proc_result_cache_repo ||= Holidays::Definition::Repository::ProcResultCache.new
88
+ end
89
+
90
+ def custom_methods_repository
91
+ @custom_methods_repository ||= Holidays::Definition::Repository::CustomMethods.new
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,70 @@
1
+ require 'holidays/finder/context/between'
2
+ require 'holidays/finder/context/dates_driver_builder'
3
+ require 'holidays/finder/context/next_holiday'
4
+ require 'holidays/finder/context/parse_options'
5
+ require 'holidays/finder/context/search'
6
+ require 'holidays/finder/context/year_holiday'
7
+ require 'holidays/finder/rules/in_region'
8
+ require 'holidays/finder/rules/year_range'
9
+
10
+ module Holidays
11
+ module Factory
12
+ module Finder
13
+ class << self
14
+ def search
15
+ Holidays::Finder::Context::Search.new(
16
+ Factory::Definition.holidays_by_month_repository,
17
+ Factory::Definition.function_processor,
18
+ Factory::DateCalculator.day_of_month_calculator,
19
+ rules,
20
+ )
21
+ end
22
+
23
+ def between
24
+ Holidays::Finder::Context::Between.new(
25
+ search,
26
+ dates_driver_builder,
27
+ parse_options,
28
+ )
29
+ end
30
+
31
+ def next_holiday
32
+ Holidays::Finder::Context::NextHoliday.new(
33
+ search,
34
+ dates_driver_builder,
35
+ parse_options,
36
+ )
37
+ end
38
+
39
+ def year_holiday
40
+ Holidays::Finder::Context::YearHoliday.new(
41
+ search,
42
+ dates_driver_builder,
43
+ parse_options,
44
+ )
45
+ end
46
+
47
+ def parse_options
48
+ Holidays::Finder::Context::ParseOptions.new(
49
+ Factory::Definition.regions_repository,
50
+ Factory::Definition.region_validator,
51
+ Factory::Definition.merger,
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ def dates_driver_builder
58
+ Holidays::Finder::Context::DatesDriverBuilder.new
59
+ end
60
+
61
+ def rules
62
+ {
63
+ :in_region => Holidays::Finder::Rules::InRegion,
64
+ :year_range => Holidays::Finder::Rules::YearRange,
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,43 @@
1
+ module Holidays
2
+ module Finder
3
+ module Context
4
+ class Between
5
+ def initialize(definition_search, dates_driver_builder, options_parser)
6
+ @definition_search = definition_search
7
+ @dates_driver_builder = dates_driver_builder
8
+ @options_parser = options_parser
9
+ end
10
+
11
+ def call(start_date, end_date, options)
12
+ validate!(start_date, end_date)
13
+
14
+ regions, observed, informal = @options_parser.call(options)
15
+ dates_driver = @dates_driver_builder.call(start_date, end_date)
16
+
17
+ holidays = []
18
+ opts = gather_options(observed, informal)
19
+
20
+ holidays = @definition_search.call(dates_driver, regions, opts)
21
+ holidays = holidays.select{|holiday|holiday[:date].between?(start_date, end_date)}
22
+ holidays.sort{|a, b| a[:date] <=> b[:date] }
23
+ end
24
+
25
+ private
26
+
27
+ def validate!(start_date, end_date)
28
+ raise ArgumentError unless start_date
29
+ raise ArgumentError unless end_date
30
+ end
31
+
32
+ def gather_options(observed, informal)
33
+ opts = []
34
+
35
+ opts << :observed if observed == true
36
+ opts << :informal if informal == true
37
+
38
+ opts
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -2,10 +2,10 @@
2
2
  # we will iterate over each year and then over each month internally and check to see if the
3
3
  # supplied dates match any holidays for the region and date. So if we supply start_date of 2015/1/1
4
4
  # and end_date of 2015/6/1 then we will return a date driver of {:2015 => [0, 1, 2, 5, 6, 7]}.
5
- # In the logic in the 'between' use case we will iterate over this and compare dates in these
6
- # months to the supplied range to determine whether they should be returned to the user.
5
+ # In the logic in the various other 'finder' contexts we will iterate over this and compare dates
6
+ # in these months to the supplied range to determine whether they should be returned to the user.
7
7
  module Holidays
8
- module UseCase
8
+ module Finder
9
9
  module Context
10
10
  class DatesDriverBuilder
11
11
  def call(start_date, end_date)
@@ -54,7 +54,9 @@ module Holidays
54
54
 
55
55
  def clean(dates_driver)
56
56
  dates_driver.each do |year, months|
57
- dates_driver[year] << 0 # Always add variable month '0' for proc calc purposes
57
+ # Always add variable month '0' for proc calc purposes. For example, 'easter' lives in
58
+ # 'month 0' but is vital to calculating a lot of easter-related dates.
59
+ dates_driver[year] << 0
58
60
 
59
61
  dates_driver[year].uniq!
60
62
  dates_driver[year].sort!
@@ -0,0 +1,57 @@
1
+ module Holidays
2
+ module Finder
3
+ module Context
4
+ class NextHoliday
5
+ def initialize(definition_search, dates_driver_builder, options_parser)
6
+ @definition_search = definition_search
7
+ @dates_driver_builder = dates_driver_builder
8
+ @options_parser = options_parser
9
+ end
10
+
11
+ def call(holidays_count, from_date, options)
12
+ validate!(holidays_count, from_date)
13
+
14
+ regions, observed, informal = @options_parser.call(options)
15
+
16
+ holidays = []
17
+ ret_holidays = []
18
+ opts = gather_options(observed, informal)
19
+
20
+ # This could be smarter but I don't have any evidence that just checking for
21
+ # the next 12 months will cause us issues. If it does we can implement something
22
+ # smarter here to check in smaller increments.
23
+ dates_driver = @dates_driver_builder.call(from_date, from_date >> 12)
24
+
25
+ ret_holidays = @definition_search.call(dates_driver, regions, opts)
26
+
27
+ ret_holidays.sort{|a, b| a[:date] <=> b[:date] }.each do |holiday|
28
+ if holiday[:date] >= from_date
29
+ holidays << holiday
30
+ holidays_count -= 1
31
+ break if holidays_count == 0
32
+ end
33
+ end
34
+
35
+ holidays.sort{|a, b| a[:date] <=> b[:date] }
36
+ end
37
+
38
+ private
39
+
40
+ def validate!(holidays_count, from_date)
41
+ raise ArgumentError unless holidays_count
42
+ raise ArgumentError if holidays_count <= 0
43
+ raise ArgumentError unless from_date
44
+ end
45
+
46
+ def gather_options(observed, informal)
47
+ opts = []
48
+
49
+ opts << :observed if observed == true
50
+ opts << :informal if informal == true
51
+
52
+ opts
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,5 @@
1
1
  module Holidays
2
- module Option
2
+ module Finder
3
3
  module Context
4
4
  class ParseOptions
5
5
  def initialize(regions_repo, region_validator, definition_merger)
@@ -24,8 +24,6 @@ module Holidays
24
24
 
25
25
  private
26
26
 
27
- attr_reader :regions_repo, :region_validator, :definition_merger
28
-
29
27
  # Check regions against list of supported regions and return an array of
30
28
  # symbols.
31
29
  #
@@ -43,7 +41,7 @@ module Holidays
43
41
  regions.delete_if do |r|
44
42
  if r.to_s =~ /_$/
45
43
  load_containing_region(r.to_s)
46
- regions << regions_repo.search(r.to_s)
44
+ regions << @regions_repo.search(r.to_s)
47
45
  true
48
46
  end
49
47
  end
@@ -53,7 +51,7 @@ module Holidays
53
51
  load_definition_data("north_america") if regions.include?(:us) # special case for north_america/US cross-linking
54
52
 
55
53
  regions.each do |region|
56
- unless region == :any || regions_repo.exists?(region)
54
+ unless region == :any || @regions_repo.exists?(region)
57
55
  begin
58
56
  load_definition_data(region.to_s)
59
57
  rescue NameError => e
@@ -73,7 +71,7 @@ module Holidays
73
71
 
74
72
  def validate!(regions)
75
73
  regions.each do |r|
76
- raise UnknownRegionError unless region_validator.valid?(r)
74
+ raise UnknownRegionError unless @region_validator.valid?(r)
77
75
  end
78
76
  end
79
77
 
@@ -82,7 +80,7 @@ module Holidays
82
80
  def load_containing_region(sub_reg)
83
81
  prefix = sub_reg.split('_').first
84
82
 
85
- return if regions_repo.exists?(prefix.to_sym)
83
+ return if @regions_repo.exists?(prefix.to_sym)
86
84
 
87
85
  begin
88
86
  load_definition_data(prefix)
@@ -94,7 +92,7 @@ module Holidays
94
92
  def load_definition_data(region)
95
93
  target_region_module = Module.const_get("Holidays").const_get(region.upcase)
96
94
 
97
- definition_merger.call(
95
+ @definition_merger.call(
98
96
  target_region_module.defined_regions,
99
97
  target_region_module.holidays_by_month,
100
98
  target_region_module.custom_methods,
@@ -0,0 +1,86 @@
1
+ module Holidays
2
+ module Finder
3
+ module Context
4
+ class Search
5
+ def initialize(holidays_by_month_repo, custom_method_processor, day_of_month_calculator, rules)
6
+ @holidays_by_month_repo = holidays_by_month_repo
7
+ @custom_method_processor = custom_method_processor
8
+ @day_of_month_calculator = day_of_month_calculator
9
+ @rules = rules
10
+ end
11
+
12
+ def call(dates_driver, regions, options)
13
+ validate!(dates_driver)
14
+
15
+ holidays = []
16
+ dates_driver.each do |year, months|
17
+ months.each do |month|
18
+ next unless hbm = @holidays_by_month_repo.find_by_month(month)
19
+ hbm.each do |h|
20
+ next if (h[:type] == :informal) && !informal_set?(options)
21
+ next unless @rules[:in_region].call(regions, h[:regions])
22
+
23
+ if h[:year_ranges]
24
+ next unless @rules[:year_range].call(year, h[:year_ranges])
25
+ end
26
+
27
+ if h[:function]
28
+ result = @custom_method_processor.call(
29
+ year, month, h[:mday],
30
+ h[:function], h[:function_arguments], h[:function_modifier],
31
+ )
32
+
33
+ #FIXME The result should always be present, see https://github.com/holidays/holidays/issues/204 for more information
34
+ if result
35
+ month = result.month
36
+ mday = result.mday
37
+ end
38
+ else
39
+ mday = h[:mday] || @day_of_month_calculator.call(year, month, h[:week], h[:wday])
40
+ end
41
+
42
+ # Silently skip bad mdays
43
+ #TODO Should we be doing something different here? We have no concept of logging right now. Maybe we should add it?
44
+ begin
45
+ date = Date.civil(year, month, mday)
46
+ rescue; next; end
47
+
48
+ if observed_set?(options) && h[:observed]
49
+ date = @custom_method_processor.call(
50
+ date.year, date.month, date.day,
51
+ h[:observed],
52
+ [:date],
53
+ )
54
+ end
55
+
56
+ holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
57
+ end
58
+ end
59
+ end
60
+
61
+ holidays
62
+ end
63
+
64
+ private
65
+
66
+ def validate!(dates_driver)
67
+ raise ArgumentError if dates_driver.nil? || dates_driver.empty?
68
+
69
+ dates_driver.each do |year, months|
70
+ months.each do |month|
71
+ raise ArgumentError unless month >= 0 && month <= 12
72
+ end
73
+ end
74
+ end
75
+
76
+ def informal_set?(options)
77
+ options && options.include?(:informal) == true
78
+ end
79
+
80
+ def observed_set?(options)
81
+ options && options.include?(:observed) == true
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end