holidays 4.6.0 → 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
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