fat_core 2.0.1 → 3.0.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/bin/console +14 -0
- data/fat_core.gemspec +1 -0
- data/lib/fat_core/all.rb +14 -0
- data/lib/fat_core/array.rb +29 -22
- data/lib/fat_core/big_decimal.rb +12 -0
- data/lib/fat_core/date.rb +951 -938
- data/lib/fat_core/enumerable.rb +19 -3
- data/lib/fat_core/hash.rb +48 -43
- data/lib/fat_core/kernel.rb +4 -2
- data/lib/fat_core/nil.rb +13 -13
- data/lib/fat_core/numeric.rb +82 -86
- data/lib/fat_core/range.rb +164 -160
- data/lib/fat_core/string.rb +247 -242
- data/lib/fat_core/symbol.rb +17 -11
- data/lib/fat_core/version.rb +2 -2
- data/lib/fat_core.rb +0 -21
- data/spec/lib/array_spec.rb +2 -0
- data/spec/lib/big_decimal_spec.rb +7 -0
- data/spec/lib/date_spec.rb +7 -26
- data/spec/lib/enumerable_spec.rb +26 -1
- data/spec/lib/hash_spec.rb +2 -0
- data/spec/lib/kernel_spec.rb +2 -0
- data/spec/lib/nil_spec.rb +6 -0
- data/spec/lib/numeric_spec.rb +1 -5
- data/spec/lib/range_spec.rb +1 -1
- data/spec/lib/string_spec.rb +1 -6
- data/spec/lib/symbol_spec.rb +1 -1
- data/spec/spec_helper.rb +0 -2
- metadata +9 -8
- data/lib/fat_core/boolean.rb +0 -25
- data/lib/fat_core/latex_eruby.rb +0 -11
- data/lib/fat_core/period.rb +0 -533
- data/spec/lib/period_spec.rb +0 -677
data/lib/fat_core/date.rb
CHANGED
@@ -1,1033 +1,1046 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# Date.parse_american('9/11/2001') #=> Date(2011, 9, 11)
|
20
|
-
# Date.parse_american('9/11/01') #=> Date(2011, 9, 11)
|
21
|
-
# Date.parse_american('9/11/1') #=> ArgumentError
|
22
|
-
#
|
23
|
-
# @param str [#to_s] a stringling of the form MM/DD/YYYY
|
24
|
-
# @return [Date] the date represented by the string paramenter.
|
25
|
-
def self.parse_american(str)
|
26
|
-
unless str.to_s =~ %r{\A\s*(\d\d?)\s*/\s*(\d\d?)\s*/\s*(\d?\d?\d\d)\s*\z}
|
27
|
-
raise ArgumentError, "date string must be of form 'MM?/DD?/YY(YY)?'"
|
1
|
+
require 'date'
|
2
|
+
require 'active_support/core_ext/date'
|
3
|
+
require 'active_support/core_ext/time'
|
4
|
+
require 'active_support/core_ext/numeric/time'
|
5
|
+
require 'active_support/core_ext/integer/time'
|
6
|
+
require 'fat_core/string'
|
7
|
+
|
8
|
+
module FatCore
|
9
|
+
module Date
|
10
|
+
# Constants for Begining of Time (BOT) and End of Time (EOT)
|
11
|
+
# Both outside the range of what we would find in an accounting app.
|
12
|
+
::Date::BOT = ::Date.parse('1900-01-01')
|
13
|
+
::Date::EOT = ::Date.parse('3000-12-31')
|
14
|
+
|
15
|
+
# Predecessor of self. Allows Date to work as a countable element
|
16
|
+
# of a Range.
|
17
|
+
def pred
|
18
|
+
self - 1.day
|
28
19
|
end
|
29
|
-
year = $3.to_i
|
30
|
-
month = $1.to_i
|
31
|
-
day = $2.to_i
|
32
|
-
year += 2000 if year < 100
|
33
|
-
Date.new(year, month, day)
|
34
|
-
end
|
35
20
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
#
|
41
|
-
# Assuming that Date.current at the time of execution is 2014-07-26 and
|
42
|
-
# using the default spec_type of :from. The return values are actually Date
|
43
|
-
# objects, but are shown below as textual dates.
|
44
|
-
#
|
45
|
-
# A fully specified date returns that date:
|
46
|
-
# Date.parse_spec('2001-09-11') # =>
|
47
|
-
# Commercial weeks can be specified using, for example W32 or 32W, with the
|
48
|
-
# week beginning on Monday, ending on Sunday.
|
49
|
-
# Date.parse_spec('2012-W32') # =>
|
50
|
-
# Date.parse_spec('2012-W32', :to) # =>
|
51
|
-
# Date.parse_spec('W32') # =>
|
52
|
-
#
|
53
|
-
# A spec of the form Q3 or 3Q returns the beginning or end of calendar
|
54
|
-
# quarters.
|
55
|
-
# Date.parse_spec('Q3') # =>
|
56
|
-
#
|
57
|
-
# @param spec [#to_s] a stringling containing the spec to be interpreted
|
58
|
-
# @param spec_type [:from, :to] interpret the spec as a from- or to-spec
|
59
|
-
# respectively, defaulting to interpretation as a to-spec.
|
60
|
-
# @return [Date] a date object equivalent to the date spec
|
61
|
-
def self.parse_spec(spec, spec_type = :from)
|
62
|
-
spec = spec.to_s.strip
|
63
|
-
unless [:from, :to].include?(spec_type)
|
64
|
-
raise ArgumentError, "invalid date spec type: '#{spec_type}'"
|
21
|
+
# Successor of self. Allows Date to work as a countable element
|
22
|
+
# of a Range.
|
23
|
+
def succ
|
24
|
+
self + 1.day
|
65
25
|
end
|
66
26
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
116
|
-
when /^(\d\d\d\d)[-\/](\d)[Hh]$/, /^(\d\d\d\d)[-\/][Hh](\d)$/
|
117
|
-
# Year-Half
|
118
|
-
year = $1.to_i
|
119
|
-
half = $2.to_i
|
120
|
-
unless [1, 2].include?(half)
|
121
|
-
raise ArgumentError, "bad date format: #{spec}"
|
122
|
-
end
|
123
|
-
month = half * 6
|
124
|
-
if spec_type == :from
|
125
|
-
Date.new(year, month, 15).beginning_of_half
|
126
|
-
else
|
127
|
-
Date.new(year, month, 1).end_of_half
|
128
|
-
end
|
129
|
-
when /^([12])[hH]$/, /^[hH]([12])$/
|
130
|
-
# Half only
|
131
|
-
this_year = today.year
|
132
|
-
half = $1.to_i
|
133
|
-
date = Date.new(this_year, half * 6, 15)
|
134
|
-
if spec_type == :from
|
135
|
-
date.beginning_of_half
|
136
|
-
else
|
137
|
-
date.end_of_half
|
138
|
-
end
|
139
|
-
when /^(\d\d\d\d)[-\/](\d\d?)*$/
|
140
|
-
# Year-Month only
|
141
|
-
if spec_type == :from
|
142
|
-
Date.new($1.to_i, $2.to_i, 1)
|
143
|
-
else
|
144
|
-
Date.new($1.to_i, $2.to_i, 1).end_of_month
|
145
|
-
end
|
146
|
-
when /^(\d\d?)[-\/](\d\d?)*$/
|
147
|
-
# Month-Day only
|
148
|
-
if spec_type == :from
|
149
|
-
Date.new(today.year, $1.to_i, $2.to_i)
|
150
|
-
else
|
151
|
-
Date.new(today.year, $1.to_i, $2.to_i).end_of_month
|
152
|
-
end
|
153
|
-
when /\A(\d\d?)\z/
|
154
|
-
# Month only
|
155
|
-
if spec_type == :from
|
156
|
-
Date.new(today.year, $1.to_i, 1)
|
157
|
-
else
|
158
|
-
Date.new(today.year, $1.to_i, 1).end_of_month
|
159
|
-
end
|
160
|
-
when /^(\d\d\d\d)$/
|
161
|
-
# Year only
|
162
|
-
if spec_type == :from
|
163
|
-
Date.new($1.to_i, 1, 1)
|
164
|
-
else
|
165
|
-
Date.new($1.to_i, 12, 31)
|
166
|
-
end
|
167
|
-
when /^(to|this_?)?day/
|
168
|
-
today
|
169
|
-
when /^(yester|last_?)?day/
|
170
|
-
today - 1.day
|
171
|
-
when /^(this_?)?week/
|
172
|
-
spec_type == :from ? today.beginning_of_week : today.end_of_week
|
173
|
-
when /last_?week/
|
174
|
-
if spec_type == :from
|
175
|
-
(today - 1.week).beginning_of_week
|
176
|
-
else
|
177
|
-
(today - 1.week).end_of_week
|
178
|
-
end
|
179
|
-
when /^(this_?)?biweek/
|
180
|
-
if spec_type == :from
|
181
|
-
today.beginning_of_biweek
|
182
|
-
else
|
183
|
-
today.end_of_biweek
|
184
|
-
end
|
185
|
-
when /last_?biweek/
|
186
|
-
if spec_type == :from
|
187
|
-
(today - 2.week).beginning_of_biweek
|
188
|
-
else
|
189
|
-
(today - 2.week).end_of_biweek
|
190
|
-
end
|
191
|
-
when /^(this_?)?semimonth/
|
192
|
-
spec_type == :from ? today.beginning_of_semimonth : today.end_of_semimonth
|
193
|
-
when /^last_?semimonth/
|
194
|
-
if spec_type == :from
|
195
|
-
(today - 15.days).beginning_of_semimonth
|
196
|
-
else
|
197
|
-
(today - 15.days).end_of_semimonth
|
198
|
-
end
|
199
|
-
when /^(this_?)?month/
|
200
|
-
if spec_type == :from
|
201
|
-
today.beginning_of_month
|
202
|
-
else
|
203
|
-
today.end_of_month
|
204
|
-
end
|
205
|
-
when /^last_?month/
|
206
|
-
if spec_type == :from
|
207
|
-
(today - 1.month).beginning_of_month
|
208
|
-
else
|
209
|
-
(today - 1.month).end_of_month
|
27
|
+
# Format as an ISO string.
|
28
|
+
def iso
|
29
|
+
strftime('%Y-%m-%d')
|
30
|
+
end
|
31
|
+
|
32
|
+
# Format date to TeX documents as ISO strings
|
33
|
+
def tex_quote
|
34
|
+
iso
|
35
|
+
end
|
36
|
+
|
37
|
+
# Format as an all-numeric string, i.e. 'YYYYMMDD'
|
38
|
+
def num
|
39
|
+
strftime('%Y%m%d')
|
40
|
+
end
|
41
|
+
|
42
|
+
# Format as an inactive Org date (see emacs org-mode)
|
43
|
+
def org
|
44
|
+
strftime('[%Y-%m-%d %a]')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Format as an English string
|
48
|
+
def eng
|
49
|
+
strftime('%B %e, %Y')
|
50
|
+
end
|
51
|
+
|
52
|
+
# Format date in MM/DD/YYYY form, as typical for the short American
|
53
|
+
# form.
|
54
|
+
def american
|
55
|
+
strftime '%-m/%-d/%Y'
|
56
|
+
end
|
57
|
+
|
58
|
+
# Does self fall on a weekend?
|
59
|
+
def weekend?
|
60
|
+
saturday? || sunday?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Does self fall on a weekday?
|
64
|
+
def weekday?
|
65
|
+
!weekend?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Self's calendar half: 1 or 2
|
69
|
+
def half
|
70
|
+
case month
|
71
|
+
when (1..6)
|
72
|
+
1
|
73
|
+
when (7..12)
|
74
|
+
2
|
210
75
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
76
|
+
end
|
77
|
+
|
78
|
+
# Self's calendar quarter: 1, 2, 3, or 4
|
79
|
+
def quarter
|
80
|
+
case month
|
81
|
+
when (1..3)
|
82
|
+
1
|
83
|
+
when (4..6)
|
84
|
+
2
|
85
|
+
when (7..9)
|
86
|
+
3
|
87
|
+
when (10..12)
|
88
|
+
4
|
216
89
|
end
|
217
|
-
|
218
|
-
|
219
|
-
|
90
|
+
end
|
91
|
+
|
92
|
+
# The date that is the first day of the half-year in which self falls.
|
93
|
+
def beginning_of_half
|
94
|
+
if month > 9
|
95
|
+
(beginning_of_quarter - 15).beginning_of_quarter
|
96
|
+
elsif month > 6
|
97
|
+
beginning_of_quarter
|
220
98
|
else
|
221
|
-
|
99
|
+
beginning_of_year
|
222
100
|
end
|
223
|
-
|
224
|
-
|
225
|
-
|
101
|
+
end
|
102
|
+
|
103
|
+
# The date that is the last day of the half-year in which self falls.
|
104
|
+
def end_of_half
|
105
|
+
if month < 4
|
106
|
+
(end_of_quarter + 15).end_of_quarter
|
107
|
+
elsif month < 7
|
108
|
+
end_of_quarter
|
226
109
|
else
|
227
|
-
|
110
|
+
end_of_year
|
228
111
|
end
|
229
|
-
|
230
|
-
|
231
|
-
|
112
|
+
end
|
113
|
+
|
114
|
+
# The date that is the first day of the bimonth in which self
|
115
|
+
# falls. A 'bimonth' is a two-month calendar period beginning on the
|
116
|
+
# first day of the odd-numbered months. E.g., 2014-01-01 to
|
117
|
+
# 2014-02-28 is the first bimonth of 2014.
|
118
|
+
def beginning_of_bimonth
|
119
|
+
if month.odd?
|
120
|
+
beginning_of_month
|
232
121
|
else
|
233
|
-
(
|
122
|
+
(self - 1.month).beginning_of_month
|
234
123
|
end
|
235
|
-
|
236
|
-
|
237
|
-
|
124
|
+
end
|
125
|
+
|
126
|
+
# The date that is the last day of the bimonth in which self falls.
|
127
|
+
# A 'bimonth' is a two-month calendar period beginning on the first
|
128
|
+
# day of the odd-numbered months. E.g., 2014-01-01 to 2014-02-28 is
|
129
|
+
# the first bimonth of 2014.
|
130
|
+
def end_of_bimonth
|
131
|
+
if month.odd?
|
132
|
+
(self + 1.month).end_of_month
|
238
133
|
else
|
239
|
-
|
134
|
+
end_of_month
|
240
135
|
end
|
241
|
-
|
242
|
-
|
243
|
-
|
136
|
+
end
|
137
|
+
|
138
|
+
# The date that is the first day of the semimonth in which self
|
139
|
+
# falls. A semimonth is a calendar period beginning on the 1st or
|
140
|
+
# 16th of each month and ending on the 15th or last day of the month
|
141
|
+
# respectively. So each year has exactly 24 semimonths.
|
142
|
+
def beginning_of_semimonth
|
143
|
+
if day >= 16
|
144
|
+
::Date.new(year, month, 16)
|
244
145
|
else
|
245
|
-
|
146
|
+
beginning_of_month
|
246
147
|
end
|
247
|
-
|
248
|
-
|
249
|
-
|
148
|
+
end
|
149
|
+
|
150
|
+
# The date that is the last day of the semimonth in which self
|
151
|
+
# falls. A semimonth is a calendar period beginning on the 1st or
|
152
|
+
# 16th of each month and ending on the 15th or last day of the month
|
153
|
+
# respectively. So each year has exactly 24 semimonths.
|
154
|
+
def end_of_semimonth
|
155
|
+
if day <= 15
|
156
|
+
::Date.new(year, month, 15)
|
250
157
|
else
|
251
|
-
|
158
|
+
end_of_month
|
252
159
|
end
|
253
|
-
|
254
|
-
|
255
|
-
|
160
|
+
end
|
161
|
+
|
162
|
+
# Note: we use a Monday start of the week in the next two methods because
|
163
|
+
# commercial week counting assumes a Monday start.
|
164
|
+
def beginning_of_biweek
|
165
|
+
if cweek.odd?
|
166
|
+
beginning_of_week(:monday)
|
256
167
|
else
|
257
|
-
(
|
168
|
+
(self - 1.week).beginning_of_week(:monday)
|
258
169
|
end
|
259
|
-
|
260
|
-
|
261
|
-
|
170
|
+
end
|
171
|
+
|
172
|
+
def end_of_biweek
|
173
|
+
if cweek.odd?
|
174
|
+
(self + 1.week).end_of_week(:monday)
|
262
175
|
else
|
263
|
-
|
176
|
+
end_of_week(:monday)
|
264
177
|
end
|
265
|
-
when /^never/
|
266
|
-
nil
|
267
|
-
else
|
268
|
-
raise ArgumentError, "bad date spec: '#{spec}''"
|
269
|
-
end # !> previous definition of length was here
|
270
|
-
end
|
271
|
-
|
272
|
-
# Predecessor of self. Allows Date to work as a countable element
|
273
|
-
# of a Range.
|
274
|
-
def pred
|
275
|
-
self - 1.day
|
276
|
-
end
|
277
|
-
|
278
|
-
# Successor of self. Allows Date to work as a countable element
|
279
|
-
# of a Range.
|
280
|
-
def succ
|
281
|
-
self + 1.day
|
282
|
-
end
|
283
|
-
|
284
|
-
# Format as an ISO string.
|
285
|
-
def iso
|
286
|
-
strftime('%Y-%m-%d')
|
287
|
-
end
|
288
|
-
|
289
|
-
# Format date to TeX documents as ISO strings
|
290
|
-
def tex_quote
|
291
|
-
iso
|
292
|
-
end
|
293
|
-
|
294
|
-
# Format as an all-numeric string, i.e. 'YYYYMMDD'
|
295
|
-
def num
|
296
|
-
strftime('%Y%m%d')
|
297
|
-
end
|
298
|
-
|
299
|
-
def format_by(fmt = '%Y-%m-%d')
|
300
|
-
fmt ||= '%Y-%m-%d'
|
301
|
-
strftime(fmt)
|
302
|
-
end
|
303
|
-
|
304
|
-
# Format as an inactive Org date (see emacs org-mode)
|
305
|
-
def org
|
306
|
-
strftime('[%Y-%m-%d %a]')
|
307
|
-
end
|
308
|
-
|
309
|
-
# Format as an English string
|
310
|
-
def eng
|
311
|
-
strftime('%B %e, %Y')
|
312
|
-
end
|
313
|
-
|
314
|
-
# Format date in MM/DD/YYYY form, as typical for the short American
|
315
|
-
# form.
|
316
|
-
def american
|
317
|
-
strftime '%-m/%-d/%Y'
|
318
|
-
end
|
319
|
-
|
320
|
-
# Does self fall on a weekend?
|
321
|
-
def weekend?
|
322
|
-
saturday? || sunday?
|
323
|
-
end
|
324
|
-
|
325
|
-
# Does self fall on a weekday?
|
326
|
-
def weekday?
|
327
|
-
!weekend?
|
328
|
-
end
|
329
|
-
|
330
|
-
# Self's calendar half: 1 or 2
|
331
|
-
def half
|
332
|
-
case month
|
333
|
-
when (1..6)
|
334
|
-
1
|
335
|
-
when (7..12)
|
336
|
-
2
|
337
178
|
end
|
338
|
-
end
|
339
179
|
|
340
|
-
|
341
|
-
|
342
|
-
case month
|
343
|
-
when (1..3)
|
344
|
-
1
|
345
|
-
when (4..6)
|
346
|
-
2
|
347
|
-
when (7..9)
|
348
|
-
3
|
349
|
-
when (10..12)
|
350
|
-
4
|
180
|
+
def beginning_of_year?
|
181
|
+
beginning_of_year == self
|
351
182
|
end
|
352
|
-
end
|
353
183
|
|
354
|
-
|
355
|
-
|
356
|
-
if month > 9
|
357
|
-
(beginning_of_quarter - 15).beginning_of_quarter
|
358
|
-
elsif month > 6
|
359
|
-
beginning_of_quarter
|
360
|
-
else
|
361
|
-
beginning_of_year
|
184
|
+
def end_of_year?
|
185
|
+
end_of_year == self
|
362
186
|
end
|
363
|
-
end
|
364
187
|
|
365
|
-
|
366
|
-
|
367
|
-
if month < 4
|
368
|
-
(end_of_quarter + 15).end_of_quarter
|
369
|
-
elsif month < 7
|
370
|
-
end_of_quarter
|
371
|
-
else
|
372
|
-
end_of_year
|
188
|
+
def beginning_of_half?
|
189
|
+
beginning_of_half == self
|
373
190
|
end
|
374
|
-
end
|
375
191
|
|
376
|
-
|
377
|
-
|
378
|
-
# first day of the odd-numbered months. E.g., 2014-01-01 to
|
379
|
-
# 2014-02-28 is the first bimonth of 2014.
|
380
|
-
def beginning_of_bimonth
|
381
|
-
if month.odd?
|
382
|
-
beginning_of_month
|
383
|
-
else
|
384
|
-
(self - 1.month).beginning_of_month
|
192
|
+
def end_of_half?
|
193
|
+
end_of_half == self
|
385
194
|
end
|
386
|
-
end
|
387
195
|
|
388
|
-
|
389
|
-
|
390
|
-
# day of the odd-numbered months. E.g., 2014-01-01 to 2014-02-28 is
|
391
|
-
# the first bimonth of 2014.
|
392
|
-
def end_of_bimonth
|
393
|
-
if month.odd?
|
394
|
-
(self + 1.month).end_of_month
|
395
|
-
else
|
396
|
-
end_of_month
|
196
|
+
def beginning_of_quarter?
|
197
|
+
beginning_of_quarter == self
|
397
198
|
end
|
398
|
-
end
|
399
199
|
|
400
|
-
|
401
|
-
|
402
|
-
# 16th of each month and ending on the 15th or last day of the month
|
403
|
-
# respectively. So each year has exactly 24 semimonths.
|
404
|
-
def beginning_of_semimonth
|
405
|
-
if day >= 16
|
406
|
-
Date.new(year, month, 16)
|
407
|
-
else
|
408
|
-
beginning_of_month
|
200
|
+
def end_of_quarter?
|
201
|
+
end_of_quarter == self
|
409
202
|
end
|
410
|
-
end
|
411
203
|
|
412
|
-
|
413
|
-
|
414
|
-
# 16th of each month and ending on the 15th or last day of the month
|
415
|
-
# respectively. So each year has exactly 24 semimonths.
|
416
|
-
def end_of_semimonth
|
417
|
-
if day <= 15
|
418
|
-
Date.new(year, month, 15)
|
419
|
-
else
|
420
|
-
end_of_month
|
204
|
+
def beginning_of_bimonth?
|
205
|
+
month.odd? && beginning_of_month == self
|
421
206
|
end
|
422
|
-
end
|
423
207
|
|
424
|
-
|
425
|
-
|
426
|
-
def beginning_of_biweek
|
427
|
-
if cweek.odd?
|
428
|
-
beginning_of_week(:monday)
|
429
|
-
else
|
430
|
-
(self - 1.week).beginning_of_week(:monday)
|
208
|
+
def end_of_bimonth?
|
209
|
+
month.even? && end_of_month == self
|
431
210
|
end
|
432
|
-
end
|
433
211
|
|
434
|
-
|
435
|
-
|
436
|
-
(self + 1.week).end_of_week(:monday)
|
437
|
-
else
|
438
|
-
end_of_week(:monday)
|
212
|
+
def beginning_of_month?
|
213
|
+
beginning_of_month == self
|
439
214
|
end
|
440
|
-
end
|
441
|
-
|
442
|
-
def beginning_of_year?
|
443
|
-
beginning_of_year == self
|
444
|
-
end
|
445
|
-
|
446
|
-
def end_of_year?
|
447
|
-
end_of_year == self
|
448
|
-
end
|
449
|
-
|
450
|
-
def beginning_of_half?
|
451
|
-
beginning_of_half == self
|
452
|
-
end
|
453
|
-
|
454
|
-
def end_of_half?
|
455
|
-
end_of_half == self
|
456
|
-
end
|
457
|
-
|
458
|
-
def beginning_of_quarter?
|
459
|
-
beginning_of_quarter == self
|
460
|
-
end
|
461
|
-
|
462
|
-
def end_of_quarter?
|
463
|
-
end_of_quarter == self
|
464
|
-
end
|
465
|
-
|
466
|
-
def beginning_of_bimonth?
|
467
|
-
month.odd? && beginning_of_month == self
|
468
|
-
end
|
469
|
-
|
470
|
-
def end_of_bimonth?
|
471
|
-
month.even? && end_of_month == self
|
472
|
-
end
|
473
|
-
|
474
|
-
def beginning_of_month?
|
475
|
-
beginning_of_month == self
|
476
|
-
end
|
477
|
-
|
478
|
-
def end_of_month?
|
479
|
-
end_of_month == self
|
480
|
-
end
|
481
|
-
|
482
|
-
def beginning_of_semimonth?
|
483
|
-
beginning_of_semimonth == self
|
484
|
-
end
|
485
215
|
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
def beginning_of_biweek?
|
491
|
-
beginning_of_biweek == self
|
492
|
-
end
|
493
|
-
|
494
|
-
def end_of_biweek?
|
495
|
-
end_of_biweek == self
|
496
|
-
end
|
497
|
-
|
498
|
-
def beginning_of_week?
|
499
|
-
beginning_of_week == self
|
500
|
-
end
|
216
|
+
def end_of_month?
|
217
|
+
end_of_month == self
|
218
|
+
end
|
501
219
|
|
502
|
-
|
503
|
-
|
504
|
-
|
220
|
+
def beginning_of_semimonth?
|
221
|
+
beginning_of_semimonth == self
|
222
|
+
end
|
505
223
|
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
end
|
224
|
+
def end_of_semimonth?
|
225
|
+
end_of_semimonth == self
|
226
|
+
end
|
510
227
|
|
511
|
-
|
512
|
-
|
513
|
-
when :year
|
514
|
-
next_year
|
515
|
-
when :half
|
516
|
-
next_month(6)
|
517
|
-
when :quarter
|
518
|
-
next_month(3)
|
519
|
-
when :bimonth
|
520
|
-
next_month(2)
|
521
|
-
when :month
|
522
|
-
next_month
|
523
|
-
when :semimonth
|
524
|
-
self + 15.days
|
525
|
-
when :biweek
|
526
|
-
self + 14.days
|
527
|
-
when :week
|
528
|
-
self + 7.days
|
529
|
-
when :day
|
530
|
-
self + 1.days
|
531
|
-
else
|
532
|
-
raise ArgumentError, "add_chunk unknown chunk: '#{chunk}'"
|
228
|
+
def beginning_of_biweek?
|
229
|
+
beginning_of_biweek == self
|
533
230
|
end
|
534
|
-
end
|
535
231
|
|
536
|
-
|
537
|
-
|
538
|
-
when :year
|
539
|
-
beginning_of_year
|
540
|
-
when :half
|
541
|
-
beginning_of_half
|
542
|
-
when :quarter
|
543
|
-
beginning_of_quarter
|
544
|
-
when :bimonth
|
545
|
-
beginning_of_bimonth
|
546
|
-
when :month
|
547
|
-
beginning_of_month
|
548
|
-
when :semimonth
|
549
|
-
beginning_of_semimonth
|
550
|
-
when :biweek
|
551
|
-
beginning_of_biweek
|
552
|
-
when :week
|
553
|
-
beginning_of_week
|
554
|
-
when :day
|
555
|
-
self
|
556
|
-
else
|
557
|
-
raise ArgumentError, "unknown chunk sym: '#{sym}'"
|
232
|
+
def end_of_biweek?
|
233
|
+
end_of_biweek == self
|
558
234
|
end
|
559
|
-
end
|
560
235
|
|
561
|
-
|
562
|
-
|
563
|
-
when :year
|
564
|
-
end_of_year
|
565
|
-
when :half
|
566
|
-
end_of_half
|
567
|
-
when :quarter
|
568
|
-
end_of_quarter
|
569
|
-
when :bimonth
|
570
|
-
end_of_bimonth
|
571
|
-
when :month
|
572
|
-
end_of_month
|
573
|
-
when :semimonth
|
574
|
-
end_of_semimonth
|
575
|
-
when :biweek
|
576
|
-
end_of_biweek
|
577
|
-
when :week
|
578
|
-
end_of_week
|
579
|
-
when :day
|
580
|
-
self
|
581
|
-
else
|
582
|
-
raise ArgumentError, "unknown chunk sym: '#{sym}'"
|
236
|
+
def beginning_of_week?
|
237
|
+
beginning_of_week == self
|
583
238
|
end
|
584
|
-
end
|
585
239
|
|
586
|
-
|
587
|
-
|
588
|
-
# executive-order-closing-executive-departments-and-agencies-federal-gover
|
589
|
-
FED_DECREED_HOLIDAYS =
|
590
|
-
[
|
591
|
-
Date.parse('2012-12-24')
|
592
|
-
].freeze
|
593
|
-
|
594
|
-
def self.days_in_month(y, m)
|
595
|
-
raise ArgumentError, 'illegal month number' if m < 1 || m > 12
|
596
|
-
days = Time::COMMON_YEAR_DAYS_IN_MONTH[m]
|
597
|
-
if m == 2
|
598
|
-
Date.new(y, m, 1).leap? ? 29 : 28
|
599
|
-
else
|
600
|
-
days
|
240
|
+
def end_of_week?
|
241
|
+
end_of_week == self
|
601
242
|
end
|
602
|
-
end
|
603
243
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
d = Date.new(year, month, 1).end_of_month
|
627
|
-
d -= 1 while d.wday != wday
|
628
|
-
# Set d to the nth wday in month
|
629
|
-
nd = 1
|
630
|
-
while nd != n
|
631
|
-
d -= 7
|
632
|
-
nd += 1
|
244
|
+
def add_chunk(chunk)
|
245
|
+
case chunk
|
246
|
+
when :year
|
247
|
+
next_year
|
248
|
+
when :half
|
249
|
+
next_month(6)
|
250
|
+
when :quarter
|
251
|
+
next_month(3)
|
252
|
+
when :bimonth
|
253
|
+
next_month(2)
|
254
|
+
when :month
|
255
|
+
next_month
|
256
|
+
when :semimonth
|
257
|
+
self + 15.days
|
258
|
+
when :biweek
|
259
|
+
self + 14.days
|
260
|
+
when :week
|
261
|
+
self + 7.days
|
262
|
+
when :day
|
263
|
+
self + 1.days
|
264
|
+
else
|
265
|
+
raise ArgumentError, "add_chunk unknown chunk: '#{chunk}'"
|
633
266
|
end
|
634
|
-
d
|
635
|
-
else
|
636
|
-
raise ArgumentError,
|
637
|
-
'Arg 1 to nth_wday_in_month_year cannot be zero'
|
638
267
|
end
|
639
|
-
end
|
640
|
-
|
641
|
-
def within_6mos_of?(d)
|
642
|
-
# Date 6 calendar months before self
|
643
|
-
start_date = self - 6.months + 2.days
|
644
|
-
end_date = self + 6.months - 2.days
|
645
|
-
(start_date..end_date).cover?(d)
|
646
|
-
end
|
647
268
|
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
269
|
+
def beginning_of_chunk(sym)
|
270
|
+
case sym
|
271
|
+
when :year
|
272
|
+
beginning_of_year
|
273
|
+
when :half
|
274
|
+
beginning_of_half
|
275
|
+
when :quarter
|
276
|
+
beginning_of_quarter
|
277
|
+
when :bimonth
|
278
|
+
beginning_of_bimonth
|
279
|
+
when :month
|
280
|
+
beginning_of_month
|
281
|
+
when :semimonth
|
282
|
+
beginning_of_semimonth
|
283
|
+
when :biweek
|
284
|
+
beginning_of_biweek
|
285
|
+
when :week
|
286
|
+
beginning_of_week
|
287
|
+
when :day
|
288
|
+
self
|
289
|
+
else
|
290
|
+
raise ArgumentError, "unknown chunk sym: '#{sym}'"
|
291
|
+
end
|
292
|
+
end
|
667
293
|
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
294
|
+
def end_of_chunk(sym)
|
295
|
+
case sym
|
296
|
+
when :year
|
297
|
+
end_of_year
|
298
|
+
when :half
|
299
|
+
end_of_half
|
300
|
+
when :quarter
|
301
|
+
end_of_quarter
|
302
|
+
when :bimonth
|
303
|
+
end_of_bimonth
|
304
|
+
when :month
|
305
|
+
end_of_month
|
306
|
+
when :semimonth
|
307
|
+
end_of_semimonth
|
308
|
+
when :biweek
|
309
|
+
end_of_biweek
|
310
|
+
when :week
|
311
|
+
end_of_week
|
312
|
+
when :day
|
313
|
+
self
|
314
|
+
else
|
315
|
+
raise ArgumentError, "unknown chunk sym: '#{sym}'"
|
316
|
+
end
|
317
|
+
end
|
672
318
|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
319
|
+
def within_6mos_of?(d)
|
320
|
+
# Date 6 calendar months before self
|
321
|
+
start_date = self - 6.months + 2.days
|
322
|
+
end_date = self + 6.months - 2.days
|
323
|
+
(start_date..end_date).cover?(d)
|
324
|
+
end
|
678
325
|
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
#######################################################
|
683
|
-
def fed_fixed_holiday?
|
684
|
-
# Fixed-date holidays on weekdays
|
685
|
-
if mon == 1 && mday == 1
|
686
|
-
# New Years (January 1),
|
687
|
-
true
|
688
|
-
elsif mon == 7 && mday == 4
|
689
|
-
# Independence Day (July 4),
|
690
|
-
true
|
691
|
-
elsif mon == 11 && mday == 11
|
692
|
-
# Veterans Day (November 11),
|
693
|
-
true
|
694
|
-
elsif mon == 12 && mday == 25
|
695
|
-
# Christmas (December 25), and
|
696
|
-
true
|
697
|
-
else
|
698
|
-
false
|
326
|
+
def easter_this_year
|
327
|
+
# Return the date of Easter in self's year
|
328
|
+
::Date.easter(year)
|
699
329
|
end
|
700
|
-
end
|
701
330
|
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
# No moveable feasts in certain months
|
707
|
-
if [3, 4, 6, 7, 8, 12].include?(month)
|
708
|
-
false
|
709
|
-
elsif monday?
|
710
|
-
moveable_mondays = []
|
711
|
-
# MLK's Birthday (Third Monday in Jan)
|
712
|
-
moveable_mondays << nth_wday_in_month?(3, 1, 1)
|
713
|
-
# Washington's Birthday (Third Monday in Feb)
|
714
|
-
moveable_mondays << nth_wday_in_month?(3, 1, 2)
|
715
|
-
# Memorial Day (Last Monday in May)
|
716
|
-
moveable_mondays << nth_wday_in_month?(-1, 1, 5)
|
717
|
-
# Labor Day (First Monday in Sep)
|
718
|
-
moveable_mondays << nth_wday_in_month?(1, 1, 9)
|
719
|
-
# Columbus Day (Second Monday in Oct)
|
720
|
-
moveable_mondays << nth_wday_in_month?(2, 1, 10)
|
721
|
-
# Other Mondays
|
722
|
-
moveable_mondays.any?
|
723
|
-
elsif thursday?
|
724
|
-
# Thanksgiving Day (Fourth Thur in Nov)
|
725
|
-
nth_wday_in_month?(4, 4, 11)
|
726
|
-
else
|
727
|
-
false
|
331
|
+
def easter?
|
332
|
+
# Am I Easter?
|
333
|
+
self == easter_this_year
|
728
334
|
end
|
729
|
-
end
|
730
335
|
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
# Some days are holidays by executive decree
|
736
|
-
return true if FED_DECREED_HOLIDAYS.include?(self)
|
737
|
-
|
738
|
-
# Is self a fixed holiday
|
739
|
-
return true if fed_fixed_holiday? || fed_moveable_feast?
|
740
|
-
|
741
|
-
if friday? && month == 12 && day == 26
|
742
|
-
# If Christmas falls on a Thursday, apparently, the Friday after is
|
743
|
-
# treated as a holiday as well. See 2003, 2008, for example.
|
744
|
-
true
|
745
|
-
elsif friday?
|
746
|
-
# A Friday is a holiday if a fixed-date holiday
|
747
|
-
# would fall on the following Saturday
|
748
|
-
(self + 1).fed_fixed_holiday? || (self + 1).fed_moveable_feast?
|
749
|
-
elsif monday?
|
750
|
-
# A Monday is a holiday if a fixed-date holiday
|
751
|
-
# would fall on the preceding Sunday
|
752
|
-
(self - 1).fed_fixed_holiday? || (self - 1).fed_moveable_feast?
|
753
|
-
else
|
754
|
-
false
|
336
|
+
def nth_wday_in_month?(n, wday, month)
|
337
|
+
# Is self the nth weekday in the given month of its year?
|
338
|
+
# If n is negative, count from last day of month
|
339
|
+
self == ::Date.nth_wday_in_year_month(n, wday, year, month)
|
755
340
|
end
|
756
|
-
end
|
757
341
|
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
true
|
788
|
-
else
|
789
|
-
false
|
342
|
+
#######################################################
|
343
|
+
# Calculations for Federal holidays
|
344
|
+
# 5 USC 6103
|
345
|
+
#######################################################
|
346
|
+
# Holidays decreed by executive order
|
347
|
+
# See http://www.whitehouse.gov/the-press-office/2012/12/21/
|
348
|
+
# executive-order-closing-executive-departments-and-agencies-federal-gover
|
349
|
+
FED_DECREED_HOLIDAYS =
|
350
|
+
[
|
351
|
+
::Date.parse('2012-12-24')
|
352
|
+
].freeze
|
353
|
+
|
354
|
+
def fed_fixed_holiday?
|
355
|
+
# Fixed-date holidays on weekdays
|
356
|
+
if mon == 1 && mday == 1
|
357
|
+
# New Years (January 1),
|
358
|
+
true
|
359
|
+
elsif mon == 7 && mday == 4
|
360
|
+
# Independence Day (July 4),
|
361
|
+
true
|
362
|
+
elsif mon == 11 && mday == 11
|
363
|
+
# Veterans Day (November 11),
|
364
|
+
true
|
365
|
+
elsif mon == 12 && mday == 25
|
366
|
+
# Christmas (December 25), and
|
367
|
+
true
|
368
|
+
else
|
369
|
+
false
|
370
|
+
end
|
790
371
|
end
|
791
|
-
end
|
792
372
|
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
case month
|
801
|
-
when 1
|
802
|
-
# MLK's Birthday (Third Monday in Jan) since 1998
|
803
|
-
year >= 1998 && nth_wday_in_month?(3, 1, 1)
|
804
|
-
when 2
|
805
|
-
# Lincoln's birthday until 1953
|
806
|
-
# Washington's Birthday (Third Monday in Feb)
|
807
|
-
(year <= 1953 && month == 2 && day == 12) ||
|
808
|
-
(year <= 1970 ? (month == 2 && day == 22)
|
809
|
-
: nth_wday_in_month?(3, 1, 2))
|
810
|
-
when 3, 4
|
811
|
-
# Good Friday
|
812
|
-
if !friday?
|
373
|
+
def fed_moveable_feast?
|
374
|
+
# See if today is a "movable feast," all of which are
|
375
|
+
# rigged to fall on Monday except Thanksgiving
|
376
|
+
|
377
|
+
# No moveable feasts in certain months
|
378
|
+
if [3, 4, 6, 7, 8, 12].include?(month)
|
813
379
|
false
|
814
|
-
elsif
|
815
|
-
|
380
|
+
elsif monday?
|
381
|
+
moveable_mondays = []
|
382
|
+
# MLK's Birthday (Third Monday in Jan)
|
383
|
+
moveable_mondays << nth_wday_in_month?(3, 1, 1)
|
384
|
+
# Washington's Birthday (Third Monday in Feb)
|
385
|
+
moveable_mondays << nth_wday_in_month?(3, 1, 2)
|
386
|
+
# Memorial Day (Last Monday in May)
|
387
|
+
moveable_mondays << nth_wday_in_month?(-1, 1, 5)
|
388
|
+
# Labor Day (First Monday in Sep)
|
389
|
+
moveable_mondays << nth_wday_in_month?(1, 1, 9)
|
390
|
+
# Columbus Day (Second Monday in Oct)
|
391
|
+
moveable_mondays << nth_wday_in_month?(2, 1, 10)
|
392
|
+
# Other Mondays
|
393
|
+
moveable_mondays.any?
|
394
|
+
elsif thursday?
|
395
|
+
# Thanksgiving Day (Fourth Thur in Nov)
|
396
|
+
nth_wday_in_month?(4, 4, 11)
|
397
|
+
else
|
816
398
|
false
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def fed_holiday?
|
403
|
+
# All Saturdays and Sundays are "holidays"
|
404
|
+
return true if weekend?
|
405
|
+
|
406
|
+
# Some days are holidays by executive decree
|
407
|
+
return true if FED_DECREED_HOLIDAYS.include?(self)
|
408
|
+
|
409
|
+
# Is self a fixed holiday
|
410
|
+
return true if fed_fixed_holiday? || fed_moveable_feast?
|
411
|
+
|
412
|
+
if friday? && month == 12 && day == 26
|
413
|
+
# If Christmas falls on a Thursday, apparently, the Friday after is
|
414
|
+
# treated as a holiday as well. See 2003, 2008, for example.
|
415
|
+
true
|
416
|
+
elsif friday?
|
417
|
+
# A Friday is a holiday if a fixed-date holiday
|
418
|
+
# would fall on the following Saturday
|
419
|
+
(self + 1).fed_fixed_holiday? || (self + 1).fed_moveable_feast?
|
420
|
+
elsif monday?
|
421
|
+
# A Monday is a holiday if a fixed-date holiday
|
422
|
+
# would fall on the preceding Sunday
|
423
|
+
(self - 1).fed_fixed_holiday? || (self - 1).fed_moveable_feast?
|
817
424
|
else
|
818
|
-
|
425
|
+
false
|
819
426
|
end
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
427
|
+
end
|
428
|
+
|
429
|
+
#######################################################
|
430
|
+
# Calculations for NYSE holidays
|
431
|
+
# Rule 51 and supplementary material
|
432
|
+
#######################################################
|
433
|
+
|
434
|
+
# Rule: if it falls on Saturday, observe on preceding Friday.
|
435
|
+
# Rule: if it falls on Sunday, observe on following Monday.
|
436
|
+
#
|
437
|
+
# New Year's Day, January 1.
|
438
|
+
# Birthday of Martin Luther King, Jr., the third Monday in January.
|
439
|
+
# Washington's Birthday, the third Monday in February.
|
440
|
+
# Good Friday Friday before Easter Sunday. NOTE: not a fed holiday
|
441
|
+
# Memorial Day, the last Monday in May.
|
442
|
+
# Independence Day, July 4.
|
443
|
+
# Labor Day, the first Monday in September.
|
444
|
+
# NOTE: Columbus and Veterans days not observed
|
445
|
+
# Thanksgiving Day, the fourth Thursday in November.
|
446
|
+
# Christmas Day, December 25.
|
447
|
+
|
448
|
+
def nyse_fixed_holiday?
|
449
|
+
# Fixed-date holidays
|
450
|
+
if mon == 1 && mday == 1
|
451
|
+
# New Years (January 1),
|
452
|
+
true
|
453
|
+
elsif mon == 7 && mday == 4
|
454
|
+
# Independence Day (July 4),
|
455
|
+
true
|
456
|
+
elsif mon == 12 && mday == 25
|
457
|
+
# Christmas (December 25), and
|
458
|
+
true
|
459
|
+
else
|
460
|
+
false
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def nyse_moveable_feast?
|
465
|
+
# See if today is a "movable feast," all of which are
|
466
|
+
# rigged to fall on Monday except Thanksgiving
|
467
|
+
|
468
|
+
# No moveable feasts in certain months
|
469
|
+
return false if [6, 7, 8, 10, 12].include?(month)
|
470
|
+
|
471
|
+
case month
|
472
|
+
when 1
|
473
|
+
# MLK's Birthday (Third Monday in Jan) since 1998
|
474
|
+
year >= 1998 && nth_wday_in_month?(3, 1, 1)
|
475
|
+
when 2
|
476
|
+
# Lincoln's birthday until 1953
|
477
|
+
# Washington's Birthday (Third Monday in Feb)
|
478
|
+
(year <= 1953 && month == 2 && day == 12) ||
|
479
|
+
(year <= 1970 ? (month == 2 && day == 22)
|
480
|
+
: nth_wday_in_month?(3, 1, 2))
|
481
|
+
when 3, 4
|
482
|
+
# Good Friday
|
483
|
+
if !friday?
|
841
484
|
false
|
485
|
+
elsif [1898, 1906, 1907].include?(year)
|
486
|
+
# Good Friday, the Friday before Easter, except certain years
|
487
|
+
false
|
488
|
+
else
|
489
|
+
(self + 2).easter?
|
842
490
|
end
|
843
|
-
|
844
|
-
#
|
845
|
-
|
846
|
-
|
847
|
-
#
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
491
|
+
when 5
|
492
|
+
# Memorial Day (Last Monday in May)
|
493
|
+
year <= 1970 ? (month == 5 && day == 30) : nth_wday_in_month?(-1, 1, 5)
|
494
|
+
when 9
|
495
|
+
# Labor Day (First Monday in Sep)
|
496
|
+
nth_wday_in_month?(1, 1, 9)
|
497
|
+
when 10
|
498
|
+
# Columbus Day (Oct 12) 1909--1953
|
499
|
+
year >= 1909 && year <= 1953 && day == 12
|
500
|
+
when 11
|
501
|
+
if tuesday?
|
502
|
+
# Election Day. Until 1968 all Election Days. From 1972 to 1980
|
503
|
+
# Election Day in presidential years only. Election Day is the first
|
504
|
+
# Tuesday after the first Monday in November.
|
505
|
+
first_tuesday = ::Date.nth_wday_in_year_month(1, 1, year, 11) + 1
|
506
|
+
is_election_day = (self == first_tuesday)
|
507
|
+
if year <= 1968
|
508
|
+
is_election_day
|
509
|
+
elsif year <= 1980
|
510
|
+
is_election_day && (year % 4).zero?
|
511
|
+
else
|
512
|
+
false
|
513
|
+
end
|
514
|
+
elsif thursday?
|
515
|
+
# Historically Thanksgiving (NYSE closed all day) had been declared to
|
516
|
+
# be the last Thursday in November until 1938; the next-to-last
|
517
|
+
# Thursday in November from 1939 to 1941 (therefore the 3rd Thursday
|
518
|
+
# in 1940 and 1941); the last Thursday in November in 1942; the fourth
|
519
|
+
# Thursday in November since 1943;
|
520
|
+
if year < 1938
|
521
|
+
nth_wday_in_month?(-1, 4, 11)
|
522
|
+
elsif year <= 1941
|
523
|
+
nth_wday_in_month?(3, 4, 11)
|
524
|
+
elsif year == 1942
|
525
|
+
nth_wday_in_month?(-1, 4, 11)
|
526
|
+
else
|
527
|
+
nth_wday_in_month?(4, 4, 11)
|
528
|
+
end
|
529
|
+
elsif day == 11
|
530
|
+
# Armistice or Veterans Day. 1918--1921; 1934--1953.
|
531
|
+
(year >= 1918 && year <= 1921) || (year >= 1934 && year <= 1953)
|
855
532
|
else
|
856
|
-
|
533
|
+
false
|
857
534
|
end
|
858
|
-
elsif day == 11
|
859
|
-
# Armistice or Veterans Day. 1918--1921; 1934--1953.
|
860
|
-
(year >= 1918 && year <= 1921) || (year >= 1934 && year <= 1953)
|
861
535
|
else
|
862
536
|
false
|
863
537
|
end
|
864
|
-
else
|
865
|
-
false
|
866
538
|
end
|
867
|
-
end
|
868
539
|
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
540
|
+
# They NYSE has closed on several occasions outside its normal holiday
|
541
|
+
# rules. This detects those dates beginning in 1960. Closing for part of a
|
542
|
+
# day is not counted. See http://www1.nyse.com/pdfs/closings.pdf
|
543
|
+
def nyse_special_holiday?
|
544
|
+
return false unless self > ::Date.parse('1960-01-01')
|
545
|
+
case self
|
546
|
+
when ::Date.parse('1961-05-29')
|
547
|
+
# Day before Decoaration Day
|
548
|
+
true
|
549
|
+
when ::Date.parse('1963-11-25')
|
550
|
+
# President Kennedy's funeral
|
551
|
+
true
|
552
|
+
when ::Date.parse('1965-12-24')
|
553
|
+
# Christmas eve unscheduled for normal holiday
|
554
|
+
true
|
555
|
+
when ::Date.parse('1968-02-12')
|
556
|
+
# Lincoln birthday
|
557
|
+
true
|
558
|
+
when ::Date.parse('1968-04-09')
|
559
|
+
# Mourning MLK
|
560
|
+
true
|
561
|
+
when ::Date.parse('1968-07-05')
|
562
|
+
# Day after Independence Day
|
563
|
+
true
|
564
|
+
when (::Date.parse('1968-06-12')..::Date.parse('1968-12-31'))
|
565
|
+
# Paperwork crisis (closed on Wednesdays if no other holiday in week)
|
566
|
+
wednesday? && (self - 2).nyse_workday? && (self - 1).nyse_workday? &&
|
567
|
+
(self + 1).nyse_workday? && (self + 2).nyse_workday?
|
568
|
+
when ::Date.parse('1969-02-10')
|
569
|
+
# Heavy snow
|
570
|
+
true
|
571
|
+
when ::Date.parse('1969-05-31')
|
572
|
+
# Eisenhower Funeral
|
573
|
+
true
|
574
|
+
when ::Date.parse('1969-07-21')
|
575
|
+
# Moon landing
|
576
|
+
true
|
577
|
+
when ::Date.parse('1972-12-28')
|
578
|
+
# Truman Funeral
|
579
|
+
true
|
580
|
+
when ::Date.parse('1973-01-25')
|
581
|
+
# Johnson Funeral
|
582
|
+
true
|
583
|
+
when ::Date.parse('1977-07-14')
|
584
|
+
# Electrical blackout NYC
|
585
|
+
true
|
586
|
+
when ::Date.parse('1985-09-27')
|
587
|
+
# Hurricane Gloria
|
588
|
+
true
|
589
|
+
when ::Date.parse('1994-04-27')
|
590
|
+
# Nixon Funeral
|
591
|
+
true
|
592
|
+
when (::Date.parse('2001-09-11')..::Date.parse('2001-09-14'))
|
593
|
+
# 9-11 Attacks
|
594
|
+
a = a
|
595
|
+
true
|
596
|
+
when (::Date.parse('2004-06-11')..::Date.parse('2001-09-14'))
|
597
|
+
# Reagan Funeral
|
598
|
+
true
|
599
|
+
when ::Date.parse('2007-01-02')
|
600
|
+
# Observance death of President Ford
|
601
|
+
true
|
602
|
+
when ::Date.parse('2012-10-29'), ::Date.parse('2012-10-30')
|
603
|
+
# Hurricane Sandy
|
604
|
+
true
|
605
|
+
else
|
606
|
+
false
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def nyse_holiday?
|
611
|
+
# All Saturdays and Sundays are "holidays"
|
612
|
+
return true if weekend?
|
613
|
+
|
614
|
+
# Is self a fixed holiday
|
615
|
+
return true if nyse_fixed_holiday? || nyse_moveable_feast?
|
616
|
+
|
617
|
+
return true if nyse_special_holiday?
|
618
|
+
|
619
|
+
if friday? && (self >= ::Date.parse('1959-07-03'))
|
620
|
+
# A Friday is a holiday if a holiday would fall on the following
|
621
|
+
# Saturday. The rule does not apply if the Friday "ends a monthly or
|
622
|
+
# yearly accounting period." Adopted July 3, 1959. E.g, December 31,
|
623
|
+
# 2010, fell on a Friday, so New Years was on Saturday, but the NYSE
|
624
|
+
# opened because it ended a yearly accounting period. I believe 12/31
|
625
|
+
# is the only date to which the exception can apply since only New
|
626
|
+
# Year's can fall on the first of the month.
|
627
|
+
!end_of_quarter? &&
|
628
|
+
((self + 1).nyse_fixed_holiday? || (self + 1).nyse_moveable_feast?)
|
629
|
+
elsif monday?
|
630
|
+
# A Monday is a holiday if a holiday would fall on the
|
631
|
+
# preceding Sunday. This has apparently always been the rule.
|
632
|
+
(self - 1).nyse_fixed_holiday? || (self - 1).nyse_moveable_feast?
|
633
|
+
else
|
634
|
+
false
|
635
|
+
end
|
935
636
|
end
|
936
|
-
end
|
937
637
|
|
938
|
-
|
939
|
-
|
940
|
-
return true if weekend?
|
941
|
-
|
942
|
-
# Is self a fixed holiday
|
943
|
-
return true if nyse_fixed_holiday? || nyse_moveable_feast?
|
944
|
-
|
945
|
-
return true if nyse_special_holiday?
|
946
|
-
|
947
|
-
if friday? && (self >= Date.parse('1959-07-03'))
|
948
|
-
# A Friday is a holiday if a holiday would fall on the following
|
949
|
-
# Saturday. The rule does not apply if the Friday "ends a monthly or
|
950
|
-
# yearly accounting period." Adopted July 3, 1959. E.g, December 31,
|
951
|
-
# 2010, fell on a Friday, so New Years was on Saturday, but the NYSE
|
952
|
-
# opened because it ended a yearly accounting period. I believe 12/31
|
953
|
-
# is the only date to which the exception can apply since only New
|
954
|
-
# Year's can fall on the first of the month.
|
955
|
-
!end_of_quarter? &&
|
956
|
-
((self + 1).nyse_fixed_holiday? || (self + 1).nyse_moveable_feast?)
|
957
|
-
elsif monday?
|
958
|
-
# A Monday is a holiday if a holiday would fall on the
|
959
|
-
# preceding Sunday. This has apparently always been the rule.
|
960
|
-
(self - 1).nyse_fixed_holiday? || (self - 1).nyse_moveable_feast?
|
961
|
-
else
|
962
|
-
false
|
638
|
+
def fed_workday?
|
639
|
+
!fed_holiday?
|
963
640
|
end
|
964
|
-
end
|
965
641
|
|
966
|
-
|
967
|
-
|
968
|
-
|
642
|
+
def nyse_workday?
|
643
|
+
!nyse_holiday?
|
644
|
+
end
|
645
|
+
alias trading_day? nyse_workday?
|
646
|
+
|
647
|
+
def add_fed_business_days(n)
|
648
|
+
d = dup
|
649
|
+
return d if n.zero?
|
650
|
+
incr = n.negative? ? -1 : 1
|
651
|
+
n = n.abs
|
652
|
+
while n.positive?
|
653
|
+
d += incr
|
654
|
+
n -= 1 if d.fed_workday?
|
655
|
+
end
|
656
|
+
d
|
657
|
+
end
|
969
658
|
|
970
|
-
|
971
|
-
|
972
|
-
end
|
973
|
-
alias trading_day? nyse_workday?
|
974
|
-
|
975
|
-
def add_fed_business_days(n)
|
976
|
-
d = dup
|
977
|
-
return d if n.zero?
|
978
|
-
incr = n.negative? ? -1 : 1
|
979
|
-
n = n.abs
|
980
|
-
while n.positive?
|
981
|
-
d += incr
|
982
|
-
n -= 1 if d.fed_workday?
|
659
|
+
def next_fed_workday
|
660
|
+
add_fed_business_days(1)
|
983
661
|
end
|
984
|
-
d
|
985
|
-
end
|
986
662
|
|
987
|
-
|
988
|
-
|
989
|
-
|
663
|
+
def prior_fed_workday
|
664
|
+
add_fed_business_days(-1)
|
665
|
+
end
|
990
666
|
|
991
|
-
|
992
|
-
|
993
|
-
|
667
|
+
def add_nyse_business_days(n)
|
668
|
+
d = dup
|
669
|
+
return d if n.zero?
|
670
|
+
incr = n.negative? ? -1 : 1
|
671
|
+
n = n.abs
|
672
|
+
while n.positive?
|
673
|
+
d += incr
|
674
|
+
n -= 1 if d.nyse_workday?
|
675
|
+
end
|
676
|
+
d
|
677
|
+
end
|
678
|
+
alias add_trading_days add_nyse_business_days
|
994
679
|
|
995
|
-
|
996
|
-
|
997
|
-
return d if n.zero?
|
998
|
-
incr = n.negative? ? -1 : 1
|
999
|
-
n = n.abs
|
1000
|
-
while n.positive?
|
1001
|
-
d += incr
|
1002
|
-
n -= 1 if d.nyse_workday?
|
680
|
+
def next_nyse_workday
|
681
|
+
add_nyse_business_days(1)
|
1003
682
|
end
|
1004
|
-
|
1005
|
-
end
|
1006
|
-
alias add_trading_days add_nyse_business_days
|
683
|
+
alias next_trading_day next_nyse_workday
|
1007
684
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
685
|
+
def prior_nyse_workday
|
686
|
+
add_nyse_business_days(-1)
|
687
|
+
end
|
688
|
+
alias prior_trading_day prior_nyse_workday
|
689
|
+
|
690
|
+
# Return self if its a trading day, otherwise skip back to the first prior
|
691
|
+
# trading day.
|
692
|
+
def prior_until_trading_day
|
693
|
+
date = dup
|
694
|
+
date -= 1 until date.trading_day?
|
695
|
+
date
|
696
|
+
end
|
1012
697
|
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
698
|
+
# Return self if its a trading day, otherwise skip forward to the first
|
699
|
+
# later trading day.
|
700
|
+
def next_until_trading_day
|
701
|
+
date = dup
|
702
|
+
date += 1 until date.trading_day?
|
703
|
+
date
|
704
|
+
end
|
705
|
+
|
706
|
+
module ClassMethods
|
707
|
+
# Convert a string with an American style date into a Date object
|
708
|
+
#
|
709
|
+
# An American style date is of the form MM/DD/YYYY, that is it places the
|
710
|
+
# month first, then the day of the month, and finally the year. The
|
711
|
+
# European convention is to place the day of the month first, DD/MM/YYYY.
|
712
|
+
# Because a date found in the wild can be ambiguous, e.g. 3/5/2014, a date
|
713
|
+
# string known to be using the American convention can be parsed using this
|
714
|
+
# method. Both the month and the day can be a single digit. The year can
|
715
|
+
# be either 2 or 4 digits, and if given as 2 digits, it adds 2000 to it to
|
716
|
+
# give the year.
|
717
|
+
#
|
718
|
+
# @example
|
719
|
+
# Date.parse_american('9/11/2001') #=> Date(2011, 9, 11)
|
720
|
+
# Date.parse_american('9/11/01') #=> Date(2011, 9, 11)
|
721
|
+
# Date.parse_american('9/11/1') #=> ArgumentError
|
722
|
+
#
|
723
|
+
# @param str [#to_s] a stringling of the form MM/DD/YYYY
|
724
|
+
# @return [Date] the date represented by the string paramenter.
|
725
|
+
def parse_american(str)
|
726
|
+
unless str.to_s =~ %r{\A\s*(\d\d?)\s*/\s*(\d\d?)\s*/\s*(\d?\d?\d\d)\s*\z}
|
727
|
+
raise ArgumentError, "date string must be of form 'MM?/DD?/YY(YY)?'"
|
728
|
+
end
|
729
|
+
year = $3.to_i
|
730
|
+
month = $1.to_i
|
731
|
+
day = $2.to_i
|
732
|
+
year += 2000 if year < 100
|
733
|
+
::Date.new(year, month, day)
|
734
|
+
end
|
735
|
+
|
736
|
+
# Convert a 'date spec' to a Date. A date spec is a short-hand way of
|
737
|
+
# specifying a date, relative to the computer clock. A date spec can
|
738
|
+
# interpreted as either a 'from spec' or a 'to spec'.
|
739
|
+
# @example
|
740
|
+
#
|
741
|
+
# Assuming that Date.current at the time of execution is 2014-07-26 and
|
742
|
+
# using the default spec_type of :from. The return values are actually Date
|
743
|
+
# objects, but are shown below as textual dates.
|
744
|
+
#
|
745
|
+
# A fully specified date returns that date:
|
746
|
+
# Date.parse_spec('2001-09-11') # =>
|
747
|
+
# Commercial weeks can be specified using, for example W32 or 32W, with the
|
748
|
+
# week beginning on Monday, ending on Sunday.
|
749
|
+
# Date.parse_spec('2012-W32') # =>
|
750
|
+
# Date.parse_spec('2012-W32', :to) # =>
|
751
|
+
# Date.parse_spec('W32') # =>
|
752
|
+
#
|
753
|
+
# A spec of the form Q3 or 3Q returns the beginning or end of calendar
|
754
|
+
# quarters.
|
755
|
+
# Date.parse_spec('Q3') # =>
|
756
|
+
#
|
757
|
+
# @param spec [#to_s] a stringling containing the spec to be interpreted
|
758
|
+
# @param spec_type [:from, :to] interpret the spec as a from- or to-spec
|
759
|
+
# respectively, defaulting to interpretation as a to-spec.
|
760
|
+
# @return [Date] a date object equivalent to the date spec
|
761
|
+
def parse_spec(spec, spec_type = :from)
|
762
|
+
spec = spec.to_s.strip
|
763
|
+
unless [:from, :to].include?(spec_type)
|
764
|
+
raise ArgumentError, "invalid date spec type: '#{spec_type}'"
|
765
|
+
end
|
766
|
+
|
767
|
+
today = ::Date.current
|
768
|
+
case spec.clean
|
769
|
+
when /\A(\d\d\d\d)[-\/](\d\d?)[-\/](\d\d?)\z/
|
770
|
+
# A specified date
|
771
|
+
::Date.new($1.to_i, $2.to_i, $3.to_i)
|
772
|
+
when /\AW(\d\d?)\z/, /\A(\d\d?)W\z/
|
773
|
+
week_num = $1.to_i
|
774
|
+
if week_num < 1 || week_num > 53
|
775
|
+
raise ArgumentError, "invalid week number (1-53): 'W#{week_num}'"
|
776
|
+
end
|
777
|
+
if spec_type == :from
|
778
|
+
::Date.commercial(today.year, week_num).beginning_of_week
|
779
|
+
else
|
780
|
+
::Date.commercial(today.year, week_num).end_of_week
|
781
|
+
end
|
782
|
+
when /\A(\d\d\d\d)[-\/]W(\d\d?)\z/, /\A(\d\d\d\d)[-\/](\d\d?)W\z/
|
783
|
+
year = $1.to_i
|
784
|
+
week_num = $2.to_i
|
785
|
+
if week_num < 1 || week_num > 53
|
786
|
+
raise ArgumentError, "invalid week number (1-53): 'W#{week_num}'"
|
787
|
+
end
|
788
|
+
if spec_type == :from
|
789
|
+
::Date.commercial(year, week_num).beginning_of_week
|
790
|
+
else
|
791
|
+
::Date.commercial(year, week_num).end_of_week
|
792
|
+
end
|
793
|
+
when /^(\d\d\d\d)[-\/](\d)[Qq]$/, /^(\d\d\d\d)[-\/][Qq](\d)$/
|
794
|
+
# Year-Quarter
|
795
|
+
year = $1.to_i
|
796
|
+
quarter = $2.to_i
|
797
|
+
unless [1, 2, 3, 4].include?(quarter)
|
798
|
+
raise ArgumentError, "bad date format: #{spec}"
|
799
|
+
end
|
800
|
+
month = quarter * 3
|
801
|
+
if spec_type == :from
|
802
|
+
::Date.new(year, month, 1).beginning_of_quarter
|
803
|
+
else
|
804
|
+
::Date.new(year, month, 1).end_of_quarter
|
805
|
+
end
|
806
|
+
when /^([1234])[qQ]$/, /^[qQ]([1234])$/
|
807
|
+
# Quarter only
|
808
|
+
this_year = today.year
|
809
|
+
quarter = $1.to_i
|
810
|
+
date = ::Date.new(this_year, quarter * 3, 15)
|
811
|
+
if spec_type == :from
|
812
|
+
date.beginning_of_quarter
|
813
|
+
else
|
814
|
+
date.end_of_quarter
|
815
|
+
end
|
816
|
+
when /^(\d\d\d\d)[-\/](\d)[Hh]$/, /^(\d\d\d\d)[-\/][Hh](\d)$/
|
817
|
+
# Year-Half
|
818
|
+
year = $1.to_i
|
819
|
+
half = $2.to_i
|
820
|
+
unless [1, 2].include?(half)
|
821
|
+
raise ArgumentError, "bad date format: #{spec}"
|
822
|
+
end
|
823
|
+
month = half * 6
|
824
|
+
if spec_type == :from
|
825
|
+
::Date.new(year, month, 15).beginning_of_half
|
826
|
+
else
|
827
|
+
::Date.new(year, month, 1).end_of_half
|
828
|
+
end
|
829
|
+
when /^([12])[hH]$/, /^[hH]([12])$/
|
830
|
+
# Half only
|
831
|
+
this_year = today.year
|
832
|
+
half = $1.to_i
|
833
|
+
date = ::Date.new(this_year, half * 6, 15)
|
834
|
+
if spec_type == :from
|
835
|
+
date.beginning_of_half
|
836
|
+
else
|
837
|
+
date.end_of_half
|
838
|
+
end
|
839
|
+
when /^(\d\d\d\d)[-\/](\d\d?)*$/
|
840
|
+
# Year-Month only
|
841
|
+
if spec_type == :from
|
842
|
+
::Date.new($1.to_i, $2.to_i, 1)
|
843
|
+
else
|
844
|
+
::Date.new($1.to_i, $2.to_i, 1).end_of_month
|
845
|
+
end
|
846
|
+
when /^(\d\d?)[-\/](\d\d?)*$/
|
847
|
+
# Month-Day only
|
848
|
+
if spec_type == :from
|
849
|
+
::Date.new(today.year, $1.to_i, $2.to_i)
|
850
|
+
else
|
851
|
+
::Date.new(today.year, $1.to_i, $2.to_i).end_of_month
|
852
|
+
end
|
853
|
+
when /\A(\d\d?)\z/
|
854
|
+
# Month only
|
855
|
+
if spec_type == :from
|
856
|
+
::Date.new(today.year, $1.to_i, 1)
|
857
|
+
else
|
858
|
+
::Date.new(today.year, $1.to_i, 1).end_of_month
|
859
|
+
end
|
860
|
+
when /^(\d\d\d\d)$/
|
861
|
+
# Year only
|
862
|
+
if spec_type == :from
|
863
|
+
::Date.new($1.to_i, 1, 1)
|
864
|
+
else
|
865
|
+
::Date.new($1.to_i, 12, 31)
|
866
|
+
end
|
867
|
+
when /^(to|this_?)?day/
|
868
|
+
today
|
869
|
+
when /^(yester|last_?)?day/
|
870
|
+
today - 1.day
|
871
|
+
when /^(this_?)?week/
|
872
|
+
spec_type == :from ? today.beginning_of_week : today.end_of_week
|
873
|
+
when /last_?week/
|
874
|
+
if spec_type == :from
|
875
|
+
(today - 1.week).beginning_of_week
|
876
|
+
else
|
877
|
+
(today - 1.week).end_of_week
|
878
|
+
end
|
879
|
+
when /^(this_?)?biweek/
|
880
|
+
if spec_type == :from
|
881
|
+
today.beginning_of_biweek
|
882
|
+
else
|
883
|
+
today.end_of_biweek
|
884
|
+
end
|
885
|
+
when /last_?biweek/
|
886
|
+
if spec_type == :from
|
887
|
+
(today - 2.week).beginning_of_biweek
|
888
|
+
else
|
889
|
+
(today - 2.week).end_of_biweek
|
890
|
+
end
|
891
|
+
when /^(this_?)?semimonth/
|
892
|
+
spec_type == :from ? today.beginning_of_semimonth : today.end_of_semimonth
|
893
|
+
when /^last_?semimonth/
|
894
|
+
if spec_type == :from
|
895
|
+
(today - 15.days).beginning_of_semimonth
|
896
|
+
else
|
897
|
+
(today - 15.days).end_of_semimonth
|
898
|
+
end
|
899
|
+
when /^(this_?)?month/
|
900
|
+
if spec_type == :from
|
901
|
+
today.beginning_of_month
|
902
|
+
else
|
903
|
+
today.end_of_month
|
904
|
+
end
|
905
|
+
when /^last_?month/
|
906
|
+
if spec_type == :from
|
907
|
+
(today - 1.month).beginning_of_month
|
908
|
+
else
|
909
|
+
(today - 1.month).end_of_month
|
910
|
+
end
|
911
|
+
when /^(this_?)?bimonth/
|
912
|
+
if spec_type == :from
|
913
|
+
today.beginning_of_bimonth
|
914
|
+
else
|
915
|
+
today.end_of_bimonth
|
916
|
+
end
|
917
|
+
when /^last_?bimonth/
|
918
|
+
if spec_type == :from
|
919
|
+
(today - 2.month).beginning_of_bimonth
|
920
|
+
else
|
921
|
+
(today - 2.month).end_of_bimonth
|
922
|
+
end
|
923
|
+
when /^(this_?)?quarter/
|
924
|
+
if spec_type == :from
|
925
|
+
today.beginning_of_quarter
|
926
|
+
else
|
927
|
+
today.end_of_quarter
|
928
|
+
end
|
929
|
+
when /^last_?quarter/
|
930
|
+
if spec_type == :from
|
931
|
+
(today - 3.months).beginning_of_quarter
|
932
|
+
else
|
933
|
+
(today - 3.months).end_of_quarter
|
934
|
+
end
|
935
|
+
when /^(this_?)?half/
|
936
|
+
if spec_type == :from
|
937
|
+
today.beginning_of_half
|
938
|
+
else
|
939
|
+
today.end_of_half
|
940
|
+
end
|
941
|
+
when /^last_?half/
|
942
|
+
if spec_type == :from
|
943
|
+
(today - 6.months).beginning_of_half
|
944
|
+
else
|
945
|
+
(today - 6.months).end_of_half
|
946
|
+
end
|
947
|
+
when /^(this_?)?year/
|
948
|
+
if spec_type == :from
|
949
|
+
today.beginning_of_year
|
950
|
+
else
|
951
|
+
today.end_of_year
|
952
|
+
end
|
953
|
+
when /^last_?year/
|
954
|
+
if spec_type == :from
|
955
|
+
(today - 1.year).beginning_of_year
|
956
|
+
else
|
957
|
+
(today - 1.year).end_of_year
|
958
|
+
end
|
959
|
+
when /^forever/
|
960
|
+
if spec_type == :from
|
961
|
+
::Date::BOT
|
962
|
+
else
|
963
|
+
::Date::EOT
|
964
|
+
end
|
965
|
+
when /^never/
|
966
|
+
nil
|
967
|
+
else
|
968
|
+
raise ArgumentError, "bad date spec: '#{spec}''"
|
969
|
+
end # !> previous definition of length was here
|
970
|
+
end
|
971
|
+
|
972
|
+
COMMON_YEAR_DAYS_IN_MONTH = [31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31,
|
973
|
+
30, 31]
|
974
|
+
def days_in_month(y, m)
|
975
|
+
raise ArgumentError, 'illegal month number' if m < 1 || m > 12
|
976
|
+
days = COMMON_YEAR_DAYS_IN_MONTH[m]
|
977
|
+
if m == 2
|
978
|
+
::Date.new(y, m, 1).leap? ? 29 : 28
|
979
|
+
else
|
980
|
+
days
|
981
|
+
end
|
982
|
+
end
|
1025
983
|
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
984
|
+
def nth_wday_in_year_month(n, wday, year, month)
|
985
|
+
# Return the nth weekday in the given month
|
986
|
+
# If n is negative, count from last day of month
|
987
|
+
wday = wday.to_i
|
988
|
+
raise ArgumentError, 'illegal weekday number' if wday < 0 || wday > 6
|
989
|
+
month = month.to_i
|
990
|
+
raise ArgumentError, 'illegal month number' if month < 1 || month > 12
|
991
|
+
n = n.to_i
|
992
|
+
if n.positive?
|
993
|
+
# Set d to the 1st wday in month
|
994
|
+
d = ::Date.new(year, month, 1)
|
995
|
+
d += 1 while d.wday != wday
|
996
|
+
# Set d to the nth wday in month
|
997
|
+
nd = 1
|
998
|
+
while nd != n
|
999
|
+
d += 7
|
1000
|
+
nd += 1
|
1001
|
+
end
|
1002
|
+
d
|
1003
|
+
elsif n.negative?
|
1004
|
+
n = -n
|
1005
|
+
# Set d to the last wday in month
|
1006
|
+
d = ::Date.new(year, month, 1).end_of_month
|
1007
|
+
d -= 1 while d.wday != wday
|
1008
|
+
# Set d to the nth wday in month
|
1009
|
+
nd = 1
|
1010
|
+
while nd != n
|
1011
|
+
d -= 7
|
1012
|
+
nd += 1
|
1013
|
+
end
|
1014
|
+
d
|
1015
|
+
else
|
1016
|
+
raise ArgumentError,
|
1017
|
+
'Arg 1 to nth_wday_in_month_year cannot be zero'
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
def easter(year)
|
1022
|
+
y = year
|
1023
|
+
a = y % 19
|
1024
|
+
b, c = y.divmod(100)
|
1025
|
+
d, e = b.divmod(4)
|
1026
|
+
f = (b + 8) / 25
|
1027
|
+
g = (b - f + 1) / 3
|
1028
|
+
h = (19 * a + b - d - g + 15) % 30
|
1029
|
+
i, k = c.divmod(4)
|
1030
|
+
l = (32 + 2 * e + 2 * i - h - k) % 7
|
1031
|
+
m = (a + 11 * h + 22 * l) / 451
|
1032
|
+
n, p = (h + l - 7 * m + 114).divmod(31)
|
1033
|
+
::Date.new(y, n, p + 1)
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
# This hook gets called by the host class when it includes this
|
1038
|
+
# module, extending that class to include the methods defined in
|
1039
|
+
# ClassMethods as class methods of the host class.
|
1040
|
+
def self.included(host_class)
|
1041
|
+
host_class.extend(ClassMethods)
|
1042
|
+
end
|
1032
1043
|
end
|
1033
1044
|
end
|
1045
|
+
|
1046
|
+
Date.include(FatCore::Date)
|