Skip to content

Commit

Permalink
Merge pull request holidays#191 from ttwo32/master
Browse files Browse the repository at this point in the history
holidays#163 add feature next_holiday
  • Loading branch information
ppeble committed May 11, 2016
2 parents f85ff01 + 1b9c4db commit 9f05164
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 130 deletions.
2 changes: 1 addition & 1 deletion lib/generated_definitions/REGIONS.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# encoding: utf-8
module Holidays
REGIONS = [:ar, :at, :au, :au_nsw, :au_vic, :au_qld, :au_nt, :au_act, :au_sa, :au_tas_south, :au_wa, :au_tas, :au_qld_cairns, :au_qld_brisbane, :au_tas_north, :au_vic_melbourne, :be_fr, :be_nl, :br, :bg_en, :bg_bg, :ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_mb, :ca_ns, :ca_pe, :ca_bc, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :us, :ch_zh, :ch_be, :ch_lu, :ch_ur, :ch_sz, :ch_ow, :ch_nw, :ch_gl, :ch_zg, :ch_fr, :ch_so, :ch_bs, :ch_bl, :ch_sh, :ch_ar, :ch_ai, :ch_sg, :ch_gr, :ch_ag, :ch_tg, :ch_ti, :ch_vd, :ch_ne, :ch_ge, :ch_ju, :ch_vs, :ch, :cl, :cr, :cz, :dk, :de, :de_bw, :de_by, :de_he, :de_nw, :de_rp, :de_sl, :de_sn_aux, :de_th_aux, :de_sn, :de_st, :de_by_aux, :de_bb, :de_mv, :de_th, :ecb_target, :el, :es_pv, :es_na, :es_an, :es_ib, :es_cm, :es_mu, :es_m, :es_ar, :es_cl, :es_cn, :es_lo, :es_ga, :es_ce, :es_o, :es_ex, :es, :es_ct, :es_v, :es_vc, :federal_reserve, :fedex, :fi, :fr, :gb, :gb_eng, :gb_wls, :gb_eaw, :gb_nir, :gb_sct, :gb_con, :je, :gb_jsy, :gg, :gb_gsy, :im, :gb_iom, :hr, :hu, :ie, :is, :it, :li, :lt, :ma, :mx, :mx_pue, :us, :ca, :nerc, :nl, :no, :nyse, :nz, :nz_sl, :nz_we, :nz_ak, :nz_nl, :nz_ne, :nz_ot, :nz_ta, :nz_sc, :nz_hb, :nz_mb, :nz_ca, :nz_ch, :nz_wl, :ph, :pl, :pt, :ro, :se, :us, :us_dc, :us_ca, :ca, :united_nations, :ups, :za, :ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_mb, :ca_ns, :ca_pe, :ca_bc, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :mx, :mx_pue, :us, :us_dc, :us_ca, :dk, :is, :no, :se, :fi, :at, :be_fr, :be_nl, :ch_zh, :ch_be, :ch_lu, :ch_ur, :ch_sz, :ch_ow, :ch_nw, :ch_gl, :ch_zg, :ch_fr, :ch_so, :ch_bs, :ch_bl, :ch_sh, :ch_ar, :ch_ai, :ch_sg, :ch_gr, :ch_ag, :ch_tg, :ch_ti, :ch_vd, :ch_ne, :ch_ge, :ch_ju, :ch_vs, :ch, :cz, :dk, :de, :de_bw, :de_by, :de_he, :de_nw, :de_rp, :de_sl, :de_sn_aux, :de_th_aux, :de_sn, :de_st, :de_by_aux, :de_bb, :de_mv, :de_th, :el, :es_pv, :es_na, :es_an, :es_ib, :es_cm, :es_mu, :es_m, :es_ar, :es_cl, :es_cn, :es_lo, :es_ga, :es_ce, :es_o, :es_ex, :es, :es_ct, :es_v, :es_vc, :fr, :gb, :gb_eng, :gb_wls, :gb_eaw, :gb_nir, :gb_sct, :gb_con, :je, :gb_jsy, :gg, :gb_gsy, :im, :gb_iom, :hr, :hu, :ie, :is, :it, :li, :lt, :nl, :no, :pl, :pt, :ro, :sk, :si, :bg_en, :bg_bg, :jp, :ve, :vi, :sk, :si, :sg]
REGIONS = [:ar, :at, :au, :au_nsw, :au_vic, :au_qld, :au_nt, :au_act, :au_sa, :au_tas_south, :au_wa, :au_tas, :au_qld_cairns, :au_qld_brisbane, :au_tas_north, :au_vic_melbourne, :be_fr, :be_nl, :br, :bg_en, :bg_bg, :ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_bc, :ca_mb, :ca_ns, :ca_pe, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :us, :ch_zh, :ch_be, :ch_lu, :ch_ur, :ch_sz, :ch_ow, :ch_nw, :ch_gl, :ch_zg, :ch_fr, :ch_so, :ch_bs, :ch_bl, :ch_sh, :ch_ar, :ch_ai, :ch_sg, :ch_gr, :ch_ag, :ch_tg, :ch_ti, :ch_vd, :ch_ne, :ch_ge, :ch_ju, :ch_vs, :ch, :cl, :cr, :cz, :dk, :de, :de_bw, :de_by, :de_he, :de_nw, :de_rp, :de_sl, :de_sn_aux, :de_th_aux, :de_sn, :de_st, :de_by_aux, :de_bb, :de_mv, :de_th, :ecb_target, :el, :es_pv, :es_na, :es_an, :es_ib, :es_cm, :es_mu, :es_m, :es_ar, :es_cl, :es_cn, :es_lo, :es_ga, :es_ce, :es_o, :es_ex, :es, :es_ct, :es_v, :es_vc, :federal_reserve, :fedex, :fi, :fr, :gb, :gb_eng, :gb_wls, :gb_eaw, :gb_nir, :gb_sct, :gb_con, :je, :gb_jsy, :gg, :gb_gsy, :im, :gb_iom, :hr, :hu, :ie, :is, :it, :li, :lt, :ma, :mx, :mx_pue, :us, :ca, :nerc, :nl, :no, :nyse, :nz, :nz_sl, :nz_we, :nz_ak, :nz_nl, :nz_ne, :nz_ot, :nz_ta, :nz_sc, :nz_hb, :nz_mb, :nz_ca, :nz_ch, :nz_wl, :ph, :pl, :pt, :ro, :se, :us, :us_dc, :us_ca, :ca, :united_nations, :ups, :za, :ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_bc, :ca_mb, :ca_ns, :ca_pe, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :mx, :mx_pue, :us, :us_dc, :us_ca, :dk, :is, :no, :se, :fi, :at, :be_fr, :be_nl, :ch_zh, :ch_be, :ch_lu, :ch_ur, :ch_sz, :ch_ow, :ch_nw, :ch_gl, :ch_zg, :ch_fr, :ch_so, :ch_bs, :ch_bl, :ch_sh, :ch_ar, :ch_ai, :ch_sg, :ch_gr, :ch_ag, :ch_tg, :ch_ti, :ch_vd, :ch_ne, :ch_ge, :ch_ju, :ch_vs, :ch, :cz, :dk, :de, :de_bw, :de_by, :de_he, :de_nw, :de_rp, :de_sl, :de_sn_aux, :de_th_aux, :de_sn, :de_st, :de_by_aux, :de_bb, :de_mv, :de_th, :el, :es_pv, :es_na, :es_an, :es_ib, :es_cm, :es_mu, :es_m, :es_ar, :es_cl, :es_cn, :es_lo, :es_ga, :es_ce, :es_o, :es_ex, :es, :es_ct, :es_v, :es_vc, :fr, :gb, :gb_eng, :gb_wls, :gb_eaw, :gb_nir, :gb_sct, :gb_con, :je, :gb_jsy, :gg, :gb_gsy, :im, :gb_iom, :hr, :hu, :ie, :is, :it, :li, :lt, :nl, :no, :pl, :pt, :ro, :sk, :si, :bg_en, :bg_bg, :jp, :ve, :vi, :sk, :si, :sg]
end
4 changes: 2 additions & 2 deletions lib/generated_definitions/ca.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Holidays
# All the definitions are available at https://github.com/holidays/holidays
module CA # :nodoc:
def self.defined_regions
[:ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_mb, :ca_ns, :ca_pe, :ca_bc, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :us]
[:ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_bc, :ca_mb, :ca_ns, :ca_pe, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :us]
end

def self.holidays_by_month
Expand All @@ -26,10 +26,10 @@ def self.holidays_by_month
2 => [{:wday => 1, :week => 3, :year_ranges => [{:after => 1990}],:name => "Family Day", :regions => [:ca_ab]},
{:wday => 1, :week => 3, :year_ranges => [{:after => 2007}],:name => "Family Day", :regions => [:ca_sk]},
{:wday => 1, :week => 3, :year_ranges => [{:after => 2008}],:name => "Family Day", :regions => [:ca_on]},
{:wday => 1, :week => 2, :year_ranges => [{:after => 2013}],:name => "Family Day", :regions => [:ca_bc]},
{:wday => 1, :week => 3, :name => "Louis Riel Day", :regions => [:ca_mb]},
{:wday => 1, :week => 3, :year_ranges => [{:after => 2015}],:name => "Nova Scotia Heritage Day", :regions => [:ca_ns]},
{:wday => 1, :week => 3, :name => "Islander Day", :regions => [:ca_pe]},
{:wday => 1, :week => 2, :year_ranges => [{:after => 2013}],:name => "BC Family Day", :regions => [:ca_bc]},
{:mday => 2, :type => :informal, :name => "Groundhog Day", :regions => [:us, :ca]},
{:mday => 14, :type => :informal, :name => "Valentine's Day", :regions => [:us, :ca]}],
3 => [{:mday => 23, :name => "St. George's Day", :regions => [:ca_nf]},
Expand Down
4 changes: 2 additions & 2 deletions lib/generated_definitions/north_america.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Holidays
# All the definitions are available at https://github.com/holidays/holidays
module NORTH_AMERICA # :nodoc:
def self.defined_regions
[:ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_mb, :ca_ns, :ca_pe, :ca_bc, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :mx, :mx_pue, :us, :us_dc, :us_ca]
[:ca, :ca_qc, :ca_ab, :ca_sk, :ca_on, :ca_bc, :ca_mb, :ca_ns, :ca_pe, :ca_nf, :ca_nt, :ca_nu, :ca_nb, :ca_yk, :mx, :mx_pue, :us, :us_dc, :us_ca]
end

def self.holidays_by_month
Expand All @@ -33,10 +33,10 @@ def self.holidays_by_month
2 => [{:wday => 1, :week => 3, :year_ranges => [{:after => 1990}],:name => "Family Day", :regions => [:ca_ab]},
{:wday => 1, :week => 3, :year_ranges => [{:after => 2007}],:name => "Family Day", :regions => [:ca_sk]},
{:wday => 1, :week => 3, :year_ranges => [{:after => 2008}],:name => "Family Day", :regions => [:ca_on]},
{:wday => 1, :week => 2, :year_ranges => [{:after => 2013}],:name => "Family Day", :regions => [:ca_bc]},
{:wday => 1, :week => 3, :name => "Louis Riel Day", :regions => [:ca_mb]},
{:wday => 1, :week => 3, :year_ranges => [{:after => 2015}],:name => "Nova Scotia Heritage Day", :regions => [:ca_ns]},
{:wday => 1, :week => 3, :name => "Islander Day", :regions => [:ca_pe]},
{:wday => 1, :week => 2, :year_ranges => [{:after => 2013}],:name => "BC Family Day", :regions => [:ca_bc]},
{:wday => 1, :week => 1, :name => "Día de la Constitución", :regions => [:mx]},
{:wday => 1, :week => 3, :name => "Presidents' Day", :regions => [:us]},
{:mday => 2, :type => :informal, :name => "Groundhog Day", :regions => [:us, :ca]},
Expand Down
34 changes: 34 additions & 0 deletions lib/holidays.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,40 @@ def between(start_date, end_date, *options)
UseCaseFactory.between.call(start_date, end_date, date_driver_hash, regions, observed, informal)
end

# Get next holidays occuring from date, inclusively.
#
# Returns an array of hashes or nil.
#
# Incoming arguments are below:
# [<tt>holidays_count</tt>] Ruby Numeric object. This is the number of holidays to return
# [<tt>options</tt>] One or more region symbols, <tt>:informal</tt> and/or <tt>:observed</tt>.
# [<tt>from_date</tt>] Ruby Date object. This is an optional param, defaulted today.
#
# ==== Example
# Date.today
# => Tue, 23 Feb 2016
#
# regions = [:us, :informal]
#
# Holidays.next_holidays(3, regions)
# => [{:name => "St. Patrick's Day",...},
# {:name => "Good Friday",...},
# {:name => "Easter Sunday",...}]
def next_holidays(holidays_count, options, from_date = Date.today)
raise ArgumentError unless holidays_count
raise ArgumentError if options.empty?
raise ArgumentError unless options.is_a?(Array)

# remove the timezone
from_date = from_date.new_offset(0) + from_date.offset if from_date.respond_to?(:new_offset)

from_date = get_date(from_date)
regions, observed, informal = OptionFactory.parse_options.call(options)
date_driver_hash = UseCaseFactory.dates_driver_builder.build(from_date)

UseCaseFactory.next_holiday.call(holidays_count, from_date, date_driver_hash, regions, observed, informal)
end

# Allows a developer to explicitly calculate and cache holidays within a given period
def cache_between(start_date, end_date, *options)
start_date, end_date = get_date(start_date), get_date(end_date)
Expand Down
124 changes: 8 additions & 116 deletions lib/holidays/use_case/context/between.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module Holidays
module UseCase
module Context
class Between
include ContextCommon

def initialize(holidays_by_month_repo, day_of_month_calculator, custom_methods_repo, proc_result_cache_repo)
@holidays_by_month_repo = holidays_by_month_repo
@day_of_month_calculator = day_of_month_calculator
Expand All @@ -13,101 +15,17 @@ def call(start_date, end_date, dates_driver, regions, observed, informal)
validate!(start_date, end_date, dates_driver, regions)

holidays = []

dates_driver.each do |year, months|
months.each do |month|
next unless hbm = holidays_by_month_repo.find_by_month(month)
hbm.each do |h|
next unless in_region?(regions, h[:regions])
next if h[:type] == :informal && !informal

# range check feature.
if h[:year_ranges]
valid_range_year = false
h[:year_ranges].each do |year_range|
next unless year_range.is_a?(Hash) && year_range.length == 1
next unless year_range.select{
|operator,year|[:before,"before",:after,"after",:limited,"limited",:between,"between"].include?(operator)}.count > 0
case year_range.keys.first
when :before,"before"
valid_range_year = true if year <= year_range[year_range.keys.first]
when :after,"after"
valid_range_year = true if year >= year_range[year_range.keys.first]
when :limited,"limited"
valid_range_year = true if year_range[year_range.keys.first].include?(year)
when :between,"between"
year_range[year_range.keys.first] = Range.new(*year_range[year_range.keys.first].split("..").map(&:to_i)) if year_range[year_range.keys.first].is_a?(String)
valid_range_year = true if year_range[year_range.keys.first].cover?(year)
end
break if valid_range_year
end
next unless valid_range_year
end

#FIXME I don't like this entire if/else. If it's a function, do something, else do some
# weird mday logic? Bollocks. I think this should be a refactor target.
if h[:function]
function_arguments = []

#FIXME This is a refactor target. We should also allow 'date'. Right now these are the only
# three things that we allow in. I think having a more testable, robust approach here is vital.
if h[:function_arguments].include?(:year)
function_arguments << year
end

if h[:function_arguments].include?(:month)
function_arguments << month
end

if h[:function_arguments].include?(:day)
function_arguments << h[:mday]
end

result = call_proc(h[:function], *function_arguments)

#FIXME This is a dangerous assumption. We should raise an error or something
# if these procs return something unexpected.
#
# Procs may return either Date or an integer representing mday
if result.kind_of?(Date)
if h[:function_modifier]
result = result + h[:function_modifier] # NOTE: This could be a positive OR negative number.
end

month = result.month
mday = result.mday
else
mday = result
end
else
mday = h[:mday] || day_of_month_calculator.call(year, month, h[:week], h[:wday])
end

# Silently skip bad mdays
begin
date = Date.civil(year, month, mday)
rescue; next; end

#FIXME We should be checking the function arguments and passing in what is specified.
# Right now all 'observed' functions require 'date' but that is by convention. Nothing
# is requiring that. We should be more explicit.
if observed && h[:observed]
date = call_proc(h[:observed], date)
end

if date.between?(start_date, end_date)
holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
end
end
end
end

holidays = make_date_array(dates_driver, regions, observed, informal)
holidays = holidays.select{|holiday|holiday[:date].between?(start_date, end_date)}
holidays.sort{|a, b| a[:date] <=> b[:date] }
end

private

attr_reader :holidays_by_month_repo, :day_of_month_calculator, :custom_methods_repo, :proc_result_cache_repo
attr_reader :holidays_by_month_repo,
:day_of_month_calculator,
:custom_methods_repo,
:proc_result_cache_repo

def validate!(start_date, end_date, dates_driver, regions)
raise ArgumentError unless start_date
Expand All @@ -121,32 +39,6 @@ def validate!(start_date, end_date, dates_driver, regions)

raise ArgumentError if regions.nil? || regions.empty?
end

def call_proc(function_id, *arguments)
function = custom_methods_repo.find(function_id)
raise Holidays::FunctionNotFound.new("Unable to find function with id '#{function_id}'") if function.nil?

proc_result_cache_repo.lookup(function, *arguments)
end

# Check sub regions.
#
# When request :any, all holidays should be returned.
# When requesting :ca_bc, holidays in :ca or :ca_bc should be returned.
# When requesting :ca, holidays in :ca but not its subregions should be returned.
def in_region?(requested, available) # :nodoc:
return true if requested.include?(:any)

# When an underscore is encountered, derive the parent regions
# symbol and include both in the requested array.
requested = requested.collect do |r|
r.to_s =~ /_/ ? [r, r.to_s.gsub(/_[\w]*$/, '').to_sym] : r
end

requested = requested.flatten.uniq

available.any? { |avail| requested.include?(avail) }
end
end
end
end
Expand Down
Loading

0 comments on commit 9f05164

Please sign in to comment.