holidays 11.6.0 → 11.8.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/README.md +11 -8
  4. data/lib/generated_definitions/REGIONS.rb +3 -3
  5. data/lib/generated_definitions/ar.rb +1 -1
  6. data/lib/generated_definitions/at.rb +1 -1
  7. data/lib/generated_definitions/au.rb +2 -2
  8. data/lib/generated_definitions/be_fr.rb +1 -1
  9. data/lib/generated_definitions/be_nl.rb +1 -1
  10. data/lib/generated_definitions/bg.rb +1 -1
  11. data/lib/generated_definitions/br.rb +1 -1
  12. data/lib/generated_definitions/by.rb +1 -1
  13. data/lib/generated_definitions/ca.rb +1 -1
  14. data/lib/generated_definitions/ch.rb +1 -1
  15. data/lib/generated_definitions/cl.rb +3 -2
  16. data/lib/generated_definitions/cn.rb +1 -1
  17. data/lib/generated_definitions/co.rb +1 -1
  18. data/lib/generated_definitions/cr.rb +1 -1
  19. data/lib/generated_definitions/cy.rb +1 -1
  20. data/lib/generated_definitions/cz.rb +1 -1
  21. data/lib/generated_definitions/de.rb +1 -1
  22. data/lib/generated_definitions/dk.rb +1 -1
  23. data/lib/generated_definitions/ecbtarget.rb +1 -1
  24. data/lib/generated_definitions/ee.rb +1 -1
  25. data/lib/generated_definitions/es.rb +1 -1
  26. data/lib/generated_definitions/europe.rb +1 -1
  27. data/lib/generated_definitions/federalreserve.rb +1 -1
  28. data/lib/generated_definitions/federalreservebanks.rb +1 -1
  29. data/lib/generated_definitions/fedex.rb +1 -1
  30. data/lib/generated_definitions/fi.rb +1 -1
  31. data/lib/generated_definitions/fr.rb +1 -1
  32. data/lib/generated_definitions/gb.rb +1 -1
  33. data/lib/generated_definitions/ge.rb +1 -1
  34. data/lib/generated_definitions/gr.rb +1 -1
  35. data/lib/generated_definitions/hk.rb +1 -1
  36. data/lib/generated_definitions/hr.rb +1 -1
  37. data/lib/generated_definitions/hu.rb +1 -1
  38. data/lib/generated_definitions/ie.rb +1 -1
  39. data/lib/generated_definitions/il.rb +1 -1
  40. data/lib/generated_definitions/in.rb +1 -1
  41. data/lib/generated_definitions/is.rb +1 -1
  42. data/lib/generated_definitions/it.rb +1 -1
  43. data/lib/generated_definitions/jp.rb +1 -1
  44. data/lib/generated_definitions/ke.rb +1 -1
  45. data/lib/generated_definitions/kr.rb +1 -1
  46. data/lib/generated_definitions/kz.rb +1 -1
  47. data/lib/generated_definitions/li.rb +1 -1
  48. data/lib/generated_definitions/lt.rb +1 -1
  49. data/lib/generated_definitions/lu.rb +1 -1
  50. data/lib/generated_definitions/lv.rb +1 -1
  51. data/lib/generated_definitions/ma.rb +3 -1
  52. data/lib/generated_definitions/mc.rb +1 -1
  53. data/lib/generated_definitions/mt_en.rb +1 -1
  54. data/lib/generated_definitions/mt_mt.rb +1 -1
  55. data/lib/generated_definitions/mx.rb +1 -1
  56. data/lib/generated_definitions/my.rb +3 -1
  57. data/lib/generated_definitions/nerc.rb +1 -1
  58. data/lib/generated_definitions/ng.rb +1 -1
  59. data/lib/generated_definitions/nl.rb +1 -1
  60. data/lib/generated_definitions/no.rb +1 -1
  61. data/lib/generated_definitions/northamerica.rb +4 -3
  62. data/lib/generated_definitions/nyse.rb +1 -1
  63. data/lib/generated_definitions/nz.rb +1 -1
  64. data/lib/generated_definitions/pe.rb +1 -1
  65. data/lib/generated_definitions/ph.rb +1 -1
  66. data/lib/generated_definitions/pl.rb +1 -1
  67. data/lib/generated_definitions/pt.rb +1 -1
  68. data/lib/generated_definitions/ro.rb +1 -1
  69. data/lib/generated_definitions/rs_cyrl.rb +1 -1
  70. data/lib/generated_definitions/rs_la.rb +1 -1
  71. data/lib/generated_definitions/ru.rb +1 -1
  72. data/lib/generated_definitions/scandinavia.rb +1 -1
  73. data/lib/generated_definitions/se.rb +1 -1
  74. data/lib/generated_definitions/sg.rb +1 -1
  75. data/lib/generated_definitions/si.rb +1 -1
  76. data/lib/generated_definitions/sk.rb +1 -1
  77. data/lib/generated_definitions/southamerica.rb +2 -1
  78. data/lib/generated_definitions/th.rb +1 -1
  79. data/lib/generated_definitions/tn.rb +1 -1
  80. data/lib/generated_definitions/tr.rb +4 -1
  81. data/lib/generated_definitions/tsx.rb +1 -1
  82. data/lib/generated_definitions/ua.rb +1 -1
  83. data/lib/generated_definitions/unitednations.rb +1 -1
  84. data/lib/generated_definitions/ups.rb +1 -1
  85. data/lib/generated_definitions/us.rb +4 -3
  86. data/lib/generated_definitions/ve.rb +1 -1
  87. data/lib/generated_definitions/vn.rb +1 -1
  88. data/lib/generated_definitions/za.rb +1 -1
  89. data/lib/holidays/core_extensions/date.rb +11 -3
  90. data/lib/holidays/date_calculator/hijri_date.rb +10 -7
  91. data/lib/holidays/date_calculator/lunar_date.rb +269 -83
  92. data/lib/holidays/definition/context/generator.rb +27 -2
  93. data/lib/holidays/definition/custom_methods/cl.rb +8 -0
  94. data/lib/holidays/definition/custom_methods/cn.rb +7 -1
  95. data/lib/holidays/definition/custom_methods/ma.rb +48 -0
  96. data/lib/holidays/definition/custom_methods/my.rb +55 -0
  97. data/lib/holidays/definition/custom_methods/registry.rb +8 -0
  98. data/lib/holidays/definition/custom_methods/tr.rb +20 -2
  99. data/lib/holidays/definition/generator/module.rb +1 -1
  100. data/lib/holidays/definition/repository/holidays_by_month.rb +2 -1
  101. data/lib/holidays/errors.rb +5 -0
  102. data/lib/holidays/finder/context/next_holiday.rb +27 -17
  103. data/lib/holidays/finder/context/search.rb +67 -23
  104. data/lib/holidays/finder/context/year_holiday.rb +6 -8
  105. data/lib/holidays/finder/rules/in_region.rb +19 -14
  106. data/lib/holidays/version.rb +1 -1
  107. metadata +3 -1
@@ -0,0 +1,48 @@
1
+ require 'date'
2
+
3
+ module Holidays
4
+ module Definition
5
+ module CustomMethods
6
+ # ma custom holiday calculations.
7
+ #
8
+ # Eid al-Fitr is 1 Shawwal and Eid al-Adha is 10 Dhul-Hijjah of the
9
+ # Hijri calendar. Both are derived from the arithmetic Islamic calendar
10
+ # (Holidays::DateCalculator::HijriDate). The civil holidays are
11
+ # proclaimed by Morocco's Ministry of Habous and Islamic Affairs and in
12
+ # some years land a day off from the arithmetic result; MINISTRY_OVERRIDES
13
+ # pins those years to the proclaimed date. Years with no entry fall back
14
+ # to the calculation. See holidays#392.
15
+ module MA
16
+ SHAWWAL = 10
17
+ DHU_AL_HIJJAH = 12
18
+
19
+ # Proclaimed first days that differ from the arithmetic calendar,
20
+ # keyed by [feast, gregorian_year]. Sourced from the Ministry of
21
+ # Habous and Islamic Affairs communiques reported via maroc.ma,
22
+ # Hespress, and Morocco World News.
23
+ MINISTRY_OVERRIDES = {
24
+ [:eid_al_fitr, 2022] => Date.new(2022, 5, 2),
25
+
26
+ [:eid_al_adha, 2021] => Date.new(2021, 7, 21),
27
+ }.freeze
28
+
29
+ class << self
30
+ def eid_al_fitr(year)
31
+ feast(:eid_al_fitr, year, SHAWWAL, 1)
32
+ end
33
+
34
+ def eid_al_adha(year)
35
+ feast(:eid_al_adha, year, DHU_AL_HIJJAH, 10)
36
+ end
37
+
38
+ private
39
+
40
+ def feast(name, year, hijri_month, hijri_day)
41
+ MINISTRY_OVERRIDES[[name, year]] ||
42
+ Holidays::Factory::DateCalculator.hijri_date.gregorian_year_occurrence(year, hijri_month, hijri_day)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ require 'date'
2
+
3
+ module Holidays
4
+ module Definition
5
+ module CustomMethods
6
+ # my custom holiday calculations.
7
+ #
8
+ # Hari Raya Puasa (Eid al-Fitr) is 1 Shawwal and Hari Raya Haji (Eid
9
+ # al-Adha) is 10 Dhul-Hijjah of the Hijri calendar. Both are derived
10
+ # from the arithmetic Islamic calendar (Holidays::DateCalculator::HijriDate).
11
+ # The civil holidays are fixed by the Yang di-Pertuan Agong (Conference
12
+ # of Rulers) on JAKIM's moon-sighting advice and in some years have
13
+ # landed a day before the arithmetic result; GAZETTE_OVERRIDES pins
14
+ # those years to the gazetted date. Years with no entry fall back to
15
+ # the calculation. See holidays#392.
16
+ module MY
17
+ SHAWWAL = 10
18
+ DHU_AL_HIJJAH = 12
19
+
20
+ # Gazetted first days that differ from the arithmetic calendar,
21
+ # keyed by [feast, gregorian_year]. Sourced from the official
22
+ # Malaysian public holiday gazette, cross-checked against
23
+ # definitions/sg.yaml (agrees in every overlapping year 2017-2026).
24
+ GAZETTE_OVERRIDES = {
25
+ [:hari_raya_puasa, 2014] => Date.new(2014, 7, 28),
26
+ [:hari_raya_puasa, 2015] => Date.new(2015, 7, 17),
27
+ [:hari_raya_puasa, 2016] => Date.new(2016, 7, 6),
28
+ [:hari_raya_puasa, 2017] => Date.new(2017, 6, 25),
29
+ [:hari_raya_puasa, 2026] => Date.new(2026, 3, 21),
30
+
31
+ [:hari_raya_haji, 2016] => Date.new(2016, 9, 12),
32
+ [:hari_raya_haji, 2017] => Date.new(2017, 9, 1),
33
+ [:hari_raya_haji, 2019] => Date.new(2019, 8, 11),
34
+ }.freeze
35
+
36
+ class << self
37
+ def hari_raya_puasa(year)
38
+ feast(:hari_raya_puasa, year, SHAWWAL, 1)
39
+ end
40
+
41
+ def hari_raya_haji(year)
42
+ feast(:hari_raya_haji, year, DHU_AL_HIJJAH, 10)
43
+ end
44
+
45
+ private
46
+
47
+ def feast(name, year, hijri_month, hijri_day)
48
+ GAZETTE_OVERRIDES[[name, year]] ||
49
+ Holidays::Factory::DateCalculator.hijri_date.gregorian_year_occurrence(year, hijri_month, hijri_day)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -13,6 +13,8 @@ require 'holidays/definition/custom_methods/is'
13
13
  require 'holidays/definition/custom_methods/jp'
14
14
  require 'holidays/definition/custom_methods/kr'
15
15
  require 'holidays/definition/custom_methods/lv'
16
+ require 'holidays/definition/custom_methods/ma'
17
+ require 'holidays/definition/custom_methods/my'
16
18
  require 'holidays/definition/custom_methods/nz'
17
19
  require 'holidays/definition/custom_methods/ph'
18
20
  require 'holidays/definition/custom_methods/se'
@@ -46,14 +48,19 @@ module Holidays
46
48
  "columbus_day_cl(year)" => CL.method(:columbus_day_cl).to_proc,
47
49
  "day_after_thanksgiving(year)" => FEDEX.method(:day_after_thanksgiving).to_proc,
48
50
  "de_buss_und_bettag(year)" => DE.method(:de_buss_und_bettag).to_proc,
51
+ "eid_al_adha(year)" => MA.method(:eid_al_adha).to_proc,
52
+ "eid_al_fitr(year)" => MA.method(:eid_al_fitr).to_proc,
49
53
  "election_day(year)" => US.method(:election_day).to_proc,
50
54
  "even_year_election_day(year)" => US.method(:even_year_election_day).to_proc,
51
55
  "fi_juhannusaatto(year)" => FI.method(:fi_juhannusaatto).to_proc,
52
56
  "fi_juhannuspaiva(year)" => FI.method(:fi_juhannuspaiva).to_proc,
53
57
  "fi_pyhainpaiva(year)" => FI.method(:fi_pyhainpaiva).to_proc,
54
58
  "georgia_state_holiday(year, month)" => US.method(:georgia_state_holiday).to_proc,
59
+ "hari_raya_haji(year)" => MY.method(:hari_raya_haji).to_proc,
60
+ "hari_raya_puasa(year)" => MY.method(:hari_raya_puasa).to_proc,
55
61
  "hobart_show_day(year)" => AU.method(:hobart_show_day).to_proc,
56
62
  "ie_st_brigids_day(year)" => IE.method(:ie_st_brigids_day).to_proc,
63
+ "indigenous_peoples_day_cl(year)" => CL.method(:indigenous_peoples_day_cl).to_proc,
57
64
  "independence_day(region, date)" => US.method(:independence_day).to_proc,
58
65
  "is_sumardagurinn_fyrsti(year)" => IS.method(:is_sumardagurinn_fyrsti).to_proc,
59
66
  "jp_citizens_holiday(year)" => JP.method(:jp_citizens_holiday).to_proc,
@@ -87,6 +94,7 @@ module Holidays
87
94
  "qld_queens_bday_october(year)" => AU.method(:qld_queens_bday_october).to_proc,
88
95
  "qld_queens_birthday_june(year)" => AU.method(:qld_queens_birthday_june).to_proc,
89
96
  "ramadan_feast(year)" => TR.method(:ramadan_feast).to_proc,
97
+ "ramadan_feast_second_in_year(year)" => TR.method(:ramadan_feast_second_in_year).to_proc,
90
98
  "rosh_hashanah(year)" => US.method(:rosh_hashanah).to_proc,
91
99
  "sacrifice_feast(year)" => TR.method(:sacrifice_feast).to_proc,
92
100
  "se_alla_helgons_dag(year)" => SE.method(:se_alla_helgons_dag).to_proc,
@@ -12,6 +12,10 @@ module Holidays
12
12
  # day (2016 Ramazan Bayramı, two days) before the arithmetic result;
13
13
  # DIYANET_OVERRIDES pins those years to the proclaimed date. Years with
14
14
  # no entry fall back to the calculation. See definitions#377.
15
+ #
16
+ # ramadan_feast only returns the first occurrence of a Gregorian year;
17
+ # ramadan_feast_second_in_year returns the second one in the rare years
18
+ # that have two (2033), and nil otherwise.
15
19
  module TR
16
20
  SHAWWAL = 10
17
21
  DHU_AL_HIJJAH = 12
@@ -19,7 +23,7 @@ module Holidays
19
23
  # Proclaimed first days that differ from the arithmetic calendar,
20
24
  # keyed by [feast, gregorian_year]. Sourced from the Diyanet dates
21
25
  # that the definition file previously carried as a fixed table, plus
22
- # https://vakithesaplama.diyanet.gov.tr/resmitatiller.php for 2021-2030.
26
+ # https://vakithesaplama.diyanet.gov.tr/resmitatiller.php for 2021-2035.
23
27
  DIYANET_OVERRIDES = {
24
28
  [:ramadan_feast, 2014] => Date.new(2014, 7, 28),
25
29
  [:ramadan_feast, 2015] => Date.new(2015, 7, 17),
@@ -32,6 +36,9 @@ module Holidays
32
36
  [:ramadan_feast, 2027] => Date.new(2027, 3, 9),
33
37
  [:ramadan_feast, 2028] => Date.new(2028, 2, 26),
34
38
  [:ramadan_feast, 2030] => Date.new(2030, 2, 4),
39
+ [:ramadan_feast, 2031] => Date.new(2031, 1, 24),
40
+ [:ramadan_feast, 2033] => Date.new(2033, 1, 2),
41
+ [:ramadan_feast, 2035] => Date.new(2035, 12, 1),
35
42
 
36
43
  [:sacrifice_feast, 2014] => Date.new(2014, 10, 4),
37
44
  [:sacrifice_feast, 2016] => Date.new(2016, 9, 12),
@@ -44,6 +51,8 @@ module Holidays
44
51
  [:sacrifice_feast, 2025] => Date.new(2025, 6, 6),
45
52
  [:sacrifice_feast, 2027] => Date.new(2027, 5, 16),
46
53
  [:sacrifice_feast, 2030] => Date.new(2030, 4, 13),
54
+ [:sacrifice_feast, 2031] => Date.new(2031, 4, 2),
55
+ [:sacrifice_feast, 2033] => Date.new(2033, 3, 11),
47
56
  }.freeze
48
57
 
49
58
  class << self
@@ -51,6 +60,11 @@ module Holidays
51
60
  feast(:ramadan_feast, year, SHAWWAL, 1)
52
61
  end
53
62
 
63
+ def ramadan_feast_second_in_year(year)
64
+ occurrences = hijri.gregorian_year_occurrences(year, SHAWWAL, 1)
65
+ occurrences.last if occurrences.size > 1
66
+ end
67
+
54
68
  def sacrifice_feast(year)
55
69
  feast(:sacrifice_feast, year, DHU_AL_HIJJAH, 10)
56
70
  end
@@ -59,7 +73,11 @@ module Holidays
59
73
 
60
74
  def feast(name, year, hijri_month, hijri_day)
61
75
  DIYANET_OVERRIDES[[name, year]] ||
62
- Holidays::Factory::DateCalculator.hijri_date.gregorian_year_occurrence(year, hijri_month, hijri_day)
76
+ hijri.gregorian_year_occurrence(year, hijri_month, hijri_day)
77
+ end
78
+
79
+ def hijri
80
+ Holidays::Factory::DateCalculator.hijri_date
63
81
  end
64
82
  end
65
83
  end
@@ -38,7 +38,7 @@ module Holidays
38
38
 
39
39
  def self.holidays_by_month
40
40
  {
41
- #{month_strings.join(",\n")}
41
+ #{month_strings.join(",\n")}
42
42
  }
43
43
  end
44
44
 
@@ -49,7 +49,8 @@ module Holidays
49
49
  existing_def[:function_modifier] == target_def[:function_modifier] &&
50
50
  existing_def[:type] == target_def[:type] &&
51
51
  existing_def[:observed] == target_def[:observed] &&
52
- existing_def[:year_ranges] == target_def[:year_ranges]
52
+ existing_def[:year_ranges] == target_def[:year_ranges] &&
53
+ existing_def[:excluded_regions] == target_def[:excluded_regions]
53
54
  end
54
55
  end
55
56
  end
@@ -8,4 +8,9 @@ module Holidays
8
8
  class InvalidRegion < Error; end
9
9
 
10
10
  class DefinitionTestError < Error; end
11
+
12
+ # Raised when a calculation needs data for a year outside the range its
13
+ # tables or formula support, e.g. a lunar date past the end of the lunar
14
+ # calendar tables.
15
+ class UnsupportedYearError < Error; end
11
16
  end
@@ -2,6 +2,11 @@ module Holidays
2
2
  module Finder
3
3
  module Context
4
4
  class NextHoliday
5
+ # Holidays are searched one 12 month window at a time. The search widens until
6
+ # enough holidays are found or this many years have been searched, which stops
7
+ # a region with no upcoming holidays from being searched forever.
8
+ MAX_YEARS = 50
9
+
5
10
  def initialize(definition_search, dates_driver_builder, options_parser)
6
11
  @definition_search = definition_search
7
12
  @dates_driver_builder = dates_driver_builder
@@ -12,31 +17,36 @@ module Holidays
12
17
  validate!(holidays_count, from_date)
13
18
 
14
19
  regions, observed, informal = @options_parser.call(options)
20
+ opts = gather_options(observed, informal)
15
21
 
16
22
  holidays = []
17
- opts = gather_options(observed, informal)
18
23
 
19
- # This could be smarter but I don't have any evidence that just checking for
20
- # the next 12 months will cause us issues. If it does we can implement something
21
- # smarter here to check in smaller increments.
22
- dates_driver = @dates_driver_builder.call(from_date, from_date >> 12)
24
+ 1.upto(MAX_YEARS) do |year|
25
+ holidays.concat(search_window(from_date, year, regions, opts))
26
+ break if holidays.size >= holidays_count
27
+ end
23
28
 
24
- @definition_search
25
- .call(dates_driver, regions, opts)
26
- .sort_by { |a| a[:date] }
27
- .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_by { |a| a[:date] }
29
+ holidays.first(holidays_count)
36
30
  end
37
31
 
38
32
  private
39
33
 
34
+ # Windows are contiguous: each starts the day after the previous one ended. The
35
+ # search only looks at the months around a window (plus month 0, which covers the
36
+ # whole year), so dates past the window end are incomplete. They are discarded here
37
+ # and found by the next window instead.
38
+ def search_window(from_date, year, regions, opts)
39
+ window_start = year == 1 ? from_date : (from_date >> 12 * (year - 1)) + 1
40
+ window_end = from_date >> 12 * year
41
+
42
+ dates_driver = @dates_driver_builder.call(window_start, window_end)
43
+
44
+ @definition_search
45
+ .call(dates_driver, regions, opts)
46
+ .select { |holiday| (window_start..window_end).cover?(holiday[:date]) }
47
+ .sort_by { |holiday| holiday[:date] }
48
+ end
49
+
40
50
  def validate!(holidays_count, from_date)
41
51
  raise ArgumentError unless holidays_count
42
52
  raise ArgumentError if holidays_count <= 0
@@ -1,3 +1,5 @@
1
+ require 'holidays/errors'
2
+
1
3
  module Holidays
2
4
  module Finder
3
5
  module Context
@@ -14,29 +16,11 @@ module Holidays
14
16
 
15
17
  holidays = []
16
18
  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 informal_type?(h[:type]) && !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
- 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
- end
33
-
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
19
+ candidates(months).each do |month, h|
20
+ next unless applicable?(year, h, regions, options)
21
+
22
+ year_dates(year, months, month, h, regions, options).each do |d|
23
+ holidays << {:date => d, :name => h[:name], :regions => h[:regions]}
40
24
  end
41
25
  end
42
26
  end
@@ -61,6 +45,66 @@ module Holidays
61
45
  end
62
46
  end
63
47
 
48
+ # Returns [month, definition] pairs to evaluate for one year of the dates
49
+ # driver. Fixed-date definitions only matter in the driver's months, but a
50
+ # function (e.g. a lunar date filed under its lunar month) can land several
51
+ # months away from the month it is filed under, so every function definition
52
+ # is evaluated, once, regardless of its month.
53
+ # See https://github.com/holidays/holidays/issues/519
54
+ def candidates(months)
55
+ by_month = @holidays_by_month_repo.all
56
+
57
+ in_driver = months.flat_map do |month|
58
+ (by_month[month] || []).map { |h| [month, h] }
59
+ end
60
+
61
+ outside_driver = (by_month.keys - months).sort.flat_map do |month|
62
+ by_month[month].select { |h| h[:function] }.map { |h| [month, h] }
63
+ end
64
+
65
+ in_driver + outside_driver
66
+ end
67
+
68
+ # A year that only pads the query with a border month (see
69
+ # DatesDriverBuilder#add_border_months) may lie past the data a
70
+ # calculation supports, e.g. lunar year 2101. Dates of such a year are
71
+ # outside the query anyway, so they are skipped rather than failing
72
+ # the query. A year the query actually covers still raises.
73
+ def year_dates(year, months, month, h, regions, options)
74
+ holiday_dates(year, month, h, regions, options)
75
+ rescue Holidays::UnsupportedYearError
76
+ raise unless border_padding?(months)
77
+
78
+ []
79
+ end
80
+
81
+ # Every date in the query adds its own month and a neighbouring one to
82
+ # its year, so a year holding a single month (besides the variable
83
+ # month 0) is only there as border padding.
84
+ def border_padding?(months)
85
+ (months - [0]).size == 1
86
+ end
87
+
88
+ def applicable?(year, h, regions, options)
89
+ return false if informal_type?(h[:type]) && !informal_set?(options)
90
+ return false unless @rules[:in_region].call(regions, h[:regions], h[:excluded_regions])
91
+ return true unless h[:year_ranges]
92
+
93
+ @rules[:year_range].call(year, h[:year_ranges])
94
+ end
95
+
96
+ def holiday_dates(year, month, h, regions, options)
97
+ dates = if h[:function]
98
+ custom_holidays(year, month, h, regions)
99
+ else
100
+ [build_date(year, month, h, regions)].compact
101
+ end
102
+
103
+ return dates unless observed_set?(options) && h[:observed]
104
+
105
+ dates.map { |d| build_observed_date(d, regions, h) }
106
+ end
107
+
64
108
  def informal_type?(type)
65
109
  type && [:informal, 'informal'].include?(type)
66
110
  end
@@ -13,15 +13,13 @@ module Holidays
13
13
 
14
14
  regions, observed, informal = @options_parser.call(options)
15
15
 
16
- # This could be smarter but I don't have any evidence that just checking for
17
- # the next 12 months will cause us issues. If it does we can implement something
18
- # smarter here to check in smaller increments.
19
- #
20
- #FIXME Could this be until the to_date instead? Save us some processing?
21
- # This is matching what was in holidays.rb currently so I'm keeping it. -pp
22
- dates_driver = @dates_driver_builder.call(from_date, from_date >> 12)
23
-
16
+ # The driver stops at to_date; the dates driver still adds January of
17
+ # the next year as a border month. Searching a full 12 months from
18
+ # from_date would make the next year a searched year rather than
19
+ # border padding, so e.g. year_holidays for 2100 would need lunar
20
+ # data for 2101.
24
21
  to_date = Date.civil(from_date.year, 12, 31)
22
+ dates_driver = @dates_driver_builder.call(from_date, to_date)
25
23
  holidays = []
26
24
  ret_holidays = []
27
25
  opts = gather_options(observed, informal)
@@ -3,26 +3,31 @@ module Holidays
3
3
  module Rules
4
4
  class InRegion
5
5
  class << self
6
- def call(requested, available)
6
+ # Each requested region is checked on its own. It qualifies when one
7
+ # of the available regions is the region itself or one of its
8
+ # ancestors (us for us_ca) and none of the excluded regions is. The
9
+ # rule matches when any requested region qualifies. :any always
10
+ # matches, whatever is excluded.
11
+ def call(requested, available, excluded = nil)
7
12
  return true if requested.include?(:any)
8
13
 
9
- # When an underscore is encountered, derive the parent regions
10
- # symbol and check for both.
11
- requested = requested.collect do |r|
12
- if r.to_s.include?('_')
13
- chunks = r.to_s.split('_')
14
+ excluded ||= []
14
15
 
15
- chunks.length.downto(1).map do |num|
16
- chunks[0..-num].join('_').to_sym
17
- end
18
- else
19
- r
20
- end
16
+ requested.any? do |region|
17
+ lineage = lineage(region)
18
+
19
+ (available & lineage).any? && (excluded & lineage).empty?
21
20
  end
21
+ end
22
+
23
+ private
22
24
 
23
- requested = requested.flatten.uniq
25
+ # The region and all of its parents, e.g. :us_ca_xx gives
26
+ # [:us, :us_ca, :us_ca_xx].
27
+ def lineage(region)
28
+ chunks = region.to_s.split('_')
24
29
 
25
- available.any? { |avail| requested.include?(avail) }
30
+ 1.upto(chunks.length).map { |n| chunks.first(n).join('_').to_sym }
26
31
  end
27
32
  end
28
33
  end
@@ -1,3 +1,3 @@
1
1
  module Holidays
2
- VERSION = '11.6.0'
2
+ VERSION = '11.8.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: holidays
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.6.0
4
+ version: 11.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Dunae
@@ -219,6 +219,8 @@ files:
219
219
  - lib/holidays/definition/custom_methods/jp.rb
220
220
  - lib/holidays/definition/custom_methods/kr.rb
221
221
  - lib/holidays/definition/custom_methods/lv.rb
222
+ - lib/holidays/definition/custom_methods/ma.rb
223
+ - lib/holidays/definition/custom_methods/my.rb
222
224
  - lib/holidays/definition/custom_methods/nz.rb
223
225
  - lib/holidays/definition/custom_methods/ph.rb
224
226
  - lib/holidays/definition/custom_methods/registry.rb