alexdunae-holidays 0.9.4

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 (77) hide show
  1. data/CHANGELOG +15 -0
  2. data/LICENSE +21 -0
  3. data/README.rdoc +77 -0
  4. data/REFERENCES +16 -0
  5. data/data/SYNTAX +111 -0
  6. data/data/au.yaml +106 -0
  7. data/data/build_defs.rb +152 -0
  8. data/data/ca.yaml +141 -0
  9. data/data/de.yaml +115 -0
  10. data/data/dk.yaml +117 -0
  11. data/data/es.yaml +161 -0
  12. data/data/fr.yaml +70 -0
  13. data/data/gb.yaml +106 -0
  14. data/data/ie.yaml +58 -0
  15. data/data/index.yaml +23 -0
  16. data/data/is.yaml +136 -0
  17. data/data/it.yaml +66 -0
  18. data/data/mx.yaml +106 -0
  19. data/data/nl.yaml +67 -0
  20. data/data/north_america_informal.yaml +49 -0
  21. data/data/nyse.yaml +63 -0
  22. data/data/pt.yaml +85 -0
  23. data/data/se.yaml +91 -0
  24. data/data/united_nations.yaml +188 -0
  25. data/data/ups.yaml +56 -0
  26. data/data/us.yaml +81 -0
  27. data/data/za.yaml +78 -0
  28. data/lib/holidays.rb +404 -0
  29. data/lib/holidays/MANIFEST +25 -0
  30. data/lib/holidays/au.rb +41 -0
  31. data/lib/holidays/ca.rb +68 -0
  32. data/lib/holidays/de.rb +52 -0
  33. data/lib/holidays/dk.rb +47 -0
  34. data/lib/holidays/es.rb +52 -0
  35. data/lib/holidays/europe.rb +215 -0
  36. data/lib/holidays/fr.rb +36 -0
  37. data/lib/holidays/gb.rb +40 -0
  38. data/lib/holidays/ie.rb +32 -0
  39. data/lib/holidays/is.rb +61 -0
  40. data/lib/holidays/it.rb +35 -0
  41. data/lib/holidays/mx.rb +51 -0
  42. data/lib/holidays/nl.rb +36 -0
  43. data/lib/holidays/north_america.rb +107 -0
  44. data/lib/holidays/nyse.rb +32 -0
  45. data/lib/holidays/pt.rb +37 -0
  46. data/lib/holidays/scandinavia.rb +114 -0
  47. data/lib/holidays/se.rb +52 -0
  48. data/lib/holidays/united_nations.rb +24 -0
  49. data/lib/holidays/ups.rb +31 -0
  50. data/lib/holidays/us.rb +48 -0
  51. data/lib/holidays/za.rb +35 -0
  52. data/test/defs/test_defs_au.rb +36 -0
  53. data/test/defs/test_defs_ca.rb +29 -0
  54. data/test/defs/test_defs_de.rb +46 -0
  55. data/test/defs/test_defs_dk.rb +30 -0
  56. data/test/defs/test_defs_es.rb +57 -0
  57. data/test/defs/test_defs_europe.rb +240 -0
  58. data/test/defs/test_defs_fr.rb +26 -0
  59. data/test/defs/test_defs_gb.rb +36 -0
  60. data/test/defs/test_defs_ie.rb +21 -0
  61. data/test/defs/test_defs_is.rb +33 -0
  62. data/test/defs/test_defs_it.rb +25 -0
  63. data/test/defs/test_defs_mx.rb +22 -0
  64. data/test/defs/test_defs_nl.rb +24 -0
  65. data/test/defs/test_defs_north_america.rb +54 -0
  66. data/test/defs/test_defs_nyse.rb +22 -0
  67. data/test/defs/test_defs_pt.rb +32 -0
  68. data/test/defs/test_defs_scandinavia.rb +75 -0
  69. data/test/defs/test_defs_se.rb +32 -0
  70. data/test/defs/test_defs_ups.rb +21 -0
  71. data/test/defs/test_defs_us.rb +23 -0
  72. data/test/defs/test_defs_za.rb +25 -0
  73. data/test/test_date.rb +87 -0
  74. data/test/test_helper.rb +11 -0
  75. data/test/test_holidays.rb +117 -0
  76. data/test/test_multiple_regions.rb +20 -0
  77. metadata +164 -0
@@ -0,0 +1,188 @@
1
+ # United Nationas holiday definitions for the Ruby Holiday gem.
2
+ # Updated 2008-11-21.
3
+ ---
4
+ 2:
5
+ - name: International Mother Language Day
6
+ regions: [un]
7
+ mday: 2
8
+ 3:
9
+ - name: United Nations Day for Women's Rights and International Peace
10
+ regions: [un]
11
+ mday: 8
12
+ - name: International Day for the Elimination of Racial Discrimination
13
+ regions: [un]
14
+ mday: 21
15
+ - name: Beginning of the Week of Solidarity with the Peoples Struggling against Racism and Racial Discrimination
16
+ regions: [un]
17
+ mday: 21
18
+ - name: World Day for Water
19
+ regions: [un]
20
+ mday: 22
21
+ - name: World Meteorological Day
22
+ regions: [un]
23
+ mday: 23
24
+ 4:
25
+ - name: World Health Day
26
+ regions: [un]
27
+ mday: 7
28
+ - name: World Book and Copyright Day
29
+ regions: [un]
30
+ mday: 23
31
+ 5:
32
+ - name: World Press Freedom Day
33
+ regions: [un]
34
+ mday: 3
35
+ - name: International Day of Families
36
+ regions: [un]
37
+ mday: 15
38
+ - name: World Telecommunication Day
39
+ regions: [un]
40
+ mday: 17
41
+ - name: World Day for Cultural Diversity for Dialogue and Development
42
+ regions: [un]
43
+ mday: 21
44
+ - name: International Day for Biological Diversity
45
+ regions: [un]
46
+ mday: 22
47
+ - name: Beginning of the Week of Solidarity with the Peoples of Non-Self-Governing Territories
48
+ regions: [un]
49
+ mday: 25
50
+ - name: International Day of United Nations Peacekeepers
51
+ regions: [un]
52
+ mday: 29
53
+ - name: World No-Tobacco Day
54
+ regions: [un]
55
+ mday: 31
56
+ 6:
57
+ - name: International Day of Innocent Children Victims of Aggression
58
+ regions: [un]
59
+ mday: 4
60
+ - name: World Environment Day
61
+ regions: [un]
62
+ mday: 5
63
+ - name: World Day to Combat Desertification and Drought
64
+ regions: [un]
65
+ mday: 17
66
+ - name: World Refugee Day
67
+ regions: [un]
68
+ mday: 20
69
+ - name: United Nations Public Service Day
70
+ regions: [un]
71
+ mday: 23
72
+ - name: International Day against Drug Abuse and Illicit Trafficking
73
+ regions: [un]
74
+ mday: 26
75
+ - name: International Day in Support of Victims of Torture
76
+ regions: [un]
77
+ mday: 26
78
+ 7:
79
+ - name: International Day of Cooperatives
80
+ week: 1
81
+ regions: [un]
82
+ wday: 6
83
+ - name: World Population Day
84
+ regions: [un]
85
+ mday: 11
86
+ 8:
87
+ - name: International Day of the World's Indigenous People
88
+ regions: [un]
89
+ mday: 9
90
+ - name: International Youth Day
91
+ regions: [un]
92
+ mday: 12
93
+ - name: International Day for the Remembrance of the Slave Trade and Its Abolition
94
+ regions: [un]
95
+ mday: 23
96
+ 9:
97
+ - name: International Literacy Day
98
+ regions: [un]
99
+ mday: 8
100
+ - name: International Day for the Preservation of the Ozone Layer
101
+ regions: [un]
102
+ mday: 16
103
+ - name: International Day of Peace
104
+ regions: [un]
105
+ mday: 21
106
+ - name: International Day of Older Persons
107
+ regions: [un]
108
+ mday: 1
109
+ 10:
110
+ - name: World Space Week
111
+ regions: [un]
112
+ mday: 4
113
+ - name: World Teachers' Day
114
+ regions: [un]
115
+ mday: 5
116
+ - name: World Habitat Day
117
+ week: 1
118
+ regions: [un]
119
+ wday: 1
120
+ - name: International Day for Natural Disaster Reduction
121
+ week: 2
122
+ regions: [un]
123
+ wday: 3
124
+ - name: World Post Day
125
+ regions: [un]
126
+ mday: 9
127
+ - name: World Mental Health Day
128
+ regions: [un]
129
+ mday: 10
130
+ - name: World Food Day
131
+ regions: [un]
132
+ mday: 16
133
+ - name: International Day for the Eradication of Poverty
134
+ regions: [un]
135
+ mday: 17
136
+ - name: United Nations Day
137
+ regions: [un]
138
+ mday: 24
139
+ - name: World Development Information Day
140
+ regions: [un]
141
+ mday: 24
142
+ - name: Disarmament Week
143
+ regions: [un]
144
+ mday: 24
145
+ 11:
146
+ - name: International Day for Preventing the Exploitation of the Environment in War and Armed Conflict
147
+ regions: [un]
148
+ mday: 6
149
+ - name: International Day for Tolerance
150
+ regions: [un]
151
+ mday: 16
152
+ - name: Africa Industrialization Day
153
+ regions: [un]
154
+ mday: 20
155
+ - name: Universal Children's Day
156
+ regions: [un]
157
+ mday: 20
158
+ - name: World Television Day
159
+ regions: [un]
160
+ mday: 21
161
+ - name: International Day for the Elimination of Violence against Women
162
+ regions: [un]
163
+ mday: 25
164
+ - name: International Day of Solidarity with the Palestinian People
165
+ regions: [un]
166
+ mday: 29
167
+ 12:
168
+ - name: World AIDS Day
169
+ regions: [un]
170
+ mday: 1
171
+ - name: International Day for the Abolition of Slavery
172
+ regions: [un]
173
+ mday: 2
174
+ - name: International Day of Disabled Persons
175
+ regions: [un]
176
+ mday: 3
177
+ - name: International Volunteer Day for Economic and Social Development
178
+ regions: [un]
179
+ mday: 5
180
+ - name: International Civil Aviation Day
181
+ regions: [un]
182
+ mday: 7
183
+ - name: Human Rights Day
184
+ regions: [un]
185
+ mday: 10
186
+ - name: International Migrants Day
187
+ regions: [un]
188
+ mday: 18
data/data/ups.yaml ADDED
@@ -0,0 +1,56 @@
1
+ # UPS holiday definitions for the Ruby Holiday gem.
2
+ #
3
+ # By Tim Anglade
4
+ #
5
+ # Updated: 2008-09-23.
6
+ # Source: http://www.ups.com/content/us/en/resources/ship/imp_exp/operation.html
7
+ ---
8
+ months:
9
+ 1:
10
+ - name: New Year's Day
11
+ regions: [ups]
12
+ mday: 1
13
+ observed: to_weekday_if_weekend
14
+ 5:
15
+ - name: Memorial Day
16
+ week: -1
17
+ regions: [ups]
18
+ wday: 1
19
+ 7:
20
+ - name: Independence Day
21
+ regions: [ups]
22
+ mday: 4
23
+ observed: to_weekday_if_weekend
24
+ 9:
25
+ - name: Labor Day
26
+ week: 1
27
+ regions: [ups]
28
+ wday: 1
29
+ 11:
30
+ - name: Thanksgiving
31
+ week: 4
32
+ regions: [ups]
33
+ wday: 4
34
+ - name: Day After Thanksgiving
35
+ week: 4
36
+ regions: [ups]
37
+ wday: 5
38
+ 12:
39
+ - name: Christmas Day
40
+ regions: [ups]
41
+ mday: 25
42
+ observed: to_weekday_if_weekend
43
+ - name: New Year's Eve
44
+ regions: [ups]
45
+ mday: 31
46
+ tests: |
47
+ {Date.civil(2008,1,1) => 'New Year\'s Day',
48
+ Date.civil(2008,5,26) => 'Memorial Day',
49
+ Date.civil(2008,7,4) => 'Independence Day',
50
+ Date.civil(2008,9,1) => 'Labor Day',
51
+ Date.civil(2008,11,27) => 'Thanksgiving',
52
+ Date.civil(2008,11,28) => 'Day After Thanksgiving',
53
+ Date.civil(2008,12,25) => 'Christmas Day',
54
+ Date.civil(2008,12,31) => 'New Year\'s Eve',}.each do |date, name|
55
+ assert_equal name, Holidays.on(date, :ups)[0][:name]
56
+ end
data/data/us.yaml ADDED
@@ -0,0 +1,81 @@
1
+ # United States holiday definitions for the Ruby Holiday gem.
2
+ #
3
+ # Updated: 2008-11-24.
4
+ # Source: http://en.wikipedia.org/wiki/Public_holidays_of_the_United_States
5
+ ---
6
+ months:
7
+ 0:
8
+ - name: Good Friday
9
+ regions: [us]
10
+ function: easter(year)-2
11
+ type: informal
12
+ 1:
13
+ - name: New Year's Day
14
+ regions: [us]
15
+ mday: 1
16
+ observed: to_weekday_if_weekend
17
+ - name: Martin Luther King, Jr. Day
18
+ week: 3
19
+ regions: [us]
20
+ wday: 1
21
+ - name: Inauguration Day
22
+ function: us_inauguration_day(year)
23
+ regions: [us_dc]
24
+ 2:
25
+ - name: Presidents' Day
26
+ week: 3
27
+ regions: [us]
28
+ wday: 1
29
+ 5:
30
+ - name: Memorial Day
31
+ week: -1
32
+ regions: [us]
33
+ wday: 1
34
+ 7:
35
+ - name: Independence Day
36
+ regions: [us]
37
+ mday: 4
38
+ observed: to_weekday_if_weekend
39
+ 9:
40
+ - name: Labor Day
41
+ week: 1
42
+ regions: [us]
43
+ wday: 1
44
+ 10:
45
+ - name: Columbus Day
46
+ week: 2
47
+ regions: [us]
48
+ wday: 1
49
+ 11:
50
+ - name: Veterans Day
51
+ regions: [us]
52
+ mday: 11
53
+ observed: to_weekday_if_weekend
54
+ - name: Thanksgiving
55
+ week: 4
56
+ regions: [us]
57
+ wday: 4
58
+ 12:
59
+ - name: Christmas Day
60
+ regions: [us]
61
+ mday: 25
62
+ observed: to_weekday_if_weekend
63
+ methods:
64
+ us_inauguration_day: |
65
+ # January 20, every fourth year, following Presidential election
66
+ def self.us_inauguration_day(year)
67
+ year % 4 == 1 ? 20 : nil
68
+ end
69
+ tests: |
70
+ {Date.civil(2008,1,1) => 'New Year\'s Day',
71
+ Date.civil(2008,1,21) => 'Martin Luther King, Jr. Day',
72
+ Date.civil(2008,2,18) => 'Presidents\' Day',
73
+ Date.civil(2008,5,26) => 'Memorial Day',
74
+ Date.civil(2008,7,4) => 'Independence Day',
75
+ Date.civil(2008,9,1) => 'Labor Day',
76
+ Date.civil(2008,10,13) => 'Columbus Day',
77
+ Date.civil(2008,11,11) => 'Veterans Day',
78
+ Date.civil(2008,11,27) => 'Thanksgiving',
79
+ Date.civil(2008,12,25) => 'Christmas Day'}.each do |date, name|
80
+ assert_equal name, Holidays.on(date, :us)[0][:name]
81
+ end
data/data/za.yaml ADDED
@@ -0,0 +1,78 @@
1
+ # South African holiday definitions for the Ruby Holiday gem.
2
+ #
3
+ # Updated: 2008-11-29.
4
+ # Sources:
5
+ # - http://en.wikipedia.org/wiki/Public_holidays_in_South_Africa
6
+ # - http://www.info.gov.za/aboutsa/holidays.htm
7
+ ---
8
+ months:
9
+ 0:
10
+ - name: Good Friday
11
+ regions: [za]
12
+ function: easter(year)-2
13
+ - name: Family Day
14
+ regions: [za]
15
+ function: easter(year)+1
16
+ 1:
17
+ - name: New Year's Day
18
+ regions: [za]
19
+ mday: 1
20
+ observed: to_monday_if_sunday
21
+ 3:
22
+ - name: Human Rights Day
23
+ regions: [za]
24
+ mday: 21
25
+ observed: to_monday_if_sunday
26
+ 4:
27
+ - name: Freedom Day
28
+ regions: [za]
29
+ mday: 27
30
+ observed: to_monday_if_sunday
31
+ 5:
32
+ - name: Workers Day
33
+ regions: [za]
34
+ mday: 1
35
+ observed: to_monday_if_sunday
36
+ 6:
37
+ - name: Youth Day
38
+ regions: [za]
39
+ mday: 16
40
+ observed: to_monday_if_sunday
41
+ 8:
42
+ - name: National Women's Day
43
+ regions: [za]
44
+ mday: 9
45
+ observed: to_monday_if_sunday
46
+ 9:
47
+ - name: Heritage Day
48
+ regions: [za]
49
+ mday: 24
50
+ observed: to_monday_if_sunday
51
+ 12:
52
+ - name: Day of Reconciliation
53
+ regions: [za]
54
+ mday: 16
55
+ observed: to_monday_if_sunday
56
+ - name: Christmas Day
57
+ regions: [za]
58
+ mday: 25
59
+ observed: to_monday_if_sunday
60
+ - name: Day of Goodwill
61
+ regions: [za]
62
+ mday: 26
63
+ observed: to_weekday_if_boxing_weekend
64
+ tests: |
65
+ {Date.civil(2007,1,1) => 'New Year\'s Day',
66
+ Date.civil(2007,3,21) => 'Human Rights Day',
67
+ Date.civil(2007,4,6) => 'Good Friday',
68
+ Date.civil(2007,4,9) => 'Family Day',
69
+ Date.civil(2007,4,27) => 'Freedom Day',
70
+ Date.civil(2007,5,1) => 'Workers Day',
71
+ Date.civil(2007,6,16) => 'Youth Day',
72
+ Date.civil(2007,8,9) => 'National Women\'s Day',
73
+ Date.civil(2007,9,24) => 'Heritage Day',
74
+ Date.civil(2007,12,16) => 'Day of Reconciliation',
75
+ Date.civil(2007,12,25) => 'Christmas Day',
76
+ Date.civil(2007,12,26) => 'Day of Goodwill'}.each do |date, name|
77
+ assert_equal name, Holidays.on(date, :za, :informal)[0][:name]
78
+ end
data/lib/holidays.rb ADDED
@@ -0,0 +1,404 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'digest/md5'
4
+ require 'date'
5
+
6
+ # == Region options
7
+ # Holidays can be defined as belonging to one or more regions and sub regions.
8
+ # The Holidays#on, Holidays#between, Date#holidays and Date#holiday? methods
9
+ # each allow you to specify a specific region.
10
+ #
11
+ # There are several different ways that you can specify a region:
12
+ #
13
+ # [<tt>:region</tt>]
14
+ # By region. For example, return holidays in the Canada with <tt>:ca</tt>.
15
+ # [<tt>:region_</tt>]
16
+ # By region and sub regions. For example, return holidays in Germany
17
+ # and all its sub regions with <tt>:de_</tt>.
18
+ # [<tt>:region_sub</tt>]
19
+ # By sub region. Return national holidays in Spain plus holidays in Spain's
20
+ # Valencia region with <tt>:es_v</tt>.
21
+ # [<tt>:any</tt>]
22
+ # Any region. Return holidays from any loaded region.
23
+ #
24
+ # == Other options
25
+ # [<tt>:observed</tt>] Return holidays on the day they are observed (e.g. on a Monday if they fall on a Sunday).
26
+ # [<tt>:informal</tt>] Include informal holidays (e.g. Valentine's Day)
27
+ #
28
+ # == Examples
29
+ # Return all holidays in the <tt>:ca</tt> and <tt>:us</tt> regions on the day that they are
30
+ # observed.
31
+ #
32
+ # Holidays.between(from, to, :ca, :us, :observed)
33
+ #
34
+ # Return all holidays in <tt>:ca</tt> and any <tt>:ca</tt> sub-region.
35
+ #
36
+ # Holidays.between(from, to, :ca_)
37
+ #
38
+ # Return all holidays in <tt>:ca_bc</tt> sub-region (which includes the <tt>:ca</tt>), including informal holidays.
39
+ #
40
+ # Holidays.between(from, to, :ca_bc, :informal)
41
+ module Holidays
42
+ # Exception thrown when an unknown region is requested.
43
+ class UnkownRegionError < ArgumentError; end
44
+
45
+ VERSION = '0.9.2'
46
+
47
+ @@regions = []
48
+ @@holidays_by_month = {}
49
+ @@proc_cache = {}
50
+
51
+ WEEKS = {:first => 1, :second => 2, :third => 3, :fourth => 4, :fifth => 5, :last => -1}
52
+ MONTH_LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
53
+ DAY_SYMBOLS = Date::DAYNAMES.collect { |n| n.downcase.intern }
54
+
55
+ # Get all holidays on a given date.
56
+ #
57
+ # [<tt>date</tt>] A Date object.
58
+ # [<tt>:options</tt>] One or more region symbols, <tt>:informal</tt> and/or <tt>:observed</tt>.
59
+ #
60
+ # Returns an array of hashes or nil. See Holidays#between for the output
61
+ # format.
62
+ #
63
+ # Also available via Date#holidays.
64
+ def self.on(date, *options)
65
+ self.between(date, date, options)
66
+ end
67
+
68
+ # Get all holidays occuring between two dates, inclusively.
69
+ #
70
+ # Returns an array of hashes or nil.
71
+ #
72
+ # Each holiday is returned as a hash with the following fields:
73
+ # [<tt>start_date</tt>] Ruby Date object.
74
+ # [<tt>end_date</tt>] Ruby Date object.
75
+ # [<tt>options</tt>] One or more region symbols, <tt>:informal</tt> and/or <tt>:observed</tt>.
76
+ #
77
+ # ==== Example
78
+ # from = Date.civil(2008,7,1)
79
+ # to = Date.civil(2008,7,31)
80
+ #
81
+ # Holidays.between(from, to, :ca, :us)
82
+ # => [{:name => 'Canada Day', :regions => [:ca]...}
83
+ # {:name => 'Independence Day'', :regions => [:us], ...}]
84
+ def self.between(start_date, end_date, *options)
85
+ regions, observed, informal = parse_options(options)
86
+ holidays = []
87
+
88
+ dates = {}
89
+ (start_date..end_date).each do |date|
90
+ # Always include month '0' for variable-month holidays
91
+ dates[date.year] = [0] unless dates[date.year]
92
+ # TODO: test this, maybe should push then flatten
93
+ dates[date.year] << date.month unless dates[date.year].include?(date.month)
94
+ end
95
+
96
+ dates.each do |year, months|
97
+ months.each do |month|
98
+ next unless hbm = @@holidays_by_month[month]
99
+
100
+ hbm.each do |h|
101
+ next unless in_region?(regions, h[:regions])
102
+
103
+ # Skip informal holidays unless they have been requested
104
+ next if h[:type] == :informal and not informal
105
+
106
+ if h[:function]
107
+ # Holiday definition requires a calculation
108
+ result = call_proc(h[:function], year)
109
+
110
+ # Procs may return either Date or an integer representing mday
111
+ if result.kind_of?(Date)
112
+ month = result.month
113
+ mday = result.mday
114
+ else
115
+ mday = result
116
+ end
117
+ else
118
+ # Calculate the mday
119
+ mday = h[:mday] || Date.calculate_mday(year, month, h[:week], h[:wday])
120
+ end
121
+
122
+ # Silently skip bad mdays
123
+ begin
124
+ date = Date.civil(year, month, mday)
125
+ rescue; next; end
126
+
127
+ # If the :observed option is set, calculate the date when the holiday
128
+ # is observed.
129
+ if observed and h[:observed]
130
+ date = call_proc(h[:observed], date)
131
+ end
132
+
133
+ if date.between?(start_date, end_date)
134
+ holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
135
+ end
136
+
137
+ end
138
+ end
139
+ end
140
+
141
+ holidays
142
+ end
143
+
144
+ # Merge a new set of definitions into the Holidays module.
145
+ #
146
+ # This method is automatically called when including holiday definition
147
+ # files.
148
+ def self.merge_defs(regions, holidays) # :nodoc:
149
+ @@regions = @@regions | regions
150
+ @@regions.uniq!
151
+
152
+ holidays.each do |month, holiday_defs|
153
+ @@holidays_by_month[month] = [] unless @@holidays_by_month[month]
154
+ holiday_defs.each do |holiday_def|
155
+
156
+ exists = false
157
+ @@holidays_by_month[month].each do |ex|
158
+ # TODO: gross.
159
+ if ex[:name] == holiday_def[:name] and ex[:wday] == holiday_def[:wday] and ex[:mday] == holiday_def[:mday] and ex[:week] == holiday_def[:week] and ex[:function_id] == holiday_def[:function_id] and ex[:type] == holiday_def[:type] and ex[:observed_id] == holiday_def[:observed_id]
160
+ # append regions
161
+ ex[:regions] << holiday_def[:regions]
162
+
163
+ # Should do this once we're done
164
+ ex[:regions].flatten!
165
+ ex[:regions].uniq!
166
+ exists = true
167
+ end
168
+ end
169
+
170
+ @@holidays_by_month[month] << holiday_def unless exists
171
+ end
172
+ end
173
+ end
174
+
175
+ # Get the date of Easter Sunday in a given year. From Easter Sunday, it is
176
+ # possible to calculate many traditional holidays in Western countries.
177
+ # Returns a Date object.
178
+ def self.easter(year)
179
+ y = year
180
+ a = y % 19
181
+ b = y / 100
182
+ c = y % 100
183
+ d = b / 4
184
+ e = b % 4
185
+ f = (b + 8) / 25
186
+ g = (b - f + 1) / 3
187
+ h = (19 * a + b - d - g + 15) % 30
188
+ i = c / 4
189
+ k = c % 4
190
+ l = (32 + 2 * e + 2 * i - h - k) % 7
191
+ m = (a + 11 * h + 22 * l) / 451
192
+ month = (h + l - 7 * m + 114) / 31
193
+ day = ((h + l - 7 * m + 114) % 31) + 1
194
+ Date.civil(year, month, day)
195
+ end
196
+
197
+ # Move date to Monday if it occurs on a Sunday.
198
+ # Used as a callback function.
199
+ def self.to_monday_if_sunday(date)
200
+ date += 1 if date.wday == 0
201
+ date
202
+ end
203
+
204
+ # Move date to Monday if it occurs on a Saturday on Sunday.
205
+ # Used as a callback function.
206
+ def self.to_monday_if_weekend(date)
207
+ date += 1 if date.wday == 0
208
+ date += 2 if date.wday == 6
209
+ date
210
+ end
211
+
212
+ # Move Boxing Day if it falls on a weekend, leaving room for Christmas.
213
+ # Used as a callback function.
214
+ def self.to_weekday_if_boxing_weekend(date)
215
+ date += 2 if date.wday == 6 or date.wday == 0
216
+ date
217
+ end
218
+
219
+ # Move date to Monday if it occurs on a Sunday or to Friday if it occurs on a
220
+ # Saturday.
221
+ # Used as a callback function.
222
+ def self.to_weekday_if_weekend(date)
223
+ date += 1 if date.wday == 0
224
+ date -= 1 if date.wday == 6
225
+ date
226
+ end
227
+
228
+ private
229
+ # Returns [(arr)regions, (bool)observed, (bool)informal]
230
+ def self.parse_options(*options) # :nodoc:
231
+ options.flatten!
232
+ observed = options.delete(:observed) ? true : false
233
+ informal = options.delete(:informal) ? true : false
234
+ regions = parse_regions(options)
235
+ return regions, observed, informal
236
+ end
237
+
238
+ # Check regions against list of supported regions and return an array of
239
+ # symbols.
240
+ #
241
+ # If a wildcard region is found (e.g. <tt>:ca_</tt>) it is expanded into all
242
+ # of its available sub regions.
243
+ def self.parse_regions(regions) # :nodoc:
244
+ regions = [regions] unless regions.kind_of?(Array)
245
+ return [:any] if regions.empty?
246
+
247
+ regions = regions.collect { |r| r.to_sym }
248
+
249
+ # Found sub region wild-card
250
+ regions.delete_if do |reg|
251
+ if reg.to_s =~ /_$/
252
+ regions << @@regions.select { |dr| dr.to_s =~ Regexp.new("^#{reg}") }
253
+ true
254
+ end
255
+ end
256
+
257
+ regions.flatten!
258
+
259
+ raise UnkownRegionError unless regions.all? { |r| r == :any or @@regions.include?(r) }
260
+
261
+ regions
262
+ end
263
+
264
+ # Check sub regions.
265
+ #
266
+ # When request :any, all holidays should be returned.
267
+ # When requesting :ca_bc, holidays in :ca or :ca_bc should be returned.
268
+ # When requesting :ca, holidays in :ca but not its subregions should be returned.
269
+ def self.in_region?(requested, available) # :nodoc:
270
+ return true if requested.include?(:any)
271
+
272
+ # When an underscore is encountered, derive the parent regions
273
+ # symbol and include both in the requested array.
274
+ requested = requested.collect do |r|
275
+ r.to_s =~ /_/ ? [r, r.to_s.gsub(/_[\w]*$/, '').to_sym] : r
276
+ end
277
+
278
+ requested = requested.flatten.uniq
279
+
280
+ available.any? { |avail| requested.include?(avail) }
281
+ end
282
+
283
+ # Call a proc function defined in a holiday definition file.
284
+ #
285
+ # Procs are cached.
286
+ #
287
+ # ==== Benchmarks
288
+ #
289
+ # Lookup Easter Sunday, with caching, by number of iterations:
290
+ #
291
+ # user system total real
292
+ # 0001 0.000000 0.000000 0.000000 ( 0.000000)
293
+ # 0010 0.000000 0.000000 0.000000 ( 0.000000)
294
+ # 0100 0.078000 0.000000 0.078000 ( 0.078000)
295
+ # 1000 0.641000 0.000000 0.641000 ( 0.641000)
296
+ # 5000 3.172000 0.015000 3.187000 ( 3.219000)
297
+ #
298
+ # Lookup Easter Sunday, without caching, by number of iterations:
299
+ #
300
+ # user system total real
301
+ # 0001 0.000000 0.000000 0.000000 ( 0.000000)
302
+ # 0010 0.016000 0.000000 0.016000 ( 0.016000)
303
+ # 0100 0.125000 0.000000 0.125000 ( 0.125000)
304
+ # 1000 1.234000 0.000000 1.234000 ( 1.234000)
305
+ # 5000 6.094000 0.031000 6.125000 ( 6.141000)
306
+ def self.call_proc(function, year) # :nodoc:
307
+ proc_key = Digest::MD5.hexdigest("#{function.to_s}_#{year.to_s}")
308
+ @@proc_cache[proc_key] = function.call(year) unless @@proc_cache[proc_key]
309
+ @@proc_cache[proc_key]
310
+ end
311
+ end
312
+
313
+ # === Extending Ruby's Date class with the Holidays gem
314
+ # The Holidays gem automatically extends Ruby's Date class and gives you access
315
+ # to three new methods: holiday?, #holidays and #calculate_mday.
316
+ #
317
+ # ==== Examples
318
+ # Lookup Canada Day in the <tt>:ca</tt> region
319
+ # Date.civil(2008,7,1).holiday?(:ca)
320
+ # => true
321
+ #
322
+ # Lookup Canada Day in the <tt>:fr</tt> region
323
+ # Date.civil(2008,7,1).holiday?(:fr)
324
+ # => false
325
+ #
326
+ # Lookup holidays on North America in January 1.
327
+ # Date.civil(2008,1,1).holidays(:ca, :mx, :us, :informal, :observed)
328
+ # => [{:name => 'New Year\'s Day'...}]
329
+ class Date
330
+ include Holidays
331
+
332
+ # Get holidays on the current date.
333
+ #
334
+ # Returns an array of hashes or nil. See Holidays#between for options
335
+ # and the output format.
336
+ #
337
+ # Date.civil('2008-01-01').holidays(:ca_)
338
+ # => [{:name => 'New Year\'s Day',...}]
339
+ #
340
+ # Also available via Holidays#on.
341
+ def holidays(*options)
342
+ Holidays.on(self, options)
343
+ end
344
+
345
+ # Check if the current date is a holiday.
346
+ #
347
+ # Returns true or false.
348
+ #
349
+ # Date.civil('2008-01-01').holiday?(:ca)
350
+ # => true
351
+ def holiday?(*options)
352
+ holidays = self.holidays(options)
353
+ holidays && !holidays.empty?
354
+ end
355
+
356
+ # Calculate day of the month based on the week number and the day of the
357
+ # week.
358
+ #
359
+ # ==== Parameters
360
+ # [<tt>year</tt>] Integer.
361
+ # [<tt>month</tt>] Integer from 1-12.
362
+ # [<tt>week</tt>] One of <tt>:first</tt>, <tt>:second</tt>, <tt>:third</tt>,
363
+ # <tt>:fourth</tt>, <tt>:fifth</tt> or <tt>:last</tt>.
364
+ # [<tt>wday</tt>] Day of the week as an integer from 0 (Sunday) to 6
365
+ # (Saturday) or as a symbol (e.g. <tt>:monday</tt>).
366
+ #
367
+ # Returns an integer.
368
+ #
369
+ # ===== Examples
370
+ # First Monday of January, 2008:
371
+ # Date.calculate_mday(2008, 1, :first, :monday)
372
+ # => 7
373
+ #
374
+ # Third Thursday of December, 2008:
375
+ # Date.calculate_mday(2008, 12, :third, :thursday)
376
+ # => 18
377
+ #
378
+ # Last Monday of January, 2008:
379
+ # Date.calculate_mday(2008, 1, :last, 1)
380
+ # => 28
381
+ #--
382
+ # see http://www.irt.org/articles/js050/index.htm
383
+ def self.calculate_mday(year, month, week, wday)
384
+ raise ArgumentError, "Week parameter must be one of Holidays::WEEKS (provided #{week})." unless WEEKS.include?(week) or WEEKS.has_value?(week)
385
+
386
+ unless wday.kind_of?(Numeric) and wday.between?(0,6) or DAY_SYMBOLS.index(wday)
387
+ raise ArgumentError, "Wday parameter must be an integer between 0 and 6 or one of Date::DAY_SYMBOLS."
388
+ end
389
+
390
+ week = WEEKS[week] if week.kind_of?(Symbol)
391
+ wday = DAY_SYMBOLS.index(wday) if wday.kind_of?(Symbol)
392
+
393
+ # :first, :second, :third, :fourth or :fifth
394
+ if week > 0
395
+ return ((week - 1) * 7) + 1 + ((7 + wday - Date.civil(year, month,(week-1)*7 + 1).wday) % 7)
396
+ end
397
+
398
+ days = MONTH_LENGTHS[month-1]
399
+
400
+ days = 29 if month == 1 and Date.civil(year,1,1).leap?
401
+
402
+ return days - ((Date.civil(year, month, days).wday - wday + 7) % 7)
403
+ end
404
+ end