bjeanes-holidays 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) 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 +111 -0
  7. data/data/build_defs.rb +152 -0
  8. data/data/ca.yaml +141 -0
  9. data/data/de.yaml +111 -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 +42 -0
  31. data/lib/holidays/ca.rb +68 -0
  32. data/lib/holidays/de.rb +50 -0
  33. data/lib/holidays/dk.rb +47 -0
  34. data/lib/holidays/es.rb +52 -0
  35. data/lib/holidays/europe.rb +214 -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 +43 -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/rakefile.rb +150 -0
  53. data/test/defs/test_defs_au.rb +36 -0
  54. data/test/defs/test_defs_ca.rb +29 -0
  55. data/test/defs/test_defs_de.rb +49 -0
  56. data/test/defs/test_defs_dk.rb +30 -0
  57. data/test/defs/test_defs_es.rb +57 -0
  58. data/test/defs/test_defs_europe.rb +243 -0
  59. data/test/defs/test_defs_fr.rb +26 -0
  60. data/test/defs/test_defs_gb.rb +36 -0
  61. data/test/defs/test_defs_ie.rb +21 -0
  62. data/test/defs/test_defs_is.rb +33 -0
  63. data/test/defs/test_defs_it.rb +25 -0
  64. data/test/defs/test_defs_mx.rb +22 -0
  65. data/test/defs/test_defs_nl.rb +24 -0
  66. data/test/defs/test_defs_north_america.rb +54 -0
  67. data/test/defs/test_defs_nyse.rb +22 -0
  68. data/test/defs/test_defs_pt.rb +56 -0
  69. data/test/defs/test_defs_scandinavia.rb +75 -0
  70. data/test/defs/test_defs_se.rb +32 -0
  71. data/test/defs/test_defs_ups.rb +21 -0
  72. data/test/defs/test_defs_us.rb +23 -0
  73. data/test/defs/test_defs_za.rb +25 -0
  74. metadata +132 -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, :second_last => -2, :third_last => -3}
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 == 2 and Date.leap?(year)
401
+
402
+ return days - ((Date.civil(year, month, days).wday - wday + 7) % 7) - (7 * (week.abs - 1))
403
+ end
404
+ end