wombleton-holidays 0.9.6
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.
- data/.gitignore +3 -0
- data/CHANGELOG +22 -0
- data/LICENSE +21 -0
- data/README.rdoc +77 -0
- data/REFERENCES +16 -0
- data/VERSION +1 -0
- data/data/SYNTAX +111 -0
- data/data/au.yaml +111 -0
- data/data/build_defs.rb +152 -0
- data/data/ca.yaml +141 -0
- data/data/cz.yaml +68 -0
- data/data/de.yaml +111 -0
- data/data/dk.yaml +117 -0
- data/data/es.yaml +161 -0
- data/data/fr.yaml +70 -0
- data/data/gb.yaml +106 -0
- data/data/ie.yaml +58 -0
- data/data/index.yaml +26 -0
- data/data/is.yaml +136 -0
- data/data/it.yaml +66 -0
- data/data/mx.yaml +106 -0
- data/data/nl.yaml +67 -0
- data/data/no.yaml +82 -0
- data/data/north_america_informal.yaml +49 -0
- data/data/nyse.yaml +63 -0
- data/data/nz.yaml +141 -0
- data/data/pt.yaml +85 -0
- data/data/se.yaml +91 -0
- data/data/united_nations.yaml +188 -0
- data/data/ups.yaml +56 -0
- data/data/us.yaml +81 -0
- data/data/za.yaml +78 -0
- data/holidays.gemspec +16 -0
- data/lib/holidays/MANIFEST +28 -0
- data/lib/holidays/au.rb +43 -0
- data/lib/holidays/ca.rb +69 -0
- data/lib/holidays/cz.rb +36 -0
- data/lib/holidays/de.rb +51 -0
- data/lib/holidays/dk.rb +48 -0
- data/lib/holidays/es.rb +53 -0
- data/lib/holidays/europe.rb +236 -0
- data/lib/holidays/fr.rb +37 -0
- data/lib/holidays/gb.rb +41 -0
- data/lib/holidays/ie.rb +33 -0
- data/lib/holidays/is.rb +62 -0
- data/lib/holidays/it.rb +36 -0
- data/lib/holidays/mx.rb +52 -0
- data/lib/holidays/nl.rb +37 -0
- data/lib/holidays/no.rb +40 -0
- data/lib/holidays/north_america.rb +108 -0
- data/lib/holidays/nyse.rb +33 -0
- data/lib/holidays/nz.rb +69 -0
- data/lib/holidays/pt.rb +38 -0
- data/lib/holidays/scandinavia.rb +115 -0
- data/lib/holidays/se.rb +53 -0
- data/lib/holidays/united_nations.rb +25 -0
- data/lib/holidays/ups.rb +32 -0
- data/lib/holidays/us.rb +49 -0
- data/lib/holidays/za.rb +36 -0
- data/lib/holidays.rb +408 -0
- data/rakefile.rb +103 -0
- data/test/defs/test_defs_au.rb +35 -0
- data/test/defs/test_defs_ca.rb +28 -0
- data/test/defs/test_defs_cz.rb +25 -0
- data/test/defs/test_defs_de.rb +48 -0
- data/test/defs/test_defs_dk.rb +29 -0
- data/test/defs/test_defs_es.rb +56 -0
- data/test/defs/test_defs_europe.rb +277 -0
- data/test/defs/test_defs_fr.rb +25 -0
- data/test/defs/test_defs_gb.rb +35 -0
- data/test/defs/test_defs_ie.rb +20 -0
- data/test/defs/test_defs_is.rb +32 -0
- data/test/defs/test_defs_it.rb +24 -0
- data/test/defs/test_defs_mx.rb +21 -0
- data/test/defs/test_defs_nl.rb +23 -0
- data/test/defs/test_defs_no.rb +28 -0
- data/test/defs/test_defs_north_america.rb +53 -0
- data/test/defs/test_defs_nyse.rb +21 -0
- data/test/defs/test_defs_nz.rb +21 -0
- data/test/defs/test_defs_pt.rb +31 -0
- data/test/defs/test_defs_scandinavia.rb +75 -0
- data/test/defs/test_defs_se.rb +32 -0
- data/test/defs/test_defs_ups.rb +20 -0
- data/test/defs/test_defs_us.rb +22 -0
- data/test/defs/test_defs_za.rb +24 -0
- data/test/test_date.rb +101 -0
- data/test/test_helper.rb +11 -0
- data/test/test_holidays.rb +117 -0
- data/test/test_multiple_regions.rb +20 -0
- metadata +172 -0
data/lib/holidays.rb
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
$:.unshift File.dirname(__FILE__)
|
|
3
|
+
|
|
4
|
+
require 'digest/md5'
|
|
5
|
+
require 'date'
|
|
6
|
+
|
|
7
|
+
# == Region options
|
|
8
|
+
# Holidays can be defined as belonging to one or more regions and sub regions.
|
|
9
|
+
# The Holidays#on, Holidays#between, Date#holidays and Date#holiday? methods
|
|
10
|
+
# each allow you to specify a specific region.
|
|
11
|
+
#
|
|
12
|
+
# There are several different ways that you can specify a region:
|
|
13
|
+
#
|
|
14
|
+
# [<tt>:region</tt>]
|
|
15
|
+
# By region. For example, return holidays in the Canada with <tt>:ca</tt>.
|
|
16
|
+
# [<tt>:region_</tt>]
|
|
17
|
+
# By region and sub regions. For example, return holidays in Germany
|
|
18
|
+
# and all its sub regions with <tt>:de_</tt>.
|
|
19
|
+
# [<tt>:region_sub</tt>]
|
|
20
|
+
# By sub region. Return national holidays in Spain plus holidays in Spain's
|
|
21
|
+
# Valencia region with <tt>:es_v</tt>.
|
|
22
|
+
# [<tt>:any</tt>]
|
|
23
|
+
# Any region. Return holidays from any loaded region.
|
|
24
|
+
#
|
|
25
|
+
# == Other options
|
|
26
|
+
# [<tt>:observed</tt>] Return holidays on the day they are observed (e.g. on a Monday if they fall on a Sunday).
|
|
27
|
+
# [<tt>:informal</tt>] Include informal holidays (e.g. Valentine's Day)
|
|
28
|
+
#
|
|
29
|
+
# == Examples
|
|
30
|
+
# Return all holidays in the <tt>:ca</tt> and <tt>:us</tt> regions on the day that they are
|
|
31
|
+
# observed.
|
|
32
|
+
#
|
|
33
|
+
# Holidays.between(from, to, :ca, :us, :observed)
|
|
34
|
+
#
|
|
35
|
+
# Return all holidays in <tt>:ca</tt> and any <tt>:ca</tt> sub-region.
|
|
36
|
+
#
|
|
37
|
+
# Holidays.between(from, to, :ca_)
|
|
38
|
+
#
|
|
39
|
+
# Return all holidays in <tt>:ca_bc</tt> sub-region (which includes the <tt>:ca</tt>), including informal holidays.
|
|
40
|
+
#
|
|
41
|
+
# Holidays.between(from, to, :ca_bc, :informal)
|
|
42
|
+
module Holidays
|
|
43
|
+
# Exception thrown when an unknown region is requested.
|
|
44
|
+
class UnknownRegionError < ArgumentError; end
|
|
45
|
+
|
|
46
|
+
VERSION = '0.9.2'
|
|
47
|
+
|
|
48
|
+
@@regions = []
|
|
49
|
+
@@holidays_by_month = {}
|
|
50
|
+
@@proc_cache = {}
|
|
51
|
+
|
|
52
|
+
WEEKS = {:first => 1, :second => 2, :third => 3, :fourth => 4, :fifth => 5, :last => -1, :second_last => -2, :third_last => -3}
|
|
53
|
+
MONTH_LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
54
|
+
DAY_SYMBOLS = Date::DAYNAMES.collect { |n| n.downcase.intern }
|
|
55
|
+
|
|
56
|
+
# Get all holidays on a given date.
|
|
57
|
+
#
|
|
58
|
+
# [<tt>date</tt>] A Date object.
|
|
59
|
+
# [<tt>:options</tt>] One or more region symbols, <tt>:informal</tt> and/or <tt>:observed</tt>.
|
|
60
|
+
#
|
|
61
|
+
# Returns an array of hashes or nil. See Holidays#between for the output
|
|
62
|
+
# format.
|
|
63
|
+
#
|
|
64
|
+
# Also available via Date#holidays.
|
|
65
|
+
def self.on(date, *options)
|
|
66
|
+
self.between(date, date, options)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get all holidays occuring between two dates, inclusively.
|
|
70
|
+
#
|
|
71
|
+
# Returns an array of hashes or nil.
|
|
72
|
+
#
|
|
73
|
+
# Each holiday is returned as a hash with the following fields:
|
|
74
|
+
# [<tt>start_date</tt>] Ruby Date object.
|
|
75
|
+
# [<tt>end_date</tt>] Ruby Date object.
|
|
76
|
+
# [<tt>options</tt>] One or more region symbols, <tt>:informal</tt> and/or <tt>:observed</tt>.
|
|
77
|
+
#
|
|
78
|
+
# ==== Example
|
|
79
|
+
# from = Date.civil(2008,7,1)
|
|
80
|
+
# to = Date.civil(2008,7,31)
|
|
81
|
+
#
|
|
82
|
+
# Holidays.between(from, to, :ca, :us)
|
|
83
|
+
# => [{:name => 'Canada Day', :regions => [:ca]...}
|
|
84
|
+
# {:name => 'Independence Day'', :regions => [:us], ...}]
|
|
85
|
+
def self.between(start_date, end_date, *options)
|
|
86
|
+
start_date = start_date.to_date if start_date.respond_to?(:to_date)
|
|
87
|
+
end_date = end_date.to_date if end_date.respond_to?(:to_date)
|
|
88
|
+
regions, observed, informal = parse_options(options)
|
|
89
|
+
holidays = []
|
|
90
|
+
|
|
91
|
+
dates = {}
|
|
92
|
+
(start_date..end_date).each do |date|
|
|
93
|
+
# Always include month '0' for variable-month holidays
|
|
94
|
+
dates[date.year] = [0] unless dates[date.year]
|
|
95
|
+
# TODO: test this, maybe should push then flatten
|
|
96
|
+
dates[date.year] << date.month unless dates[date.year].include?(date.month)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
dates.each do |year, months|
|
|
100
|
+
months.each do |month|
|
|
101
|
+
next unless hbm = @@holidays_by_month[month]
|
|
102
|
+
|
|
103
|
+
hbm.each do |h|
|
|
104
|
+
next unless in_region?(regions, h[:regions])
|
|
105
|
+
|
|
106
|
+
# Skip informal holidays unless they have been requested
|
|
107
|
+
next if h[:type] == :informal and not informal
|
|
108
|
+
|
|
109
|
+
if h[:function]
|
|
110
|
+
# Holiday definition requires a calculation
|
|
111
|
+
result = call_proc(h[:function], year)
|
|
112
|
+
|
|
113
|
+
# Procs may return either Date or an integer representing mday
|
|
114
|
+
if result.kind_of?(Date)
|
|
115
|
+
month = result.month
|
|
116
|
+
mday = result.mday
|
|
117
|
+
else
|
|
118
|
+
mday = result
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
# Calculate the mday
|
|
122
|
+
mday = h[:mday] || Date.calculate_mday(year, month, h[:week], h[:wday])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Silently skip bad mdays
|
|
126
|
+
begin
|
|
127
|
+
date = Date.civil(year, month, mday)
|
|
128
|
+
rescue; next; end
|
|
129
|
+
|
|
130
|
+
# If the :observed option is set, calculate the date when the holiday
|
|
131
|
+
# is observed.
|
|
132
|
+
if observed and h[:observed]
|
|
133
|
+
date = call_proc(h[:observed], date)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if date.between?(start_date, end_date)
|
|
137
|
+
holidays << {:date => date, :name => h[:name], :regions => h[:regions]}
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
holidays
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Merge a new set of definitions into the Holidays module.
|
|
148
|
+
#
|
|
149
|
+
# This method is automatically called when including holiday definition
|
|
150
|
+
# files.
|
|
151
|
+
def self.merge_defs(regions, holidays) # :nodoc:
|
|
152
|
+
@@regions = @@regions | regions
|
|
153
|
+
@@regions.uniq!
|
|
154
|
+
|
|
155
|
+
holidays.each do |month, holiday_defs|
|
|
156
|
+
@@holidays_by_month[month] = [] unless @@holidays_by_month[month]
|
|
157
|
+
holiday_defs.each do |holiday_def|
|
|
158
|
+
|
|
159
|
+
exists = false
|
|
160
|
+
@@holidays_by_month[month].each do |ex|
|
|
161
|
+
# TODO: gross.
|
|
162
|
+
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]
|
|
163
|
+
# append regions
|
|
164
|
+
ex[:regions] << holiday_def[:regions]
|
|
165
|
+
|
|
166
|
+
# Should do this once we're done
|
|
167
|
+
ex[:regions].flatten!
|
|
168
|
+
ex[:regions].uniq!
|
|
169
|
+
exists = true
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@@holidays_by_month[month] << holiday_def unless exists
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Get the date of Easter Sunday in a given year. From Easter Sunday, it is
|
|
179
|
+
# possible to calculate many traditional holidays in Western countries.
|
|
180
|
+
# Returns a Date object.
|
|
181
|
+
def self.easter(year)
|
|
182
|
+
y = year
|
|
183
|
+
a = y % 19
|
|
184
|
+
b = y / 100
|
|
185
|
+
c = y % 100
|
|
186
|
+
d = b / 4
|
|
187
|
+
e = b % 4
|
|
188
|
+
f = (b + 8) / 25
|
|
189
|
+
g = (b - f + 1) / 3
|
|
190
|
+
h = (19 * a + b - d - g + 15) % 30
|
|
191
|
+
i = c / 4
|
|
192
|
+
k = c % 4
|
|
193
|
+
l = (32 + 2 * e + 2 * i - h - k) % 7
|
|
194
|
+
m = (a + 11 * h + 22 * l) / 451
|
|
195
|
+
month = (h + l - 7 * m + 114) / 31
|
|
196
|
+
day = ((h + l - 7 * m + 114) % 31) + 1
|
|
197
|
+
Date.civil(year, month, day)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Move date to Monday if it occurs on a Sunday.
|
|
201
|
+
# Used as a callback function.
|
|
202
|
+
def self.to_monday_if_sunday(date)
|
|
203
|
+
date += 1 if date.wday == 0
|
|
204
|
+
date
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Move date to Monday if it occurs on a Saturday on Sunday.
|
|
208
|
+
# Used as a callback function.
|
|
209
|
+
def self.to_monday_if_weekend(date)
|
|
210
|
+
date += 1 if date.wday == 0
|
|
211
|
+
date += 2 if date.wday == 6
|
|
212
|
+
date
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Move Boxing Day if it falls on a weekend, leaving room for Christmas.
|
|
216
|
+
# Used as a callback function.
|
|
217
|
+
def self.to_weekday_if_boxing_weekend(date)
|
|
218
|
+
date += 2 if date.wday == 6 or date.wday == 0
|
|
219
|
+
date
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Move date to Monday if it occurs on a Sunday or to Friday if it occurs on a
|
|
223
|
+
# Saturday.
|
|
224
|
+
# Used as a callback function.
|
|
225
|
+
def self.to_weekday_if_weekend(date)
|
|
226
|
+
date += 1 if date.wday == 0
|
|
227
|
+
date -= 1 if date.wday == 6
|
|
228
|
+
date
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
# Returns [(arr)regions, (bool)observed, (bool)informal]
|
|
233
|
+
def self.parse_options(*options) # :nodoc:
|
|
234
|
+
options.flatten!
|
|
235
|
+
observed = options.delete(:observed) ? true : false
|
|
236
|
+
informal = options.delete(:informal) ? true : false
|
|
237
|
+
regions = parse_regions(options)
|
|
238
|
+
return regions, observed, informal
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Check regions against list of supported regions and return an array of
|
|
242
|
+
# symbols.
|
|
243
|
+
#
|
|
244
|
+
# If a wildcard region is found (e.g. <tt>:ca_</tt>) it is expanded into all
|
|
245
|
+
# of its available sub regions.
|
|
246
|
+
def self.parse_regions(regions) # :nodoc:
|
|
247
|
+
regions = [regions] unless regions.kind_of?(Array)
|
|
248
|
+
return [:any] if regions.empty?
|
|
249
|
+
|
|
250
|
+
regions = regions.collect { |r| r.to_sym }
|
|
251
|
+
|
|
252
|
+
# Found sub region wild-card
|
|
253
|
+
regions.delete_if do |reg|
|
|
254
|
+
if reg.to_s =~ /_$/
|
|
255
|
+
regions << @@regions.select { |dr| dr.to_s =~ Regexp.new("^#{reg}") }
|
|
256
|
+
true
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
regions.flatten!
|
|
261
|
+
|
|
262
|
+
require "holidays/north_america" if regions.include?(:us) # special case for north_america/US cross-linking
|
|
263
|
+
|
|
264
|
+
raise UnknownRegionError unless regions.all? { |r| r == :any or @@regions.include?(r) or begin require "holidays/#{r.to_s}"; rescue LoadError; false; end }
|
|
265
|
+
regions
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Check sub regions.
|
|
269
|
+
#
|
|
270
|
+
# When request :any, all holidays should be returned.
|
|
271
|
+
# When requesting :ca_bc, holidays in :ca or :ca_bc should be returned.
|
|
272
|
+
# When requesting :ca, holidays in :ca but not its subregions should be returned.
|
|
273
|
+
def self.in_region?(requested, available) # :nodoc:
|
|
274
|
+
return true if requested.include?(:any)
|
|
275
|
+
|
|
276
|
+
# When an underscore is encountered, derive the parent regions
|
|
277
|
+
# symbol and include both in the requested array.
|
|
278
|
+
requested = requested.collect do |r|
|
|
279
|
+
r.to_s =~ /_/ ? [r, r.to_s.gsub(/_[\w]*$/, '').to_sym] : r
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
requested = requested.flatten.uniq
|
|
283
|
+
|
|
284
|
+
available.any? { |avail| requested.include?(avail) }
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Call a proc function defined in a holiday definition file.
|
|
288
|
+
#
|
|
289
|
+
# Procs are cached.
|
|
290
|
+
#
|
|
291
|
+
# ==== Benchmarks
|
|
292
|
+
#
|
|
293
|
+
# Lookup Easter Sunday, with caching, by number of iterations:
|
|
294
|
+
#
|
|
295
|
+
# user system total real
|
|
296
|
+
# 0001 0.000000 0.000000 0.000000 ( 0.000000)
|
|
297
|
+
# 0010 0.000000 0.000000 0.000000 ( 0.000000)
|
|
298
|
+
# 0100 0.078000 0.000000 0.078000 ( 0.078000)
|
|
299
|
+
# 1000 0.641000 0.000000 0.641000 ( 0.641000)
|
|
300
|
+
# 5000 3.172000 0.015000 3.187000 ( 3.219000)
|
|
301
|
+
#
|
|
302
|
+
# Lookup Easter Sunday, without caching, by number of iterations:
|
|
303
|
+
#
|
|
304
|
+
# user system total real
|
|
305
|
+
# 0001 0.000000 0.000000 0.000000 ( 0.000000)
|
|
306
|
+
# 0010 0.016000 0.000000 0.016000 ( 0.016000)
|
|
307
|
+
# 0100 0.125000 0.000000 0.125000 ( 0.125000)
|
|
308
|
+
# 1000 1.234000 0.000000 1.234000 ( 1.234000)
|
|
309
|
+
# 5000 6.094000 0.031000 6.125000 ( 6.141000)
|
|
310
|
+
def self.call_proc(function, year) # :nodoc:
|
|
311
|
+
proc_key = Digest::MD5.hexdigest("#{function.to_s}_#{year.to_s}")
|
|
312
|
+
@@proc_cache[proc_key] = function.call(year) unless @@proc_cache[proc_key]
|
|
313
|
+
@@proc_cache[proc_key]
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# === Extending Ruby's Date class with the Holidays gem
|
|
318
|
+
# The Holidays gem automatically extends Ruby's Date class and gives you access
|
|
319
|
+
# to three new methods: holiday?, #holidays and #calculate_mday.
|
|
320
|
+
#
|
|
321
|
+
# ==== Examples
|
|
322
|
+
# Lookup Canada Day in the <tt>:ca</tt> region
|
|
323
|
+
# Date.civil(2008,7,1).holiday?(:ca)
|
|
324
|
+
# => true
|
|
325
|
+
#
|
|
326
|
+
# Lookup Canada Day in the <tt>:fr</tt> region
|
|
327
|
+
# Date.civil(2008,7,1).holiday?(:fr)
|
|
328
|
+
# => false
|
|
329
|
+
#
|
|
330
|
+
# Lookup holidays on North America in January 1.
|
|
331
|
+
# Date.civil(2008,1,1).holidays(:ca, :mx, :us, :informal, :observed)
|
|
332
|
+
# => [{:name => 'New Year\'s Day'...}]
|
|
333
|
+
class Date
|
|
334
|
+
include Holidays
|
|
335
|
+
|
|
336
|
+
# Get holidays on the current date.
|
|
337
|
+
#
|
|
338
|
+
# Returns an array of hashes or nil. See Holidays#between for options
|
|
339
|
+
# and the output format.
|
|
340
|
+
#
|
|
341
|
+
# Date.civil('2008-01-01').holidays(:ca_)
|
|
342
|
+
# => [{:name => 'New Year\'s Day',...}]
|
|
343
|
+
#
|
|
344
|
+
# Also available via Holidays#on.
|
|
345
|
+
def holidays(*options)
|
|
346
|
+
Holidays.on(self, options)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Check if the current date is a holiday.
|
|
350
|
+
#
|
|
351
|
+
# Returns true or false.
|
|
352
|
+
#
|
|
353
|
+
# Date.civil('2008-01-01').holiday?(:ca)
|
|
354
|
+
# => true
|
|
355
|
+
def holiday?(*options)
|
|
356
|
+
holidays = self.holidays(options)
|
|
357
|
+
holidays && !holidays.empty?
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Calculate day of the month based on the week number and the day of the
|
|
361
|
+
# week.
|
|
362
|
+
#
|
|
363
|
+
# ==== Parameters
|
|
364
|
+
# [<tt>year</tt>] Integer.
|
|
365
|
+
# [<tt>month</tt>] Integer from 1-12.
|
|
366
|
+
# [<tt>week</tt>] One of <tt>:first</tt>, <tt>:second</tt>, <tt>:third</tt>,
|
|
367
|
+
# <tt>:fourth</tt>, <tt>:fifth</tt> or <tt>:last</tt>.
|
|
368
|
+
# [<tt>wday</tt>] Day of the week as an integer from 0 (Sunday) to 6
|
|
369
|
+
# (Saturday) or as a symbol (e.g. <tt>:monday</tt>).
|
|
370
|
+
#
|
|
371
|
+
# Returns an integer.
|
|
372
|
+
#
|
|
373
|
+
# ===== Examples
|
|
374
|
+
# First Monday of January, 2008:
|
|
375
|
+
# Date.calculate_mday(2008, 1, :first, :monday)
|
|
376
|
+
# => 7
|
|
377
|
+
#
|
|
378
|
+
# Third Thursday of December, 2008:
|
|
379
|
+
# Date.calculate_mday(2008, 12, :third, :thursday)
|
|
380
|
+
# => 18
|
|
381
|
+
#
|
|
382
|
+
# Last Monday of January, 2008:
|
|
383
|
+
# Date.calculate_mday(2008, 1, :last, 1)
|
|
384
|
+
# => 28
|
|
385
|
+
#--
|
|
386
|
+
# see http://www.irt.org/articles/js050/index.htm
|
|
387
|
+
def self.calculate_mday(year, month, week, wday)
|
|
388
|
+
raise ArgumentError, "Week parameter must be one of Holidays::WEEKS (provided #{week})." unless WEEKS.include?(week) or WEEKS.has_value?(week)
|
|
389
|
+
|
|
390
|
+
unless wday.kind_of?(Numeric) and wday.between?(0,6) or DAY_SYMBOLS.index(wday)
|
|
391
|
+
raise ArgumentError, "Wday parameter must be an integer between 0 and 6 or one of Date::DAY_SYMBOLS."
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
week = WEEKS[week] if week.kind_of?(Symbol)
|
|
395
|
+
wday = DAY_SYMBOLS.index(wday) if wday.kind_of?(Symbol)
|
|
396
|
+
|
|
397
|
+
# :first, :second, :third, :fourth or :fifth
|
|
398
|
+
if week > 0
|
|
399
|
+
return ((week - 1) * 7) + 1 + ((7 + wday - Date.civil(year, month,(week-1)*7 + 1).wday) % 7)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
days = MONTH_LENGTHS[month-1]
|
|
403
|
+
|
|
404
|
+
days = 29 if month == 2 and Date.leap?(year)
|
|
405
|
+
|
|
406
|
+
return days - ((Date.civil(year, month, days).wday - wday + 7) % 7) - (7 * (week.abs - 1))
|
|
407
|
+
end
|
|
408
|
+
end
|
data/rakefile.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'rake/rdoctask'
|
|
4
|
+
require 'rake/gempackagetask'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'lib/holidays'
|
|
8
|
+
require 'data/build_defs'
|
|
9
|
+
|
|
10
|
+
desc 'Run all tests'
|
|
11
|
+
task :test => ["test:lib", "test:defs"]
|
|
12
|
+
|
|
13
|
+
namespace :test do
|
|
14
|
+
desc 'Run the unit tests.'
|
|
15
|
+
Rake::TestTask.new(:defs) do |t|
|
|
16
|
+
t.libs << 'lib'
|
|
17
|
+
t.test_files = FileList['test/defs/test*.rb'].exclude('test_helper.rb')
|
|
18
|
+
t.verbose = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
desc 'Run the definition tests.'
|
|
22
|
+
Rake::TestTask.new(:lib) do |t|
|
|
23
|
+
t.libs << 'lib'
|
|
24
|
+
t.test_files = FileList['test/test*.rb'].exclude('test_helper.rb')
|
|
25
|
+
t.verbose = false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
task :doc => ["defs:manifest", :rdoc]
|
|
30
|
+
|
|
31
|
+
desc 'Generate documentation.'
|
|
32
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
33
|
+
rdoc.rdoc_dir = 'doc'
|
|
34
|
+
rdoc.title = 'Ruby Holidays Gem'
|
|
35
|
+
rdoc.options << '--all' << '--inline-source' << '--line-numbers'
|
|
36
|
+
rdoc.options << '--charset' << 'utf-8'
|
|
37
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
38
|
+
rdoc.rdoc_files.include('data/SYNTAX')
|
|
39
|
+
rdoc.rdoc_files.include('lib/holidays/MANIFEST')
|
|
40
|
+
rdoc.rdoc_files.include('REFERENCES')
|
|
41
|
+
rdoc.rdoc_files.include('CHANGELOG')
|
|
42
|
+
rdoc.rdoc_files.include('LICENSE')
|
|
43
|
+
rdoc.rdoc_files.include('lib/*.rb')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc 'Definition file tasks'
|
|
47
|
+
namespace :defs do
|
|
48
|
+
DATA_PATH = 'data'
|
|
49
|
+
|
|
50
|
+
desc 'Build holiday definition files'
|
|
51
|
+
task :build_all do
|
|
52
|
+
# load the index
|
|
53
|
+
def_index = YAML.load_file("#{DATA_PATH}/index.yaml")
|
|
54
|
+
|
|
55
|
+
# create a dir for the generated tests
|
|
56
|
+
FileUtils.mkdir_p('test/defs')
|
|
57
|
+
|
|
58
|
+
def_index['defs'].each do |region, files|
|
|
59
|
+
puts "Building #{region} definition module:"
|
|
60
|
+
files = files.collect { |f| "#{DATA_PATH}/#{f}" }
|
|
61
|
+
files.uniq!
|
|
62
|
+
|
|
63
|
+
module_src, test_src = parse_holiday_defs(region, files)
|
|
64
|
+
File.open("lib/holidays/#{region.downcase.to_s}.rb","w") do |file|
|
|
65
|
+
file.puts module_src
|
|
66
|
+
end
|
|
67
|
+
unless test_src.empty?
|
|
68
|
+
File.open("test/defs/test_defs_#{region.downcase.to_s}.rb","w") do |file|
|
|
69
|
+
file.puts test_src
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
puts "Done.\n\n"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
desc 'Build the definition manifest.'
|
|
77
|
+
task :manifest do
|
|
78
|
+
File.open("lib/holidays/MANIFEST","w") do |file|
|
|
79
|
+
file.puts <<-EOH
|
|
80
|
+
==== Regional definitions
|
|
81
|
+
The following definition files are included in this installation:
|
|
82
|
+
|
|
83
|
+
EOH
|
|
84
|
+
FileList.new('lib/holidays/*.rb').each do |str|
|
|
85
|
+
file.puts('* ' + str.gsub(/^lib\/|\.rb$/, ''))
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
require 'jeweler'
|
|
93
|
+
Jeweler::Tasks.new do |gemspec|
|
|
94
|
+
gemspec.name = "wombleton-holidays"
|
|
95
|
+
gemspec.summary = "A collection of Ruby methods to deal with statutory and other holidays. You deserve a holiday!"
|
|
96
|
+
gemspec.description = "A collection of Ruby methods to deal with statutory and other holidays. You deserve a holiday!"
|
|
97
|
+
gemspec.email = "wombleton@gmail.com"
|
|
98
|
+
gemspec.homepage = "http://github.com/wombleton/holidays"
|
|
99
|
+
gemspec.authors = ["Alex Dunae", "Rowan Crawford"]
|
|
100
|
+
end
|
|
101
|
+
rescue LoadError
|
|
102
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
|
103
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
# This file is generated by the Ruby Holiday gem.
|
|
4
|
+
#
|
|
5
|
+
# Definitions loaded: data/au.yaml
|
|
6
|
+
class AuDefinitionTests < Test::Unit::TestCase # :nodoc:
|
|
7
|
+
|
|
8
|
+
def test_au
|
|
9
|
+
{Date.civil(2007,1,1) => 'New Year\'s Day',
|
|
10
|
+
Date.civil(2007,1,26) => 'Australia Day',
|
|
11
|
+
Date.civil(2007,4,6) => 'Good Friday',
|
|
12
|
+
Date.civil(2007,4,9) => 'Easter Monday',
|
|
13
|
+
Date.civil(2007,4,25) => 'ANZAC Day',
|
|
14
|
+
Date.civil(2007,12,25) => 'Christmas Day',
|
|
15
|
+
Date.civil(2007,12,26) => 'Boxing Day'}.each do |date, name|
|
|
16
|
+
assert_equal name, (Holidays.on(date, :au, :informal)[0] || {})[:name]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
[:au_sa, :au_act, :au_nsw, :au_].each do |r|
|
|
20
|
+
assert_equal 'Labour Day', Date.civil(2007,10,1).holidays(r)[0][:name]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
[:au_sa, :au_act, :au_nsw, :au_vic, :au_tas, :au_qld, :au_nt, :au_].each do |r|
|
|
24
|
+
assert_equal 'Queen\'s Birthday', Date.civil(2007,6,11).holidays(r)[0][:name]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
assert_equal 'Labour Day', Date.civil(2007,3,5).holidays(:au_wa)[0][:name]
|
|
28
|
+
assert_equal 'Labour Day', Date.civil(2007,3,12).holidays(:au_vic)[0][:name]
|
|
29
|
+
assert_equal 'Labour Day', Date.civil(2007,5,7).holidays(:au_qld)[0][:name]
|
|
30
|
+
|
|
31
|
+
assert_equal 'May Day', Date.civil(2007,5,7).holidays(:au_nt)[0][:name]
|
|
32
|
+
|
|
33
|
+
assert_equal 'Eight Hours Day', Date.civil(2007,3,12).holidays(:au_tas)[0][:name]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
# This file is generated by the Ruby Holiday gem.
|
|
4
|
+
#
|
|
5
|
+
# Definitions loaded: data/ca.yaml, data/north_america_informal.yaml
|
|
6
|
+
class CaDefinitionTests < Test::Unit::TestCase # :nodoc:
|
|
7
|
+
|
|
8
|
+
def test_ca
|
|
9
|
+
{Date.civil(2008,1,1) => 'New Year\'s Day',
|
|
10
|
+
Date.civil(2008,3,21) => 'Good Friday',
|
|
11
|
+
Date.civil(2008,3,24) => 'Easter Monday',
|
|
12
|
+
Date.civil(2008,5,19) => 'Victoria Day',
|
|
13
|
+
Date.civil(2008,7,1) => 'Canada Day',
|
|
14
|
+
Date.civil(2008,9,1) => 'Labour Day',
|
|
15
|
+
Date.civil(2008,10,13) => 'Thanksgiving',
|
|
16
|
+
Date.civil(2008,11,11) => 'Rememberance Day',
|
|
17
|
+
Date.civil(2008,12,25) => 'Christmas Day',
|
|
18
|
+
Date.civil(2008,12,26) => 'Boxing Day'}.each do |date, name|
|
|
19
|
+
assert_equal name, (Holidays.on(date, :ca, :informal)[0] || {})[:name]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Victoria Day
|
|
23
|
+
[Date.civil(2004,5,24), Date.civil(2005,5,23), Date.civil(2006,5,22),
|
|
24
|
+
Date.civil(2007,5,21), Date.civil(2008,5,19)].each do |date|
|
|
25
|
+
assert_equal 'Victoria Day', Holidays.on(date, :ca)[0][:name]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
# This file is generated by the Ruby Holiday gem.
|
|
4
|
+
#
|
|
5
|
+
# Definitions loaded: data/cz.yaml
|
|
6
|
+
class CzDefinitionTests < Test::Unit::TestCase # :nodoc:
|
|
7
|
+
|
|
8
|
+
def test_cz
|
|
9
|
+
{Date.civil(2010,1,1) => 'Den obnovy samostatného českého státu',
|
|
10
|
+
Date.civil(2010,4,5) => 'Velikonoční pondělí',
|
|
11
|
+
Date.civil(2010,5,1) => 'Svátek práce',
|
|
12
|
+
Date.civil(2010,5,8) => 'Den vítězství',
|
|
13
|
+
Date.civil(2010,7,5) => 'Den slovanských věrozvěstů Cyrila a Metoděje',
|
|
14
|
+
Date.civil(2010,7,6) => 'Den upálení mistra Jana Husa',
|
|
15
|
+
Date.civil(2010,9,28) => 'Den české státnosti',
|
|
16
|
+
Date.civil(2010,10,28) => 'Den vzniku samostatného československého státu',
|
|
17
|
+
Date.civil(2010,11,17) => 'Den boje za svobodu a demokracii',
|
|
18
|
+
Date.civil(2010,12,24) => 'Štědrý den',
|
|
19
|
+
Date.civil(2010,12,25) => '1. svátek vánoční',
|
|
20
|
+
Date.civil(2010,12,26) => '2. svátek vánoční'}.each do |date, name|
|
|
21
|
+
assert_equal name, (Holidays.on(date, :cz, :informal)[0] || {})[:name]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
# This file is generated by the Ruby Holiday gem.
|
|
4
|
+
#
|
|
5
|
+
# Definitions loaded: data/de.yaml
|
|
6
|
+
class DeDefinitionTests < Test::Unit::TestCase # :nodoc:
|
|
7
|
+
|
|
8
|
+
def test_de
|
|
9
|
+
{Date.civil(2009,1,1) => 'Neujahrstag',
|
|
10
|
+
Date.civil(2009,4,10) => 'Karfreitag',
|
|
11
|
+
Date.civil(2009,4,13) => 'Ostermontag',
|
|
12
|
+
Date.civil(2009,5,1) => 'Tag der Arbeit',
|
|
13
|
+
Date.civil(2009,5,21) => 'Christi Himmelfahrt',
|
|
14
|
+
Date.civil(2009,6,1) => 'Pfingstmontag',
|
|
15
|
+
Date.civil(2009,10,3) => 'Tag der Deutschen Einheit',
|
|
16
|
+
Date.civil(2009,12,25) => '1. Weihnachtstag',
|
|
17
|
+
Date.civil(2009,12,26) => '2. Weihnachtstag'}.each do |date, name|
|
|
18
|
+
assert_equal name, (Holidays.on(date, :de, :informal)[0] || {})[:name]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
[:de_bw, :de_by, :de_st, :de_].each do |r|
|
|
22
|
+
assert_equal 'Heilige Drei Könige', Date.civil(2009,1,6).holidays(r)[0][:name]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
[:de_bw, :de_by, :de_he, :de_nw, :de_rp, :de_sl, :de_].each do |r|
|
|
26
|
+
assert_equal 'Fronleichnam', Date.civil(2009,6,11).holidays(r)[0][:name]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
[:de_by, :de_sl, :de_].each do |r|
|
|
30
|
+
assert_equal 'Mariä Himmelfahrt', Date.civil(2009,8,15).holidays(r)[0][:name]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
[:de_bb, :de_mv, :de_sn, :de_st, :de_th, :de_].each do |r|
|
|
34
|
+
assert_equal 'Reformationstag', Date.civil(2009,10,31).holidays(r)[0][:name]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
[:de_bw, :de_by, :de_nw, :de_rp, :de_sl, :de_].each do |r|
|
|
38
|
+
assert_equal 'Allerheiligen', Date.civil(2009,11,1).holidays(r)[0][:name]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Repentance Day
|
|
42
|
+
assert_equal 'Buß- und Bettag', Date.civil(2004,11,17).holidays(:de_sn)[0][:name]
|
|
43
|
+
assert_equal 'Buß- und Bettag', Date.civil(2005,11,16).holidays(:de_sn)[0][:name]
|
|
44
|
+
assert_equal 'Buß- und Bettag', Date.civil(2006,11,22).holidays(:de_sn)[0][:name]
|
|
45
|
+
assert_equal 'Buß- und Bettag', Date.civil(2009,11,18).holidays(:de_sn)[0][:name]
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
# This file is generated by the Ruby Holiday gem.
|
|
4
|
+
#
|
|
5
|
+
# Definitions loaded: data/dk.yaml
|
|
6
|
+
class DkDefinitionTests < Test::Unit::TestCase # :nodoc:
|
|
7
|
+
|
|
8
|
+
def test_dk
|
|
9
|
+
{Date.civil(2007,1,1) => 'Nytårsdag',
|
|
10
|
+
Date.civil(2007,2,18) => 'Fastelavn',
|
|
11
|
+
Date.civil(2007,4,9) => 'Danmarks besættelse',
|
|
12
|
+
Date.civil(2007,4,16) => 'Dronningens fødselsdag',
|
|
13
|
+
Date.civil(2007,4,5) => 'Skærtorsdag',
|
|
14
|
+
Date.civil(2007,4,6) => 'Langfredag',
|
|
15
|
+
Date.civil(2007,4,8) => 'Påskedag',
|
|
16
|
+
Date.civil(2007,4,9) => '2. påskedag',
|
|
17
|
+
Date.civil(2007,5,1) => 'Arbejdernes kampdag',
|
|
18
|
+
Date.civil(2007,5,4) => 'Store Bededag',
|
|
19
|
+
Date.civil(2007,5,17) => 'Kristi Himmelfartsdag',
|
|
20
|
+
Date.civil(2007,5,27) => 'Pinsedag',
|
|
21
|
+
Date.civil(2007,5,28) => '2. Pinsedag',
|
|
22
|
+
Date.civil(2007,6,5) => 'Grundlovsdag',
|
|
23
|
+
Date.civil(2007,12,24) => 'Juleaftensdag',
|
|
24
|
+
Date.civil(2007,12,25) => '1. juledag',
|
|
25
|
+
Date.civil(2007,12,26) => '2. juledag'}.each do |date, name|
|
|
26
|
+
assert_equal name, (Holidays.on(date, :dk, :informal)[0] || {})[:name]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|