holidays 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +22 -8
- data/Rakefile +26 -8
- data/bin/console +7 -0
- data/bin/setup +5 -0
- data/{data → definitions}/SYNTAX.rdoc +0 -0
- data/{data → definitions}/ar.yaml +0 -0
- data/{data → definitions}/at.yaml +1 -1
- data/{data → definitions}/au.yaml +80 -51
- data/{data → definitions}/be.yaml +0 -0
- data/{data → definitions}/br.yaml +0 -0
- data/{data → definitions}/ca.yaml +0 -0
- data/{data → definitions}/ch.yaml +0 -0
- data/{data → definitions}/cl.yaml +0 -0
- data/{data → definitions}/cr.yaml +0 -0
- data/{data → definitions}/cz.yaml +0 -0
- data/{data → definitions}/de.yaml +25 -34
- data/{data → definitions}/dk.yaml +0 -0
- data/{data → definitions}/ecb_target.yaml +2 -2
- data/{data → definitions}/el.yaml +0 -0
- data/{data → definitions}/es.yaml +37 -26
- data/{data → definitions}/federal_reserve.yaml +0 -0
- data/{data → definitions}/fedex.yaml +9 -3
- data/{data → definitions}/fi.yaml +0 -0
- data/{data → definitions}/fr.yaml +0 -0
- data/{data → definitions}/gb.yaml +19 -19
- data/{data → definitions}/hr.yaml +0 -0
- data/{data → definitions}/hu.yaml +1 -1
- data/{data → definitions}/ie.yaml +0 -0
- data/{data → definitions}/index.yaml +0 -0
- data/{data → definitions}/is.yaml +0 -0
- data/{data → definitions}/it.yaml +0 -0
- data/{data → definitions}/jp.yaml +3 -3
- data/{data → definitions}/li.yaml +3 -3
- data/{data → definitions}/lt.yaml +0 -0
- data/{data → definitions}/ma.yaml +0 -0
- data/{data → definitions}/mx.yaml +0 -0
- data/{data → definitions}/nerc.yaml +0 -0
- data/{data → definitions}/nl.yaml +0 -0
- data/{data → definitions}/no.yaml +0 -0
- data/{data → definitions}/north_america_informal.yaml +0 -0
- data/{data → definitions}/nyse.yaml +0 -0
- data/{data → definitions}/nz.yaml +21 -10
- data/{data → definitions}/ph.yaml +0 -0
- data/{data → definitions}/pl.yaml +0 -0
- data/{data → definitions}/pt.yaml +0 -0
- data/{data → definitions}/ro.yaml +1 -2
- data/{data → definitions}/se.yaml +0 -0
- data/{data → definitions}/sg.yaml +0 -0
- data/{data → definitions}/si.yaml +6 -6
- data/{data → definitions}/sk.yaml +0 -0
- data/{data → definitions}/united_nations.yaml +0 -0
- data/{data → definitions}/ups.yaml +17 -11
- data/{data → definitions}/us.yaml +20 -15
- data/{data → definitions}/ve.yaml +0 -0
- data/{data → definitions}/vi.yaml +0 -0
- data/{data → definitions}/za.yaml +0 -0
- data/holidays.gemspec +2 -1
- data/lib/generated_definitions/MANIFEST +56 -0
- data/lib/generated_definitions/REGIONS.rb +4 -0
- data/lib/{holidays → generated_definitions}/ar.rb +2 -2
- data/lib/{holidays → generated_definitions}/at.rb +2 -2
- data/lib/{holidays → generated_definitions}/au.rb +19 -10
- data/lib/{holidays → generated_definitions}/be.rb +2 -2
- data/lib/{holidays → generated_definitions}/br.rb +2 -2
- data/lib/{holidays → generated_definitions}/ca.rb +2 -2
- data/lib/{holidays → generated_definitions}/ch.rb +2 -2
- data/lib/{holidays → generated_definitions}/cl.rb +2 -2
- data/lib/{holidays → generated_definitions}/cr.rb +2 -2
- data/lib/{holidays → generated_definitions}/cz.rb +2 -2
- data/lib/{holidays → generated_definitions}/de.rb +5 -4
- data/lib/{holidays → generated_definitions}/dk.rb +2 -2
- data/lib/{holidays → generated_definitions}/ecb_target.rb +2 -2
- data/lib/{holidays → generated_definitions}/el.rb +2 -2
- data/lib/{holidays → generated_definitions}/es.rb +6 -6
- data/lib/{holidays → generated_definitions}/europe.rb +7 -6
- data/lib/{holidays → generated_definitions}/fed_ex.rb +8 -3
- data/lib/{holidays → generated_definitions}/federal_reserve.rb +2 -2
- data/lib/{holidays → generated_definitions}/fi.rb +2 -2
- data/lib/{holidays → generated_definitions}/fr.rb +2 -2
- data/lib/{holidays → generated_definitions}/gb.rb +2 -2
- data/lib/{holidays → generated_definitions}/hr.rb +2 -2
- data/lib/{holidays → generated_definitions}/hu.rb +2 -2
- data/lib/{holidays → generated_definitions}/ie.rb +2 -2
- data/lib/{holidays → generated_definitions}/is.rb +2 -2
- data/lib/{holidays → generated_definitions}/it.rb +2 -2
- data/lib/{holidays → generated_definitions}/jp.rb +5 -5
- data/lib/{holidays → generated_definitions}/li.rb +2 -2
- data/lib/{holidays → generated_definitions}/lt.rb +2 -2
- data/lib/{holidays → generated_definitions}/ma.rb +2 -2
- data/lib/{holidays → generated_definitions}/mx.rb +2 -2
- data/lib/{holidays → generated_definitions}/nerc.rb +2 -2
- data/lib/{holidays → generated_definitions}/nl.rb +2 -2
- data/lib/{holidays → generated_definitions}/no.rb +2 -2
- data/lib/{holidays → generated_definitions}/north_america.rb +8 -3
- data/lib/{holidays → generated_definitions}/nyse.rb +2 -2
- data/lib/{holidays → generated_definitions}/nz.rb +5 -5
- data/lib/{holidays → generated_definitions}/ph.rb +2 -2
- data/lib/{holidays → generated_definitions}/pl.rb +2 -2
- data/lib/{holidays → generated_definitions}/pt.rb +2 -2
- data/lib/{holidays → generated_definitions}/ro.rb +2 -2
- data/lib/{holidays → generated_definitions}/scandinavia.rb +2 -2
- data/lib/{holidays → generated_definitions}/se.rb +2 -2
- data/lib/{holidays → generated_definitions}/sg.rb +2 -2
- data/lib/{holidays → generated_definitions}/si.rb +2 -2
- data/lib/{holidays → generated_definitions}/sk.rb +2 -2
- data/lib/{holidays → generated_definitions}/united_nations.rb +2 -2
- data/lib/{holidays → generated_definitions}/ups.rb +8 -3
- data/lib/{holidays → generated_definitions}/us.rb +8 -3
- data/lib/{holidays → generated_definitions}/ve.rb +2 -2
- data/lib/{holidays → generated_definitions}/vi.rb +2 -2
- data/lib/{holidays → generated_definitions}/za.rb +2 -2
- data/lib/holidays.rb +120 -665
- data/lib/holidays/core_extensions/date.rb +39 -0
- data/lib/holidays/date_calculator/day_of_month.rb +68 -0
- data/lib/holidays/date_calculator/easter.rb +58 -0
- data/lib/holidays/date_calculator/weekend_modifier.rb +49 -0
- data/lib/holidays/date_calculator_factory.rb +21 -0
- data/lib/holidays/definition/context/generator.rb +216 -0
- data/lib/holidays/definition/context/merger.rb +26 -0
- data/lib/holidays/definition/repository/cache.rb +33 -0
- data/lib/holidays/definition/repository/holidays_by_month.rb +49 -0
- data/lib/holidays/definition/repository/proc_cache.rb +36 -0
- data/lib/holidays/definition/repository/regions.rb +36 -0
- data/lib/holidays/definition/validator/region.rb +45 -0
- data/lib/holidays/definition_factory.rb +50 -0
- data/lib/holidays/option/context/parse_options.rb +96 -0
- data/lib/holidays/option_factory.rb +14 -0
- data/lib/holidays/use_case/context/between.rb +105 -0
- data/lib/holidays/use_case/context/dates_driver_builder.rb +64 -0
- data/lib/holidays/use_case_factory.rb +20 -0
- data/lib/holidays/version.rb +1 -1
- data/test/defs/test_defs_ar.rb +1 -1
- data/test/defs/test_defs_at.rb +2 -2
- data/test/defs/test_defs_au.rb +61 -43
- data/test/defs/test_defs_be.rb +1 -1
- data/test/defs/test_defs_br.rb +1 -1
- data/test/defs/test_defs_ca.rb +1 -1
- data/test/defs/test_defs_ch.rb +1 -1
- data/test/defs/test_defs_cl.rb +1 -1
- data/test/defs/test_defs_cr.rb +1 -1
- data/test/defs/test_defs_cz.rb +1 -1
- data/test/defs/test_defs_de.rb +18 -30
- data/test/defs/test_defs_dk.rb +1 -1
- data/test/defs/test_defs_ecb_target.rb +3 -3
- data/test/defs/test_defs_el.rb +1 -1
- data/test/defs/test_defs_es.rb +36 -25
- data/test/defs/test_defs_europe.rb +82 -84
- data/test/defs/test_defs_fed_ex.rb +4 -1
- data/test/defs/test_defs_federal_reserve.rb +1 -1
- data/test/defs/test_defs_fi.rb +1 -1
- data/test/defs/test_defs_fr.rb +1 -1
- data/test/defs/test_defs_gb.rb +20 -20
- data/test/defs/test_defs_hr.rb +1 -1
- data/test/defs/test_defs_hu.rb +2 -2
- data/test/defs/test_defs_ie.rb +1 -1
- data/test/defs/test_defs_is.rb +1 -1
- data/test/defs/test_defs_it.rb +1 -1
- data/test/defs/test_defs_jp.rb +1 -1
- data/test/defs/test_defs_li.rb +4 -4
- data/test/defs/test_defs_lt.rb +1 -1
- data/test/defs/test_defs_ma.rb +1 -1
- data/test/defs/test_defs_mx.rb +1 -1
- data/test/defs/test_defs_nerc.rb +1 -1
- data/test/defs/test_defs_nl.rb +1 -1
- data/test/defs/test_defs_no.rb +1 -1
- data/test/defs/test_defs_north_america.rb +5 -3
- data/test/defs/test_defs_nyse.rb +1 -1
- data/test/defs/test_defs_nz.rb +19 -9
- data/test/defs/test_defs_ph.rb +1 -1
- data/test/defs/test_defs_pl.rb +1 -1
- data/test/defs/test_defs_pt.rb +1 -1
- data/test/defs/test_defs_ro.rb +2 -3
- data/test/defs/test_defs_scandinavia.rb +1 -1
- data/test/defs/test_defs_se.rb +1 -1
- data/test/defs/test_defs_sg.rb +1 -1
- data/test/defs/test_defs_si.rb +7 -7
- data/test/defs/test_defs_sk.rb +1 -1
- data/test/defs/test_defs_united_nations.rb +1 -1
- data/test/defs/test_defs_ups.rb +5 -2
- data/test/defs/test_defs_us.rb +5 -3
- data/test/defs/test_defs_ve.rb +1 -1
- data/test/defs/test_defs_vi.rb +1 -1
- data/test/defs/test_defs_za.rb +1 -1
- data/test/{test_date.rb → holidays/core_extensions/test_date.rb} +8 -2
- data/test/holidays/date_calculator/test_day_of_month.rb +27 -0
- data/test/holidays/date_calculator/test_easter.rb +29 -0
- data/test/holidays/date_calculator/test_weekend_modifier.rb +33 -0
- data/test/holidays/definition/context/test_generator.rb +84 -0
- data/test/holidays/definition/context/test_merger.rb +22 -0
- data/test/holidays/definition/repository/test_cache.rb +82 -0
- data/test/holidays/definition/repository/test_holidays_by_month.rb +187 -0
- data/test/holidays/definition/repository/test_proc_cache.rb +29 -0
- data/test/holidays/definition/repository/test_regions.rb +86 -0
- data/test/holidays/definition/validator/test_region.rb +50 -0
- data/test/holidays/option/context/test_parse_options.rb +69 -0
- data/test/holidays/test_date_calculator_factory.rb +21 -0
- data/test/holidays/test_definition_factory.rb +34 -0
- data/test/holidays/test_option_factory.rb +9 -0
- data/test/holidays/test_use_case_factory.rb +13 -0
- data/test/holidays/use_case/context/test_between.rb +75 -0
- data/test/holidays/use_case/context/test_dates_driver_builder.rb +91 -0
- data/test/test_all_regions.rb +14 -3
- data/test/test_helper.rb +2 -1
- data/test/test_holidays.rb +19 -24
- data/test/test_holidays_between.rb +85 -0
- data/test/test_multiple_regions.rb +2 -2
- data/test/test_parse_definitions.rb +10 -4
- metadata +181 -111
- data/.coveralls.yml +0 -1
- data/lib/holidays/MANIFEST +0 -55
@@ -0,0 +1,26 @@
|
|
1
|
+
module Holidays
|
2
|
+
module Definition
|
3
|
+
module Context
|
4
|
+
# Merge a new set of definitions into the Holidays module.
|
5
|
+
#
|
6
|
+
# This method is automatically called when including holiday definition
|
7
|
+
# files. This is accomplished because the Generator class generates the
|
8
|
+
# definition source with this class explicitly.
|
9
|
+
class Merger
|
10
|
+
def initialize(holidays_by_month_repo, regions_repo)
|
11
|
+
@holidays_repo = holidays_by_month_repo
|
12
|
+
@regions_repo = regions_repo
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(target_regions, target_holidays)
|
16
|
+
regions_repo.add(target_regions)
|
17
|
+
holidays_repo.add(target_holidays)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :holidays_repo, :regions_repo
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Holidays
|
2
|
+
module Definition
|
3
|
+
module Repository
|
4
|
+
class Cache
|
5
|
+
def initialize
|
6
|
+
reset!
|
7
|
+
end
|
8
|
+
|
9
|
+
def cache_between(start_date, end_date, cache_data, *options)
|
10
|
+
raise ArgumentError unless cache_data
|
11
|
+
|
12
|
+
@cache_range[options] = start_date..end_date
|
13
|
+
@cache[options] = cache_data
|
14
|
+
end
|
15
|
+
|
16
|
+
def find(start_date, end_date, *options)
|
17
|
+
if range = @cache_range[options]
|
18
|
+
if range.begin <= start_date && range.end >= end_date
|
19
|
+
return @cache[options].select do |holiday|
|
20
|
+
holiday[:date] >= start_date && holiday[:date] <= end_date
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def reset!
|
27
|
+
@cache = {}
|
28
|
+
@cache_range = {}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Holidays
|
2
|
+
module Definition
|
3
|
+
module Repository
|
4
|
+
class HolidaysByMonth
|
5
|
+
def initialize
|
6
|
+
@holidays_by_month = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def all
|
10
|
+
@holidays_by_month
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_by_month(month)
|
14
|
+
raise ArgumentError unless month >= 0 && month <= 12
|
15
|
+
@holidays_by_month[month]
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(new_holidays)
|
19
|
+
new_holidays.each do |month, holiday_defs|
|
20
|
+
@holidays_by_month[month] = [] unless @holidays_by_month[month]
|
21
|
+
|
22
|
+
holiday_defs.each do |holiday_def|
|
23
|
+
exists = false
|
24
|
+
@holidays_by_month[month].each do |existing_def|
|
25
|
+
if definition_exists?(existing_def, holiday_def)
|
26
|
+
# append regions
|
27
|
+
existing_def[:regions] << holiday_def[:regions]
|
28
|
+
|
29
|
+
# Should do this once we're done
|
30
|
+
existing_def[:regions].flatten!
|
31
|
+
existing_def[:regions].uniq!
|
32
|
+
exists = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@holidays_by_month[month] << holiday_def unless exists
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def definition_exists?(existing_def, target_def)
|
44
|
+
existing_def[:name] == target_def[:name] && existing_def[:wday] == target_def[:wday] && existing_def[:mday] == target_def[:mday] && existing_def[:week] == target_def[:week] && existing_def[:function_id] == target_def[:function_id] && existing_def[:type] == target_def[:type] && existing_def[:observed_id] == target_def[:observed_id]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Holidays
|
2
|
+
module Definition
|
3
|
+
module Repository
|
4
|
+
# ==== Benchmarks
|
5
|
+
#
|
6
|
+
# Lookup Easter Sunday, with caching, by number of iterations:
|
7
|
+
#
|
8
|
+
# user system total real
|
9
|
+
# 0001 0.000000 0.000000 0.000000 ( 0.000000)
|
10
|
+
# 0010 0.000000 0.000000 0.000000 ( 0.000000)
|
11
|
+
# 0100 0.078000 0.000000 0.078000 ( 0.078000)
|
12
|
+
# 1000 0.641000 0.000000 0.641000 ( 0.641000)
|
13
|
+
# 5000 3.172000 0.015000 3.187000 ( 3.219000)
|
14
|
+
#
|
15
|
+
# Lookup Easter Sunday, without caching, by number of iterations:
|
16
|
+
#
|
17
|
+
# user system total real
|
18
|
+
# 0001 0.000000 0.000000 0.000000 ( 0.000000)
|
19
|
+
# 0010 0.016000 0.000000 0.016000 ( 0.016000)
|
20
|
+
# 0100 0.125000 0.000000 0.125000 ( 0.125000)
|
21
|
+
# 1000 1.234000 0.000000 1.234000 ( 1.234000)
|
22
|
+
# 5000 6.094000 0.031000 6.125000 ( 6.141000)
|
23
|
+
class ProcCache
|
24
|
+
def initialize
|
25
|
+
@proc_cache = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def lookup(function, year)
|
29
|
+
proc_key = Digest::MD5.hexdigest("#{function.to_s}_#{year.to_s}")
|
30
|
+
@proc_cache[proc_key] = function.call(year) unless @proc_cache[proc_key]
|
31
|
+
@proc_cache[proc_key]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Holidays
|
2
|
+
module Definition
|
3
|
+
module Repository
|
4
|
+
class Regions
|
5
|
+
def initialize
|
6
|
+
@regions = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def all
|
10
|
+
@regions
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(regions)
|
14
|
+
regions = [regions] unless regions.is_a?(Array)
|
15
|
+
|
16
|
+
regions.each do |region|
|
17
|
+
raise ArgumentError unless region.is_a?(Symbol)
|
18
|
+
end
|
19
|
+
|
20
|
+
@regions = @regions | regions
|
21
|
+
@regions.uniq!
|
22
|
+
end
|
23
|
+
|
24
|
+
def exists?(region)
|
25
|
+
raise ArgumentError unless region.is_a?(Symbol)
|
26
|
+
@regions.include?(region)
|
27
|
+
end
|
28
|
+
|
29
|
+
def search(prefix)
|
30
|
+
raise ArgumentError unless prefix.is_a?(String)
|
31
|
+
@regions.select { |region| region.to_s =~ Regexp.new("^#{prefix}") }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Holidays
|
2
|
+
module Definition
|
3
|
+
module Validator
|
4
|
+
class Region
|
5
|
+
def initialize(regions_repo)
|
6
|
+
@regions_repo = regions_repo
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?(r)
|
10
|
+
return false unless r.is_a?(Symbol)
|
11
|
+
|
12
|
+
region = find_wildcard_base(r)
|
13
|
+
|
14
|
+
(region == :any ||
|
15
|
+
regions_repo.exists?(region) ||
|
16
|
+
region_in_static_definitions?(region))
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :regions_repo
|
22
|
+
|
23
|
+
# Ex: :gb_ transformed to :gb
|
24
|
+
def find_wildcard_base(region)
|
25
|
+
r = region.to_s
|
26
|
+
|
27
|
+
if r =~ /_$/
|
28
|
+
base = r.split('_').first
|
29
|
+
else
|
30
|
+
base = r
|
31
|
+
end
|
32
|
+
|
33
|
+
base.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def region_in_static_definitions?(region)
|
37
|
+
static_regions_definition = "#{DEFINITIONS_PATH}/REGIONS.rb"
|
38
|
+
require static_regions_definition
|
39
|
+
|
40
|
+
Holidays::REGIONS.include?(region)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'holidays/definition/context/generator'
|
2
|
+
require 'holidays/definition/context/merger'
|
3
|
+
require 'holidays/definition/repository/holidays_by_month'
|
4
|
+
require 'holidays/definition/repository/regions'
|
5
|
+
require 'holidays/definition/repository/cache'
|
6
|
+
require 'holidays/definition/repository/proc_cache'
|
7
|
+
require 'holidays/definition/validator/region'
|
8
|
+
|
9
|
+
module Holidays
|
10
|
+
module DefinitionFactory
|
11
|
+
class << self
|
12
|
+
def file_parser
|
13
|
+
Definition::Context::Generator.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def source_generator
|
17
|
+
Definition::Context::Generator.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def merger
|
21
|
+
Definition::Context::Merger.new(
|
22
|
+
holidays_by_month_repository,
|
23
|
+
regions_repository
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def region_validator
|
28
|
+
Definition::Validator::Region.new(
|
29
|
+
regions_repository
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def holidays_by_month_repository
|
34
|
+
@holidays_repo ||= Definition::Repository::HolidaysByMonth.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def regions_repository
|
38
|
+
@regions_repo ||= Definition::Repository::Regions.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache_repository
|
42
|
+
@cache_repo ||= Definition::Repository::Cache.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def proc_cache_repository
|
46
|
+
@proc_cache_repo ||= Definition::Repository::ProcCache.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Holidays
|
2
|
+
module Option
|
3
|
+
module Context
|
4
|
+
class ParseOptions
|
5
|
+
def initialize(regions_repo, region_validator)
|
6
|
+
@regions_repo = regions_repo
|
7
|
+
@region_validator = region_validator
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns [(arr)regions, (bool)observed, (bool)informal]
|
11
|
+
def call(*options)
|
12
|
+
options.flatten!
|
13
|
+
|
14
|
+
#TODO This is garbage. These two deletes MUST come before the
|
15
|
+
# parse_regions call, otherwise it thinks that :observed and :informal
|
16
|
+
# are regions to parse. We should be splitting these things out.
|
17
|
+
observed = options.delete(:observed) ? true : false
|
18
|
+
informal = options.delete(:informal) ? true : false
|
19
|
+
regions = parse_regions!(options)
|
20
|
+
|
21
|
+
return regions, observed, informal
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :regions_repo, :region_validator
|
27
|
+
|
28
|
+
# Check regions against list of supported regions and return an array of
|
29
|
+
# symbols.
|
30
|
+
#
|
31
|
+
# If a wildcard region is found (e.g. <tt>:ca_</tt>) it is expanded into all
|
32
|
+
# of its available sub regions.
|
33
|
+
def parse_regions!(regions)
|
34
|
+
regions = [regions] unless regions.kind_of?(Array)
|
35
|
+
return [:any] if regions.empty?
|
36
|
+
|
37
|
+
regions = regions.collect { |r| r.to_sym }
|
38
|
+
|
39
|
+
validate!(regions)
|
40
|
+
|
41
|
+
# Found sub region wild-card
|
42
|
+
regions.delete_if do |r|
|
43
|
+
if r.to_s =~ /_$/
|
44
|
+
load_containing_region(r.to_s)
|
45
|
+
regions << regions_repo.search(r.to_s)
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
regions.flatten!
|
51
|
+
|
52
|
+
require "#{DEFINITIONS_PATH}/north_america" if regions.include?(:us) # special case for north_america/US cross-linking
|
53
|
+
|
54
|
+
regions.each do |region|
|
55
|
+
unless region == :any or regions_repo.exists?(region)
|
56
|
+
region_definition = "#{DEFINITIONS_PATH}/#{region.to_s}"
|
57
|
+
begin
|
58
|
+
require region_definition #FIXME This is unacceptable, we can't be loading external files while parsing options
|
59
|
+
rescue LoadError => e
|
60
|
+
# This could be a sub region that does not have any holiday
|
61
|
+
# definitions of its own; try to load the containing region instead.
|
62
|
+
if region.to_s =~ /_/
|
63
|
+
load_containing_region(region.to_s)
|
64
|
+
else
|
65
|
+
raise UnknownRegionError, "Could not load #{region_definition}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
regions
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate!(regions)
|
75
|
+
regions.each do |r|
|
76
|
+
raise UnknownRegionError unless region_validator.valid?(r)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Derive the containing region from a sub region wild-card or a sub region
|
81
|
+
# and load its definition. (Common code factored out from parse_regions)
|
82
|
+
def load_containing_region(sub_reg)
|
83
|
+
prefix = sub_reg.split('_').first
|
84
|
+
region_definition = "#{DEFINITIONS_PATH}/#{prefix}"
|
85
|
+
unless regions_repo.exists?(prefix.to_sym)
|
86
|
+
begin
|
87
|
+
require region_definition #FIXME This is not acceptable, we can't be loading external things while parsing options.
|
88
|
+
rescue LoadError
|
89
|
+
raise UnknownRegionError, "Could not load #{region_definition}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'holidays/option/context/parse_options'
|
2
|
+
|
3
|
+
module Holidays
|
4
|
+
module OptionFactory
|
5
|
+
class << self
|
6
|
+
def parse_options
|
7
|
+
Option::Context::ParseOptions.new(
|
8
|
+
DefinitionFactory.regions_repository,
|
9
|
+
DefinitionFactory.region_validator,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Holidays
|
2
|
+
module UseCase
|
3
|
+
module Context
|
4
|
+
class Between
|
5
|
+
def initialize(holidays_by_month_repo, day_of_month_calculator, proc_cache_repo)
|
6
|
+
@holidays_by_month_repo = holidays_by_month_repo
|
7
|
+
@day_of_month_calculator = day_of_month_calculator
|
8
|
+
@proc_cache_repo = proc_cache_repo
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(start_date, end_date, dates_driver, regions, observed, informal)
|
12
|
+
validate!(start_date, end_date, dates_driver, regions)
|
13
|
+
|
14
|
+
holidays = []
|
15
|
+
|
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
|
+
|
20
|
+
hbm.each do |h|
|
21
|
+
next unless in_region?(regions, h[:regions])
|
22
|
+
|
23
|
+
# Skip informal holidays unless they have been requested
|
24
|
+
next if h[:type] == :informal and not informal
|
25
|
+
|
26
|
+
if h[:function]
|
27
|
+
# Holiday definition requires a calculation
|
28
|
+
result = call_proc(h[:function], year)
|
29
|
+
|
30
|
+
# Procs may return either Date or an integer representing mday
|
31
|
+
if result.kind_of?(Date)
|
32
|
+
month = result.month
|
33
|
+
mday = result.mday
|
34
|
+
else
|
35
|
+
mday = result
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# Calculate the mday
|
39
|
+
mday = h[:mday] || day_of_month_calculator.call(year, month, h[:week], h[:wday])
|
40
|
+
end
|
41
|
+
|
42
|
+
# Silently skip bad mdays
|
43
|
+
begin
|
44
|
+
date = Date.civil(year, month, mday)
|
45
|
+
rescue; next; end
|
46
|
+
|
47
|
+
# If the :observed option is set, calculate the date when the holiday
|
48
|
+
# is observed.
|
49
|
+
if observed and h[:observed]
|
50
|
+
date = call_proc(h[:observed], date)
|
51
|
+
end
|
52
|
+
|
53
|
+
if date.between?(start_date, end_date)
|
54
|
+
holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
holidays.sort{|a, b| a[:date] <=> b[:date] }
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
attr_reader :holidays_by_month_repo, :day_of_month_calculator, :proc_cache_repo
|
66
|
+
|
67
|
+
def validate!(start_date, end_date, dates_driver, regions)
|
68
|
+
raise ArgumentError unless start_date
|
69
|
+
raise ArgumentError unless end_date
|
70
|
+
|
71
|
+
raise ArgumentError if dates_driver.nil? || dates_driver.empty?
|
72
|
+
|
73
|
+
dates_driver.each do |year, months|
|
74
|
+
raise ArgumentError if months.nil? || months.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
raise ArgumentError if regions.nil? || regions.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def call_proc(function, year)
|
81
|
+
proc_cache_repo.lookup(function, year)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check sub regions.
|
85
|
+
#
|
86
|
+
# When request :any, all holidays should be returned.
|
87
|
+
# When requesting :ca_bc, holidays in :ca or :ca_bc should be returned.
|
88
|
+
# When requesting :ca, holidays in :ca but not its subregions should be returned.
|
89
|
+
def in_region?(requested, available) # :nodoc:
|
90
|
+
return true if requested.include?(:any)
|
91
|
+
|
92
|
+
# When an underscore is encountered, derive the parent regions
|
93
|
+
# symbol and include both in the requested array.
|
94
|
+
requested = requested.collect do |r|
|
95
|
+
r.to_s =~ /_/ ? [r, r.to_s.gsub(/_[\w]*$/, '').to_sym] : r
|
96
|
+
end
|
97
|
+
|
98
|
+
requested = requested.flatten.uniq
|
99
|
+
|
100
|
+
available.any? { |avail| requested.include?(avail) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|