fat_core 4.7.0 → 4.7.3
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/README.org +377 -0
- data/fat_core.gemspec +3 -3
- data/lib/fat_core/bigdecimal.rb +2 -0
- data/lib/fat_core/date.rb +217 -149
- data/lib/fat_core/enumerable.rb +6 -6
- data/lib/fat_core/hash.rb +9 -10
- data/lib/fat_core/numeric.rb +5 -2
- data/lib/fat_core/patches.rb +2 -0
- data/lib/fat_core/range.rb +6 -1
- data/lib/fat_core/string.rb +25 -28
- data/lib/fat_core/version.rb +1 -1
- data/spec/lib/date_spec.rb +19 -1
- data/spec/lib/string_spec.rb +10 -3
- metadata +9 -9
- data/README.md +0 -165
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66c2dafd995954b7775a200600f938570b7bc5ad0fd5e240fc8fabb1496b69ba
|
4
|
+
data.tar.gz: a3b3ff6b1dfd0a81afd0ed89b24501e7ade68e9b14e40e8a3da54a59e79f39a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5ada6fefbf2b7cd2a5adbc7f62bc207ad7b8efb55e52874ec0b6e0a8e46a7bd5bc4d5e35f135fe20ea8a6b619f28e14adecf38ea5a2b01b92dfc5aaaf9b5f31
|
7
|
+
data.tar.gz: 621a7ace7268390f3dd900bc0dc7ac38c2a33381cb54bda460a4f8b0f7d8b273b907007299db2c893518c99f846325312095dd7a24b63783da800909487ade51
|
data/README.org
ADDED
@@ -0,0 +1,377 @@
|
|
1
|
+
[[https://travis-ci.org/ddoherty03/fat_core.svg?branch=master]]
|
2
|
+
|
3
|
+
* FatCore
|
4
|
+
|
5
|
+
fat-core is a simple gem to collect core extensions and a few new classes that
|
6
|
+
I find useful in multiple projects. The emphasis is on extending the Date
|
7
|
+
class to make it more useful in financial applications.
|
8
|
+
|
9
|
+
** Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
#+begin_SRC ruby
|
14
|
+
gem 'fat_core', :git => 'https://github.com/ddoherty03/fat_core.git'
|
15
|
+
#+end_SRC
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
#+begin_src shell
|
20
|
+
$ bundle
|
21
|
+
#+end_src
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
#+begin_src shell
|
26
|
+
$ gem install fat_core
|
27
|
+
#+end_src
|
28
|
+
|
29
|
+
** Usage
|
30
|
+
|
31
|
+
You can extend classes individually by requiring the corresponding file:
|
32
|
+
|
33
|
+
#+begin_SRC ruby
|
34
|
+
require 'fat_core/array'
|
35
|
+
require 'fat_core/bigdecimal'
|
36
|
+
require 'fat_core/date'
|
37
|
+
require 'fat_core/enumerable'
|
38
|
+
require 'fat_core/hash'
|
39
|
+
require 'fat_core/kernel'
|
40
|
+
require 'fat_core/numeric'
|
41
|
+
require 'fat_core/range'
|
42
|
+
require 'fat_core/string'
|
43
|
+
require 'fat_core/symbol'
|
44
|
+
#+end_SRC
|
45
|
+
|
46
|
+
|
47
|
+
Or, you can require them all:
|
48
|
+
|
49
|
+
#+begin_SRC ruby
|
50
|
+
require 'fat_core/all'
|
51
|
+
#+end_SRC
|
52
|
+
|
53
|
+
Many of these have little that is of general interest, but there are a few
|
54
|
+
goodies.
|
55
|
+
|
56
|
+
*** Date
|
57
|
+
|
58
|
+
**** Constants
|
59
|
+
|
60
|
+
~FatCore~ adds two constants to the ~Date~ class, Date::BOT and Date::EOT.
|
61
|
+
These represent the earliest and latest dates of practical commercial
|
62
|
+
interest. The exact values are rather arbitrary, but they prove useful in
|
63
|
+
date ranges, for example. They are defined as:
|
64
|
+
|
65
|
+
- ~Date::BOT~ :: January 1, 1900
|
66
|
+
- ~Date::EOT~ :: December 31, 3000
|
67
|
+
- ~Date::FEDERAL_DECREED_HOLIDAYS~ :: an Array of dates declared as non-work
|
68
|
+
days for federal employees by presidential proclamation
|
69
|
+
- ~Date::PRESIDENTIAL_FUNERALS~ :: an Array of dates of presidential funerals,
|
70
|
+
which are observed with a closing of most federal agencies
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
**** Formatting
|
75
|
+
|
76
|
+
~FatCore~ provides some concise methods for printing string versions of dates
|
77
|
+
that are often useful:
|
78
|
+
|
79
|
+
#+begin_SRC ruby :results output :wrap example :exports both
|
80
|
+
require 'fat_core/date'
|
81
|
+
d = Date.parse('1957-09-22')
|
82
|
+
puts "ISO: #{d.iso}"
|
83
|
+
puts "All Numbers: #{d.num}"
|
84
|
+
puts "Emacs Org Mode Inactive: #{d.org}"
|
85
|
+
puts "Emacs Org Mode Active: #{d.org(true)}"
|
86
|
+
puts "LaTeX: #{d.tex_quote}"
|
87
|
+
puts "English: #{d.eng}"
|
88
|
+
puts "American: #{d.american}"
|
89
|
+
#+end_SRC
|
90
|
+
|
91
|
+
#+RESULTS:
|
92
|
+
#+begin_example
|
93
|
+
ISO: 1957-09-22
|
94
|
+
All Numbers: 19570922
|
95
|
+
Emacs Org Mode Inactive: [1957-09-22 Sun]
|
96
|
+
Emacs Org Mode Active: <1957-09-22 Sun>
|
97
|
+
LaTeX: 1957--09--22
|
98
|
+
English: September 22, 1957
|
99
|
+
American: 9/22/1957
|
100
|
+
#+end_example
|
101
|
+
|
102
|
+
Most of these are self-explanatory, but a couple are not. The ~#org~ method
|
103
|
+
formats a date as an Emacs org-mode timestamp, by default an inactive
|
104
|
+
timestamp that does not show up in the org agenda, but can be made active with
|
105
|
+
the optional parameter set to a truthy value. See
|
106
|
+
[[https://orgmode.org/manual/Timestamps.html#Timestamps]].
|
107
|
+
|
108
|
+
The ~#tex_quote~ method formats the date in iso form but using TeX's
|
109
|
+
convention of using en-dashes to separate the components.
|
110
|
+
|
111
|
+
**** Chunks
|
112
|
+
|
113
|
+
Many of the methods provided by ~FatCore~ deal with various calendar periods
|
114
|
+
that are less common than those provided by the Ruby Standard Library or gems
|
115
|
+
such as ~active_suupor~. This documentation refers to these calendar periods
|
116
|
+
as "chunks", and they are the following:
|
117
|
+
|
118
|
+
- year,
|
119
|
+
- half,
|
120
|
+
- quarter,
|
121
|
+
- bimonth,
|
122
|
+
- month,
|
123
|
+
- semimonth,
|
124
|
+
- biweek,
|
125
|
+
- week, and
|
126
|
+
- day
|
127
|
+
|
128
|
+
~FatCore~ provides methods that query whether the date falls on the beginning
|
129
|
+
or end of each of these chunks:
|
130
|
+
|
131
|
+
#+begin_SRC ruby :wrap example :exports both
|
132
|
+
require 'fat_core/date'
|
133
|
+
|
134
|
+
tab = []
|
135
|
+
d = Date.parse('2017-06-30')
|
136
|
+
%i[beginning end].each do |side|
|
137
|
+
%i(year half quarter bimonth month semimonth biweek week ).each do |chunk|
|
138
|
+
meth = "#{side}_of_#{chunk}?".to_sym
|
139
|
+
tab << [d.iso, meth.to_s, "#{d.send(meth)}"]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
tab
|
143
|
+
#+end_SRC
|
144
|
+
|
145
|
+
#+RESULTS:
|
146
|
+
#+begin_example
|
147
|
+
| 2017-06-30 | beginning_of_year? | false |
|
148
|
+
| 2017-06-30 | beginning_of_half? | false |
|
149
|
+
| 2017-06-30 | beginning_of_quarter? | false |
|
150
|
+
| 2017-06-30 | beginning_of_bimonth? | false |
|
151
|
+
| 2017-06-30 | beginning_of_month? | false |
|
152
|
+
| 2017-06-30 | beginning_of_semimonth? | false |
|
153
|
+
| 2017-06-30 | beginning_of_biweek? | false |
|
154
|
+
| 2017-06-30 | beginning_of_week? | false |
|
155
|
+
| 2017-06-30 | end_of_year? | false |
|
156
|
+
| 2017-06-30 | end_of_half? | true |
|
157
|
+
| 2017-06-30 | end_of_quarter? | true |
|
158
|
+
| 2017-06-30 | end_of_bimonth? | true |
|
159
|
+
| 2017-06-30 | end_of_month? | true |
|
160
|
+
| 2017-06-30 | end_of_semimonth? | true |
|
161
|
+
| 2017-06-30 | end_of_biweek? | false |
|
162
|
+
| 2017-06-30 | end_of_week? | false |
|
163
|
+
#+end_example
|
164
|
+
|
165
|
+
It also provides corresponding methods that return the date at the beginning
|
166
|
+
or end of the calendar chunk, starting at the given date:
|
167
|
+
|
168
|
+
#+begin_SRC ruby :wrap example :exports both
|
169
|
+
require 'fat_core/date'
|
170
|
+
|
171
|
+
tab = []
|
172
|
+
d = Date.parse('2017-04-21')
|
173
|
+
%i[beginning end].each do |side|
|
174
|
+
%i(year half quarter bimonth month semimonth biweek week ).each do |chunk|
|
175
|
+
meth = "#{side}_of_#{chunk}".to_sym
|
176
|
+
tab << [d.iso, "d.#{meth}", "#{d.send(meth)}"]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
tab
|
180
|
+
#+end_SRC
|
181
|
+
|
182
|
+
#+RESULTS:
|
183
|
+
#+begin_example
|
184
|
+
| 2017-04-21 | d.beginning_of_year | 2017-01-01 |
|
185
|
+
| 2017-04-21 | d.beginning_of_half | 2017-01-01 |
|
186
|
+
| 2017-04-21 | d.beginning_of_quarter | 2017-04-01 |
|
187
|
+
| 2017-04-21 | d.beginning_of_bimonth | 2017-03-01 |
|
188
|
+
| 2017-04-21 | d.beginning_of_month | 2017-04-01 |
|
189
|
+
| 2017-04-21 | d.beginning_of_semimonth | 2017-04-16 |
|
190
|
+
| 2017-04-21 | d.beginning_of_biweek | 2017-04-10 |
|
191
|
+
| 2017-04-21 | d.beginning_of_week | 2017-04-17 |
|
192
|
+
| 2017-04-21 | d.end_of_year | 2017-12-31 |
|
193
|
+
| 2017-04-21 | d.end_of_half | 2017-06-30 |
|
194
|
+
| 2017-04-21 | d.end_of_quarter | 2017-06-30 |
|
195
|
+
| 2017-04-21 | d.end_of_bimonth | 2017-04-30 |
|
196
|
+
| 2017-04-21 | d.end_of_month | 2017-04-30 |
|
197
|
+
| 2017-04-21 | d.end_of_semimonth | 2017-04-30 |
|
198
|
+
| 2017-04-21 | d.end_of_biweek | 2017-04-23 |
|
199
|
+
| 2017-04-21 | d.end_of_week | 2017-04-23 |
|
200
|
+
#+end_example
|
201
|
+
|
202
|
+
You can query which numerical half, quarter, etc. that a given date falls in:
|
203
|
+
|
204
|
+
#+begin_SRC ruby :exports both :wrap example
|
205
|
+
require 'fat_core/date'
|
206
|
+
|
207
|
+
tab = []
|
208
|
+
%i(year half quarter bimonth month semimonth biweek week ).each do |chunk|
|
209
|
+
d = Date.parse('2017-04-21') + rand(100)
|
210
|
+
meth = "#{chunk}".to_sym
|
211
|
+
tab << [d.iso, "d.#{meth}", "in #{chunk} number #{d.send(meth)}"]
|
212
|
+
end
|
213
|
+
tab
|
214
|
+
#+end_SRC
|
215
|
+
|
216
|
+
#+RESULTS:
|
217
|
+
#+begin_example
|
218
|
+
| 2017-07-05 | d.year | in year number 2017 |
|
219
|
+
| 2017-06-03 | d.half | in half number 1 |
|
220
|
+
| 2017-05-30 | d.quarter | in quarter number 2 |
|
221
|
+
| 2017-07-08 | d.bimonth | in bimonth number 4 |
|
222
|
+
| 2017-06-28 | d.month | in month number 6 |
|
223
|
+
| 2017-05-14 | d.semimonth | in semimonth number 9 |
|
224
|
+
| 2017-07-25 | d.biweek | in biweek number 15 |
|
225
|
+
| 2017-06-19 | d.week | in week number 25 |
|
226
|
+
#+end_example
|
227
|
+
|
228
|
+
**** Parsing
|
229
|
+
|
230
|
+
~FatCore~ also adds some convenience methods for parsing strings as ~Date~
|
231
|
+
objects.
|
232
|
+
|
233
|
+
***** American Dates
|
234
|
+
|
235
|
+
Americans often write dates in the form M/d/Y, and the normal parse method
|
236
|
+
will parse such a string as d/M/Y, often resulting in invalid date errors.
|
237
|
+
~FatCore~ adds the specialty parsing method, ~Date.parse_american~ to handle
|
238
|
+
such strings.
|
239
|
+
|
240
|
+
#+begin_SRC ruby :results output :exports both :wrap example
|
241
|
+
require 'fat_core/date'
|
242
|
+
|
243
|
+
begin
|
244
|
+
ss = '9/22/1957'
|
245
|
+
Date.parse(ss)
|
246
|
+
rescue Date::Error => ex
|
247
|
+
puts "Date.parse('#{ss}') raises #{ex.class} (#{ex}), but"
|
248
|
+
puts "Date.parse_american('#{ss}') => #{Date.parse_american(ss)}"
|
249
|
+
end
|
250
|
+
#+end_SRC
|
251
|
+
|
252
|
+
#+RESULTS:
|
253
|
+
#+begin_example
|
254
|
+
Date.parse('9/22/1957') raises Date::Error (invalid date), but
|
255
|
+
Date.parse_american('9/22/1957') => 1957-09-22
|
256
|
+
#+end_example
|
257
|
+
|
258
|
+
***** Date Specs
|
259
|
+
|
260
|
+
|
261
|
+
|
262
|
+
**** Holidays and Workdays
|
263
|
+
|
264
|
+
- weekend?
|
265
|
+
- weekday?
|
266
|
+
|
267
|
+
**** Weekdays in Month
|
268
|
+
|
269
|
+
**** Easter
|
270
|
+
|
271
|
+
|
272
|
+
The ~Date~ class extension adds two methods for determining whether a given
|
273
|
+
date is a US federal holiday as defined by federal law, including such things
|
274
|
+
as federal holidays established by executive decree:
|
275
|
+
|
276
|
+
#+begin_SRC ruby
|
277
|
+
require 'fat_core/date'
|
278
|
+
Date.parse('2014-05-18').fed_holiday? => true # It's a weekend
|
279
|
+
Date.parse('2014-01-01').fed_holiday? => true # It's New Years
|
280
|
+
#+end_SRC
|
281
|
+
|
282
|
+
Likewise, days on which the NYSE is closed can be gotten with:
|
283
|
+
|
284
|
+
#+begin_SRC ruby
|
285
|
+
Date.parse('2014-04-18').nyse_holiday? => true # It's Good Friday
|
286
|
+
#+end_SRC
|
287
|
+
|
288
|
+
Conversely, ~Date#fed_workday?~ and ~Date#nyse_workday?~ return true if the
|
289
|
+
federal government and the NYSE respectively are open for business on those
|
290
|
+
days.
|
291
|
+
|
292
|
+
In addition, the Date class, as extended by FatCore, adds ~#next_<chunk>~
|
293
|
+
methods for calendar periods in addition to those provided by the core Date
|
294
|
+
class: ~#next_half~, ~#next_quarter~, ~#next_bimonth~, and ~#next_semimonth~,
|
295
|
+
~#next_biweek~. There are also ~#prior_<chunk>~ variants of these, as well as
|
296
|
+
methods for finding the end and beginning of all these periods (e.g.,
|
297
|
+
~#beginning_of_bimonth~) and for querying whether a Date is at the beginning or
|
298
|
+
end of these periods (e.g., ~#beginning_of_bimonth?~, ~#end_of_bimonth?~, etc.).
|
299
|
+
|
300
|
+
FatCore also provides convenience formatting methods, such as ~Date#iso~ for
|
301
|
+
quickly converting a Date to a string of the form 'YYYY-MM-DD', ~Date#org~ for
|
302
|
+
formatting a Date as an Emacs org-mode timestamp, and several others.
|
303
|
+
|
304
|
+
Finally, it provides a ~#parse_spec~ method for parsing a string, typically
|
305
|
+
provided by a user, allowing all the period chunks to be conveniently and
|
306
|
+
tersely specified by a user. For example, the string '2Q' will be parsed as the
|
307
|
+
second calendar quarter of the current year, while '2014-3Q' will be parsed as
|
308
|
+
the third quarter of the year 2014.
|
309
|
+
|
310
|
+
*** Range
|
311
|
+
|
312
|
+
You can also extend the Range class with several useful methods that emphasize
|
313
|
+
coverage of one range by one or more others (~#spanned_by?~ and ~#gaps~),
|
314
|
+
contiguity of Ranges to one another (~#contiguous?~, ~#left_contiguous?~, and
|
315
|
+
~#right_contiguous?~, ~#join~), and the testing of overlaps between ranges
|
316
|
+
(~#overlaps?~, ~#overlaps_among?~). These are put to good use in the
|
317
|
+
'fat_period' ([[https://github.com/ddoherty03/fat_period]]) gem, which combines
|
318
|
+
fat_core's extended Range class with its extended Date class to make a useful
|
319
|
+
Period class for date ranges, and you may find fat_core's extended Range class
|
320
|
+
likewise useful.
|
321
|
+
|
322
|
+
For example, you can use the ~#gaps~ method to find the gaps left in the
|
323
|
+
coverage on one Range by an Array of other Ranges:
|
324
|
+
|
325
|
+
#+begin_SRC ruby
|
326
|
+
require 'fat_core/range'
|
327
|
+
(0..12).gaps([(0..2), (5..7), (10..12)]) => [(3..4), (8..9)]
|
328
|
+
#+end_SRC
|
329
|
+
|
330
|
+
**
|
331
|
+
* Enumerable
|
332
|
+
|
333
|
+
FatCore::Enumerable extends Enumerable with the ~#each_with_flags~ method that
|
334
|
+
yields the elements of the Enumerable but also yields two booleans, ~first~ and
|
335
|
+
~last~ that are set to true on respectively, the first and last element of the
|
336
|
+
Enumerable. This makes it easy to treat these two cases specially without
|
337
|
+
testing the index as in ~#each_with_index~.
|
338
|
+
|
339
|
+
*** Hash
|
340
|
+
|
341
|
+
FatCore::Hash extends the Hash class with some useful methods for element
|
342
|
+
deletion (~#delete_with_value~) and for manipulating the keys
|
343
|
+
(~#keys_with_value~, ~#remap_keys~ and ~#replace_keys~) of a Hash. It also
|
344
|
+
provides ~#each_pair_with_flags~ as an analog to Enumerable's
|
345
|
+
~#each_with_flags~.
|
346
|
+
|
347
|
+
*** String
|
348
|
+
|
349
|
+
FatCore::String has methods for performing matching of one string with another
|
350
|
+
(~#matches_with~, ~#fuzzy_match~), for converting a string to title-case as
|
351
|
+
might by used in the title of a book (~#entitle~), for converting a String into
|
352
|
+
a useable Symbol (~#as_sym~) and vice-versa (~#as_string~ also
|
353
|
+
~Symbol#as_string~), for wrapping with an optional hanging indent (~#wrap~),
|
354
|
+
cleaning up errant spaces (~#clean~), and computing the Damerau-Levenshtein
|
355
|
+
distance between strings (~#distance~). And several others.
|
356
|
+
|
357
|
+
*** TeX Quoting
|
358
|
+
|
359
|
+
Several of the extension, most notably 'fat_core/string', provides a
|
360
|
+
~#tex_quote~ method for quoting the string version of an object so as to allow
|
361
|
+
its inclusion in a TeX document and quote characters such as '$' or '%' that
|
362
|
+
have a special meaning for TeX.
|
363
|
+
|
364
|
+
*** Numbers
|
365
|
+
|
366
|
+
FatCore::Numeric has methods for inserting grouping commas into a number
|
367
|
+
(~#commas~ and ~#group~), for converting seconds to HH:MM:SS.dd format
|
368
|
+
(~#secs_to_hms~), for testing for integrality (~#whole?~ and ~#int_if_whole~), and
|
369
|
+
testing for sign (~#signum~).
|
370
|
+
|
371
|
+
** Contributing
|
372
|
+
|
373
|
+
1. Fork it ([[http://github.com/ddoherty03/fat_core/fork]] )
|
374
|
+
2. Create your feature branch (~git checkout -b my-new-feature~)
|
375
|
+
3. Commit your changes (~git commit -am 'Add some feature'~)
|
376
|
+
4. Push to the branch (~git push origin my-new-feature~)
|
377
|
+
5. Create new Pull Request
|
data/fat_core.gemspec
CHANGED
@@ -10,8 +10,8 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.authors = ['Daniel E. Doherty']
|
11
11
|
spec.email = ['ded@ddoherty.net']
|
12
12
|
spec.summary = 'fat_core provides some useful core extensions'
|
13
|
-
spec.description = '
|
14
|
-
spec.homepage = ''
|
13
|
+
spec.description = 'Useful extensions to Date, String, Range and other classes'
|
14
|
+
spec.homepage = 'https://github.com/ddoherty03/fat_core.git'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
spec.required_ruby_version = '>= 2.2.2'
|
17
17
|
spec.metadata['yard.run'] = 'yri' # use "yard" to build full HTML docs.
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.require_paths = ['lib']
|
24
24
|
|
25
25
|
spec.add_development_dependency 'simplecov'
|
26
|
-
spec.add_development_dependency 'bundler'
|
26
|
+
spec.add_development_dependency 'bundler'
|
27
27
|
spec.add_development_dependency 'rake'
|
28
28
|
spec.add_development_dependency 'rspec'
|
29
29
|
spec.add_development_dependency 'byebug'
|
data/lib/fat_core/bigdecimal.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'bigdecimal'
|
2
2
|
|
3
3
|
module FatCore
|
4
|
+
# Extensions to BigDecimal class
|
4
5
|
module BigDecimal
|
5
6
|
# Provide a human-readable display for BigDecimal. e.g., while debugging.
|
6
7
|
# The inspect method in BigDecimal is unreadable, as it exposes the
|
@@ -13,6 +14,7 @@ module FatCore
|
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
17
|
+
# Override the core inspect method.
|
16
18
|
class BigDecimal
|
17
19
|
prepend(FatCore::BigDecimal)
|
18
20
|
# @!parse include FatCore::BigDecimal
|
data/lib/fat_core/date.rb
CHANGED
@@ -84,8 +84,12 @@ module FatCore
|
|
84
84
|
# Format as an inactive Org date timestamp of the form `[YYYY-MM-DD <dow>]`
|
85
85
|
# (see Emacs org-mode)
|
86
86
|
# @return [String]
|
87
|
-
def org
|
88
|
-
|
87
|
+
def org(active = false)
|
88
|
+
if active
|
89
|
+
strftime('<%Y-%m-%d %a>')
|
90
|
+
else
|
91
|
+
strftime('[%Y-%m-%d %a]')
|
92
|
+
end
|
89
93
|
end
|
90
94
|
|
91
95
|
# :category: Formatting
|
@@ -108,20 +112,6 @@ module FatCore
|
|
108
112
|
# :category: Queries
|
109
113
|
# @group Queries
|
110
114
|
|
111
|
-
# Does self fall on a weekend?
|
112
|
-
# @return [Boolean]
|
113
|
-
def weekend?
|
114
|
-
saturday? || sunday?
|
115
|
-
end
|
116
|
-
|
117
|
-
# :category: Queries
|
118
|
-
|
119
|
-
# Does self fall on a weekday?
|
120
|
-
# @return [Boolean]
|
121
|
-
def weekday?
|
122
|
-
!weekend?
|
123
|
-
end
|
124
|
-
|
125
115
|
# :category: Queries
|
126
116
|
|
127
117
|
# Self's calendar "half" by analogy to calendar quarters: 1 or 2, depending
|
@@ -155,6 +145,50 @@ module FatCore
|
|
155
145
|
end
|
156
146
|
end
|
157
147
|
|
148
|
+
# Self's calendar bimonth: 1, 2, 3, 4, 5, or 6 depending on which calendar
|
149
|
+
# bimonth the date falls in.
|
150
|
+
# @return [1, 2, 3, 4, 5, 6]
|
151
|
+
def bimonth
|
152
|
+
case month
|
153
|
+
when (1..2)
|
154
|
+
1
|
155
|
+
when (3..4)
|
156
|
+
2
|
157
|
+
when (5..6)
|
158
|
+
3
|
159
|
+
when (7..8)
|
160
|
+
4
|
161
|
+
when (9..10)
|
162
|
+
5
|
163
|
+
when (11..12)
|
164
|
+
6
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Self's calendar semimonth: 1, through 24 depending on which calendar
|
169
|
+
# semimonth the date falls in.
|
170
|
+
# @return [Integer]
|
171
|
+
def semimonth
|
172
|
+
(month - 1) * 2 + (day <= 15 ? 1 : 2)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Self's calendar biweek: 1, through 24 depending on which calendar
|
176
|
+
# semimonth the date falls in.
|
177
|
+
# @return [Integer]
|
178
|
+
def biweek
|
179
|
+
if cweek.odd?
|
180
|
+
(cweek + 1) / 2
|
181
|
+
else
|
182
|
+
cweek / 2
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Self's calendar week: just calls cweek.
|
187
|
+
# @return [Integer]
|
188
|
+
def week
|
189
|
+
cweek
|
190
|
+
end
|
191
|
+
|
158
192
|
# :category: Queries
|
159
193
|
|
160
194
|
# Return whether the date falls on the first day of a year.
|
@@ -315,13 +349,13 @@ module FatCore
|
|
315
349
|
# dates 2 days after date six months before and 2 days before the date six
|
316
350
|
# months after the date `d`.
|
317
351
|
#
|
318
|
-
# @param
|
352
|
+
# @param from_date [::Date] the middle of the six-month range
|
319
353
|
# @return [Boolean]
|
320
|
-
def within_6mos_of?(
|
354
|
+
def within_6mos_of?(from_date)
|
321
355
|
# ::Date 6 calendar months before self
|
322
356
|
start_date = self - 6.months + 2.days
|
323
357
|
end_date = self + 6.months - 2.days
|
324
|
-
(start_date..end_date).cover?(
|
358
|
+
(start_date..end_date).cover?(from_date)
|
325
359
|
end
|
326
360
|
|
327
361
|
# Return whether this date is Easter Sunday for the year in which it falls
|
@@ -337,15 +371,15 @@ module FatCore
|
|
337
371
|
# Return whether this date is the `n`th weekday `wday` of the given `month` in
|
338
372
|
# this date's year.
|
339
373
|
#
|
340
|
-
# @param
|
374
|
+
# @param nth [Integer] number of wday in month, if negative count from end of
|
341
375
|
# the month
|
342
376
|
# @param wday [Integer] day of week, 0 is Sunday, 1 Monday, etc.
|
343
377
|
# @param month [Integer] the month number, 1 is January, 2 is February, etc.
|
344
378
|
# @return [Boolean]
|
345
|
-
def nth_wday_in_month?(
|
379
|
+
def nth_wday_in_month?(nth, wday, month)
|
346
380
|
# Is self the nth weekday in the given month of its year?
|
347
|
-
# If
|
348
|
-
self == ::Date.nth_wday_in_year_month(
|
381
|
+
# If nth is negative, count from last day of month
|
382
|
+
self == ::Date.nth_wday_in_year_month(nth, wday, year, month)
|
349
383
|
end
|
350
384
|
|
351
385
|
# :category: Relative ::Dates
|
@@ -478,61 +512,64 @@ module FatCore
|
|
478
512
|
# Return the date that is +n+ calendar halves after this date, where a
|
479
513
|
# calendar half is a period of 6 months.
|
480
514
|
#
|
481
|
-
# @param
|
515
|
+
# @param num [Integer] number of halves to advance, can be negative
|
482
516
|
# @return [::Date] new date n halves after this date
|
483
|
-
def next_half(
|
484
|
-
|
485
|
-
return self if
|
486
|
-
|
517
|
+
def next_half(num = 1)
|
518
|
+
num = num.floor
|
519
|
+
return self if num.zero?
|
520
|
+
|
521
|
+
next_month(num * 6)
|
487
522
|
end
|
488
523
|
|
489
524
|
# Return the date that is +n+ calendar halves before this date, where a
|
490
525
|
# calendar half is a period of 6 months.
|
491
526
|
#
|
492
|
-
# @param
|
527
|
+
# @param num [Integer] number of halves to retreat, can be negative
|
493
528
|
# @return [::Date] new date n halves before this date
|
494
|
-
def prior_half(
|
495
|
-
next_half(-
|
529
|
+
def prior_half(num = 1)
|
530
|
+
next_half(-num)
|
496
531
|
end
|
497
532
|
|
498
533
|
# Return the date that is +n+ calendar quarters after this date, where a
|
499
534
|
# calendar quarter is a period of 3 months.
|
500
535
|
#
|
501
|
-
# @param
|
536
|
+
# @param num [Integer] number of quarters to advance, can be negative
|
502
537
|
# @return [::Date] new date n quarters after this date
|
503
|
-
def next_quarter(
|
504
|
-
|
505
|
-
return self if
|
506
|
-
|
538
|
+
def next_quarter(num = 1)
|
539
|
+
num = num.floor
|
540
|
+
return self if num.zero?
|
541
|
+
|
542
|
+
next_month(num * 3)
|
507
543
|
end
|
508
544
|
|
509
545
|
# Return the date that is +n+ calendar quarters before this date, where a
|
510
546
|
# calendar quarter is a period of 3 months.
|
511
547
|
#
|
512
|
-
# @param
|
548
|
+
# @param num [Integer] number of quarters to retreat, can be negative
|
513
549
|
# @return [::Date] new date n quarters after this date
|
514
|
-
def prior_quarter(
|
515
|
-
next_quarter(-
|
550
|
+
def prior_quarter(num = 1)
|
551
|
+
next_quarter(-num)
|
516
552
|
end
|
517
553
|
|
518
554
|
# Return the date that is +n+ calendar bimonths after this date, where a
|
519
555
|
# calendar bimonth is a period of 2 months.
|
520
556
|
#
|
521
|
-
# @param
|
557
|
+
# @param num [Integer] number of bimonths to advance, can be negative
|
522
558
|
# @return [::Date] new date n bimonths after this date
|
523
|
-
def next_bimonth(
|
524
|
-
|
525
|
-
return self if
|
526
|
-
|
559
|
+
def next_bimonth(num = 1)
|
560
|
+
num = num.floor
|
561
|
+
return self if num.zero?
|
562
|
+
|
563
|
+
next_month(num * 2)
|
527
564
|
end
|
528
565
|
|
529
566
|
# Return the date that is +n+ calendar bimonths before this date, where a
|
530
567
|
# calendar bimonth is a period of 2 months.
|
531
568
|
#
|
532
|
-
# @param
|
569
|
+
# @param num [Integer] number of bimonths to retreat, can be negative
|
533
570
|
# @return [::Date] new date n bimonths before this date
|
534
|
-
def prior_bimonth(
|
535
|
-
next_bimonth(-
|
571
|
+
def prior_bimonth(num = 1)
|
572
|
+
next_bimonth(-num)
|
536
573
|
end
|
537
574
|
|
538
575
|
# Return the date that is +n+ semimonths after this date. Each semimonth begins
|
@@ -542,15 +579,16 @@ module FatCore
|
|
542
579
|
# as far into the next month past the 1st as the current date is past the
|
543
580
|
# 16th, but never past the 15th of the next month.
|
544
581
|
#
|
545
|
-
# @param
|
582
|
+
# @param num [Integer] number of semimonths to advance, can be negative
|
546
583
|
# @return [::Date] new date n semimonths after this date
|
547
|
-
def next_semimonth(
|
548
|
-
|
549
|
-
return self if
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
584
|
+
def next_semimonth(num = 1)
|
585
|
+
num = num.floor
|
586
|
+
return self if num.zero?
|
587
|
+
|
588
|
+
factor = num.negative? ? -1 : 1
|
589
|
+
num = num.abs
|
590
|
+
if num.even?
|
591
|
+
next_month(num / 2)
|
554
592
|
else
|
555
593
|
# Advance or retreat one semimonth
|
556
594
|
next_sm =
|
@@ -577,23 +615,21 @@ module FatCore
|
|
577
615
|
[prior_month.beginning_of_month + 16.days + (day - 1).days,
|
578
616
|
prior_month.end_of_month].min
|
579
617
|
end
|
580
|
-
|
618
|
+
elsif factor.positive?
|
581
619
|
# In the second half of the month (17th to the 31st), go as many
|
582
620
|
# days into the next month as we are past the 16th. Thus, as many as
|
583
621
|
# 15 days. But we don't want to go past the first half of the next
|
584
622
|
# month, so we only go so far as the 15th of the next month.
|
585
623
|
# ::Date.parse('2015-02-18').next_semimonth should be the 3rd of the
|
586
624
|
# following month.
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
beginning_of_month + [(day - 16), 15].min
|
591
|
-
end
|
625
|
+
next_month.beginning_of_month + [(day - 16), 15].min
|
626
|
+
else
|
627
|
+
beginning_of_month + [(day - 16), 15].min
|
592
628
|
end
|
593
|
-
|
629
|
+
num -= 1
|
594
630
|
# Now that n is even, advance (or retreat) n / 2 months unless we're done.
|
595
|
-
if
|
596
|
-
next_sm.next_month(factor *
|
631
|
+
if num >= 2
|
632
|
+
next_sm.next_month(factor * num / 2)
|
597
633
|
else
|
598
634
|
next_sm
|
599
635
|
end
|
@@ -608,30 +644,31 @@ module FatCore
|
|
608
644
|
# as the current date is past the 15th, but never past the 14th of the the
|
609
645
|
# current month.
|
610
646
|
#
|
611
|
-
# @param
|
647
|
+
# @param num [Integer] number of semimonths to retreat, can be negative
|
612
648
|
# @return [::Date] new date n semimonths before this date
|
613
|
-
def prior_semimonth(
|
614
|
-
next_semimonth(-
|
649
|
+
def prior_semimonth(num = 1)
|
650
|
+
next_semimonth(-num)
|
615
651
|
end
|
616
652
|
|
617
653
|
# Return the date that is +n+ biweeks after this date where each biweek is 14
|
618
654
|
# days.
|
619
655
|
#
|
620
|
-
# @param
|
656
|
+
# @param num [Integer] number of biweeks to advance, can be negative
|
621
657
|
# @return [::Date] new date n biweeks after this date
|
622
|
-
def next_biweek(
|
623
|
-
|
624
|
-
return self if
|
625
|
-
|
658
|
+
def next_biweek(num = 1)
|
659
|
+
num = num.floor
|
660
|
+
return self if num.zero?
|
661
|
+
|
662
|
+
self + (14 * num)
|
626
663
|
end
|
627
664
|
|
628
665
|
# Return the date that is +n+ biweeks before this date where each biweek is 14
|
629
666
|
# days.
|
630
667
|
#
|
631
|
-
# @param
|
668
|
+
# @param num [Integer] number of biweeks to retreat, can be negative
|
632
669
|
# @return [::Date] new date n biweeks before this date
|
633
|
-
def prior_biweek(
|
634
|
-
next_biweek(-
|
670
|
+
def prior_biweek(num = 1)
|
671
|
+
next_biweek(-num)
|
635
672
|
end
|
636
673
|
|
637
674
|
# Return the date that is +n+ weeks after this date where each week is 7 days.
|
@@ -639,21 +676,22 @@ module FatCore
|
|
639
676
|
# goes to the first day of the week in the next week and does not take an
|
640
677
|
# argument +n+ to go multiple weeks.
|
641
678
|
#
|
642
|
-
# @param
|
679
|
+
# @param num [Integer] number of weeks to advance
|
643
680
|
# @return [::Date] new date n weeks after this date
|
644
|
-
def next_week(
|
645
|
-
|
646
|
-
return self if
|
647
|
-
|
681
|
+
def next_week(num = 1)
|
682
|
+
num = num.floor
|
683
|
+
return self if num.zero?
|
684
|
+
|
685
|
+
self + (7 * num)
|
648
686
|
end
|
649
687
|
|
650
688
|
# Return the date that is +n+ weeks before this date where each week is 7
|
651
689
|
# days.
|
652
690
|
#
|
653
|
-
# @param
|
691
|
+
# @param num [Integer] number of weeks to retreat
|
654
692
|
# @return [::Date] new date n weeks from this date
|
655
|
-
def prior_week(
|
656
|
-
next_week(-
|
693
|
+
def prior_week(num)
|
694
|
+
next_week(-num)
|
657
695
|
end
|
658
696
|
|
659
697
|
# NOTE: #next_day is defined in active_support.
|
@@ -661,10 +699,10 @@ module FatCore
|
|
661
699
|
# Return the date that is +n+ weeks before this date where each week is 7
|
662
700
|
# days.
|
663
701
|
#
|
664
|
-
# @param
|
702
|
+
# @param num [Integer] number of days to retreat
|
665
703
|
# @return [::Date] new date n days before this date
|
666
|
-
def prior_day(
|
667
|
-
next_day(-
|
704
|
+
def prior_day(num)
|
705
|
+
next_day(-num)
|
668
706
|
end
|
669
707
|
|
670
708
|
# :category: Relative ::Dates
|
@@ -673,28 +711,28 @@ module FatCore
|
|
673
711
|
#
|
674
712
|
# @param chunk [Symbol] one of +:year+, +:half+, +:quarter+, +:bimonth+,
|
675
713
|
# +:month+, +:semimonth+, +:biweek+, +:week+, or +:day+.
|
676
|
-
# @param
|
714
|
+
# @param num [Integer] the number of chunks to add, can be negative
|
677
715
|
# @return [::Date] the date n chunks from this date
|
678
|
-
def add_chunk(chunk,
|
716
|
+
def add_chunk(chunk, num = 1)
|
679
717
|
case chunk
|
680
718
|
when :year
|
681
|
-
next_year(
|
719
|
+
next_year(num)
|
682
720
|
when :half
|
683
|
-
next_month(6)
|
721
|
+
next_month(6 * num)
|
684
722
|
when :quarter
|
685
|
-
next_month(3)
|
723
|
+
next_month(3 * num)
|
686
724
|
when :bimonth
|
687
|
-
next_month(2)
|
725
|
+
next_month(2 * num)
|
688
726
|
when :month
|
689
|
-
next_month(
|
727
|
+
next_month(num)
|
690
728
|
when :semimonth
|
691
|
-
next_semimonth(
|
729
|
+
next_semimonth(num)
|
692
730
|
when :biweek
|
693
|
-
next_biweek(
|
731
|
+
next_biweek(num)
|
694
732
|
when :week
|
695
|
-
next_week(
|
733
|
+
next_week(num)
|
696
734
|
when :day
|
697
|
-
next_day(
|
735
|
+
next_day(num)
|
698
736
|
else
|
699
737
|
raise ArgumentError, "add_chunk unknown chunk: '#{chunk}'"
|
700
738
|
end
|
@@ -764,6 +802,22 @@ module FatCore
|
|
764
802
|
end
|
765
803
|
end
|
766
804
|
|
805
|
+
# @group Holidays and Workdays
|
806
|
+
|
807
|
+
# Does self fall on a weekend?
|
808
|
+
# @return [Boolean]
|
809
|
+
def weekend?
|
810
|
+
saturday? || sunday?
|
811
|
+
end
|
812
|
+
|
813
|
+
# :category: Queries
|
814
|
+
|
815
|
+
# Does self fall on a weekday?
|
816
|
+
# @return [Boolean]
|
817
|
+
def weekday?
|
818
|
+
!weekend?
|
819
|
+
end
|
820
|
+
|
767
821
|
# Return the date for Easter in the Western Church for the year in which this
|
768
822
|
# date falls.
|
769
823
|
#
|
@@ -773,8 +827,6 @@ module FatCore
|
|
773
827
|
::Date.easter(year)
|
774
828
|
end
|
775
829
|
|
776
|
-
# @group Federal Holidays and Workdays
|
777
|
-
|
778
830
|
# Holidays decreed by Presidential proclamation
|
779
831
|
FED_DECREED_HOLIDAYS =
|
780
832
|
[
|
@@ -782,7 +834,8 @@ module FatCore
|
|
782
834
|
# http://www.whitehouse.gov/the-press-office/2012/12/21
|
783
835
|
::Date.parse('2012-12-24'),
|
784
836
|
# And Trump
|
785
|
-
::Date.parse('2018-12-24')
|
837
|
+
::Date.parse('2018-12-24'),
|
838
|
+
::Date.parse('2019-12-24')
|
786
839
|
].freeze
|
787
840
|
|
788
841
|
# Presidential funeral since JFK
|
@@ -803,7 +856,7 @@ module FatCore
|
|
803
856
|
::Date.parse('2007-01-02'),
|
804
857
|
# GHWBFuneral
|
805
858
|
::Date.parse('2018-12-05')
|
806
|
-
]
|
859
|
+
].freeze
|
807
860
|
|
808
861
|
# Return whether this date is a United States federal holiday.
|
809
862
|
#
|
@@ -865,16 +918,17 @@ module FatCore
|
|
865
918
|
# Return the date that is n federal workdays after or before (if n < 0) this
|
866
919
|
# date.
|
867
920
|
#
|
868
|
-
# @param
|
921
|
+
# @param num [Integer] number of federal workdays to add to this date
|
869
922
|
# @return [::Date]
|
870
|
-
def add_fed_workdays(
|
923
|
+
def add_fed_workdays(num)
|
871
924
|
d = dup
|
872
|
-
return d if
|
873
|
-
|
874
|
-
|
875
|
-
|
925
|
+
return d if num.zero?
|
926
|
+
|
927
|
+
incr = num.negative? ? -1 : 1
|
928
|
+
num = num.abs
|
929
|
+
while num.positive?
|
876
930
|
d += incr
|
877
|
-
|
931
|
+
num -= 1 if d.fed_workday?
|
878
932
|
end
|
879
933
|
d
|
880
934
|
end
|
@@ -975,8 +1029,10 @@ module FatCore
|
|
975
1029
|
# Calculations for NYSE holidays are from Rule 51 and supplementary materials
|
976
1030
|
# for the Rules of the New York Stock Exchange, Inc.
|
977
1031
|
#
|
978
|
-
# * General Rule 1: if a regular holiday falls on Saturday, observe it on
|
979
|
-
#
|
1032
|
+
# * General Rule 1: if a regular holiday falls on Saturday, observe it on
|
1033
|
+
# the preceding Friday.
|
1034
|
+
# * General Rule 2: if a regular holiday falls on Sunday, observe it on
|
1035
|
+
# the following Monday.
|
980
1036
|
#
|
981
1037
|
# These are the regular holidays:
|
982
1038
|
#
|
@@ -1042,16 +1098,17 @@ module FatCore
|
|
1042
1098
|
# Return the date that is n NYSE trading days after or before (if n < 0) this
|
1043
1099
|
# date.
|
1044
1100
|
#
|
1045
|
-
# @param
|
1101
|
+
# @param num [Integer] number of NYSE trading days to add to this date
|
1046
1102
|
# @return [::Date]
|
1047
|
-
def add_nyse_workdays(
|
1103
|
+
def add_nyse_workdays(num)
|
1048
1104
|
d = dup
|
1049
|
-
return d if
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1105
|
+
return d if num.zero?
|
1106
|
+
|
1107
|
+
incr = num.negative? ? -1 : 1
|
1108
|
+
num = num.abs
|
1109
|
+
while num.positive?
|
1053
1110
|
d += incr
|
1054
|
-
|
1111
|
+
num -= 1 if d.nyse_workday?
|
1055
1112
|
end
|
1056
1113
|
d
|
1057
1114
|
end
|
@@ -1095,8 +1152,6 @@ module FatCore
|
|
1095
1152
|
date
|
1096
1153
|
end
|
1097
1154
|
|
1098
|
-
protected
|
1099
|
-
|
1100
1155
|
# Return whether this date is a fixed holiday for the NYSE, that is, a holiday
|
1101
1156
|
# that falls on the same date each year.
|
1102
1157
|
#
|
@@ -1209,7 +1264,9 @@ module FatCore
|
|
1209
1264
|
# @return [Boolean]
|
1210
1265
|
def nyse_special_holiday?
|
1211
1266
|
return false unless self > ::Date.parse('1960-01-01')
|
1267
|
+
|
1212
1268
|
return true if PRESIDENTIAL_FUNERALS.include?(self)
|
1269
|
+
|
1213
1270
|
case self
|
1214
1271
|
when ::Date.parse('1961-05-29')
|
1215
1272
|
# Day before Decoaration Day
|
@@ -1281,9 +1338,11 @@ module FatCore
|
|
1281
1338
|
# @param str [String, #to_s] a stringling of the form MM/DD/YYYY
|
1282
1339
|
# @return [::Date] the date represented by the str paramenter.
|
1283
1340
|
def parse_american(str)
|
1284
|
-
|
1341
|
+
re = %r{\A\s*(\d\d?)\s*[-/]\s*(\d\d?)\s*[-/]\s*((\d\d)?\d\d)\s*\z}
|
1342
|
+
unless str.to_s =~ re
|
1285
1343
|
raise ArgumentError, "date string must be of form 'MM?/DD?/YY(YY)?'"
|
1286
1344
|
end
|
1345
|
+
|
1287
1346
|
year = $3.to_i
|
1288
1347
|
month = $1.to_i
|
1289
1348
|
day = $2.to_i
|
@@ -1345,13 +1404,13 @@ module FatCore
|
|
1345
1404
|
# designated by spec
|
1346
1405
|
def parse_spec(spec, spec_type = :from)
|
1347
1406
|
spec = spec.to_s.strip
|
1348
|
-
unless [
|
1407
|
+
unless %i[from to].include?(spec_type)
|
1349
1408
|
raise ArgumentError, "invalid date spec type: '#{spec_type}'"
|
1350
1409
|
end
|
1351
1410
|
|
1352
1411
|
today = ::Date.current
|
1353
1412
|
case spec.clean
|
1354
|
-
when
|
1413
|
+
when %r{\A(\d\d\d\d)[-/](\d\d?)[-/](\d\d?)\z}
|
1355
1414
|
# A specified date
|
1356
1415
|
::Date.new($1.to_i, $2.to_i, $3.to_i)
|
1357
1416
|
when /\AW(\d\d?)\z/, /\A(\d\d?)W\z/
|
@@ -1359,29 +1418,32 @@ module FatCore
|
|
1359
1418
|
if week_num < 1 || week_num > 53
|
1360
1419
|
raise ArgumentError, "invalid week number (1-53): '#{spec}'"
|
1361
1420
|
end
|
1421
|
+
|
1362
1422
|
if spec_type == :from
|
1363
1423
|
::Date.commercial(today.year, week_num).beginning_of_week
|
1364
1424
|
else
|
1365
1425
|
::Date.commercial(today.year, week_num).end_of_week
|
1366
1426
|
end
|
1367
|
-
when
|
1427
|
+
when %r{\A(\d\d\d\d)[-/]W(\d\d?)\z}, %r{\A(\d\d\d\d)[-/](\d\d?)W\z}
|
1368
1428
|
year = $1.to_i
|
1369
1429
|
week_num = $2.to_i
|
1370
1430
|
if week_num < 1 || week_num > 53
|
1371
1431
|
raise ArgumentError, "invalid week number (1-53): '#{spec}'"
|
1372
1432
|
end
|
1433
|
+
|
1373
1434
|
if spec_type == :from
|
1374
1435
|
::Date.commercial(year, week_num).beginning_of_week
|
1375
1436
|
else
|
1376
1437
|
::Date.commercial(year, week_num).end_of_week
|
1377
1438
|
end
|
1378
|
-
when
|
1439
|
+
when %r{^(\d\d\d\d)[-/](\d)[Qq]$}, %r{^(\d\d\d\d)[-/][Qq](\d)$}
|
1379
1440
|
# Year-Quarter
|
1380
1441
|
year = $1.to_i
|
1381
1442
|
quarter = $2.to_i
|
1382
1443
|
unless [1, 2, 3, 4].include?(quarter)
|
1383
1444
|
raise ArgumentError, "invalid quarter number (1-4): '#{spec}'"
|
1384
1445
|
end
|
1446
|
+
|
1385
1447
|
month = quarter * 3
|
1386
1448
|
if spec_type == :from
|
1387
1449
|
::Date.new(year, month, 1).beginning_of_quarter
|
@@ -1395,19 +1457,20 @@ module FatCore
|
|
1395
1457
|
unless [1, 2, 3, 4].include?(quarter)
|
1396
1458
|
raise ArgumentError, "invalid quarter number (1-4): '#{spec}'"
|
1397
1459
|
end
|
1460
|
+
|
1398
1461
|
date = ::Date.new(this_year, quarter * 3, 15)
|
1399
1462
|
if spec_type == :from
|
1400
1463
|
date.beginning_of_quarter
|
1401
1464
|
else
|
1402
1465
|
date.end_of_quarter
|
1403
1466
|
end
|
1404
|
-
when
|
1467
|
+
when %r{^(\d\d\d\d)[-/](\d)[Hh]$}, %r{^(\d\d\d\d)[-/][Hh](\d)$}
|
1405
1468
|
# Year-Half
|
1406
1469
|
year = $1.to_i
|
1407
1470
|
half = $2.to_i
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1471
|
+
msg = "invalid half number: '#{spec}'"
|
1472
|
+
raise ArgumentError, msg unless [1, 2].include?(half)
|
1473
|
+
|
1411
1474
|
month = half * 6
|
1412
1475
|
if spec_type == :from
|
1413
1476
|
::Date.new(year, month, 15).beginning_of_half
|
@@ -1418,34 +1481,36 @@ module FatCore
|
|
1418
1481
|
# Half only
|
1419
1482
|
this_year = today.year
|
1420
1483
|
half = $1.to_i
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1484
|
+
msg = "invalid half number: '#{spec}'"
|
1485
|
+
raise ArgumentError, msg unless [1, 2].include?(half)
|
1486
|
+
|
1424
1487
|
date = ::Date.new(this_year, half * 6, 15)
|
1425
1488
|
if spec_type == :from
|
1426
1489
|
date.beginning_of_half
|
1427
1490
|
else
|
1428
1491
|
date.end_of_half
|
1429
1492
|
end
|
1430
|
-
when
|
1493
|
+
when %r{^(\d\d\d\d)[-/](\d\d?)*$}
|
1431
1494
|
# Year-Month only
|
1432
1495
|
year = $1.to_i
|
1433
1496
|
month = $2.to_i
|
1434
1497
|
unless [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].include?(month)
|
1435
1498
|
raise ArgumentError, "invalid month number (1-12): '#{spec}'"
|
1436
1499
|
end
|
1500
|
+
|
1437
1501
|
if spec_type == :from
|
1438
1502
|
::Date.new(year, month, 1)
|
1439
1503
|
else
|
1440
1504
|
::Date.new(year, month, 1).end_of_month
|
1441
1505
|
end
|
1442
|
-
when
|
1506
|
+
when %r{^(\d\d?)[-/](\d\d?)*$}
|
1443
1507
|
# Month-Day only
|
1444
1508
|
month = $1.to_i
|
1445
1509
|
day = $2.to_i
|
1446
1510
|
unless [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].include?(month)
|
1447
1511
|
raise ArgumentError, "invalid month number (1-12): '#{spec}'"
|
1448
1512
|
end
|
1513
|
+
|
1449
1514
|
if spec_type == :from
|
1450
1515
|
::Date.new(today.year, month, day)
|
1451
1516
|
else
|
@@ -1457,6 +1522,7 @@ module FatCore
|
|
1457
1522
|
unless [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].include?(month)
|
1458
1523
|
raise ArgumentError, "invalid month number (1-12): '#{spec}'"
|
1459
1524
|
end
|
1525
|
+
|
1460
1526
|
if spec_type == :from
|
1461
1527
|
::Date.new(today.year, month, 1)
|
1462
1528
|
else
|
@@ -1580,11 +1646,12 @@ module FatCore
|
|
1580
1646
|
# starting with January = 1, etc.
|
1581
1647
|
COMMON_YEAR_DAYS_IN_MONTH = [31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31,
|
1582
1648
|
30, 31].freeze
|
1583
|
-
def days_in_month(
|
1584
|
-
raise ArgumentError, 'illegal month number' if
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1649
|
+
def days_in_month(year, month)
|
1650
|
+
raise ArgumentError, 'illegal month number' if month < 1 || month > 12
|
1651
|
+
|
1652
|
+
days = COMMON_YEAR_DAYS_IN_MONTH[month]
|
1653
|
+
if month == 2
|
1654
|
+
::Date.new(year, month, 1).leap? ? 29 : 28
|
1588
1655
|
else
|
1589
1656
|
days
|
1590
1657
|
end
|
@@ -1593,41 +1660,43 @@ module FatCore
|
|
1593
1660
|
# Return the nth weekday in the given month. If n is negative, count from
|
1594
1661
|
# last day of month.
|
1595
1662
|
#
|
1596
|
-
# @param
|
1663
|
+
# @param nth [Integer] the ordinal number for the weekday
|
1597
1664
|
# @param wday [Integer] the weekday of interest with Monday 0 to Sunday 6
|
1598
1665
|
# @param year [Integer] the year of interest
|
1599
1666
|
# @param month [Integer] the month of interest with January 1 to December 12
|
1600
|
-
def nth_wday_in_year_month(
|
1667
|
+
def nth_wday_in_year_month(nth, wday, year, month)
|
1601
1668
|
wday = wday.to_i
|
1602
|
-
raise ArgumentError, 'illegal weekday number' if wday
|
1669
|
+
raise ArgumentError, 'illegal weekday number' if wday.negative? || wday > 6
|
1670
|
+
|
1603
1671
|
month = month.to_i
|
1604
1672
|
raise ArgumentError, 'illegal month number' if month < 1 || month > 12
|
1605
|
-
|
1606
|
-
|
1673
|
+
|
1674
|
+
nth = nth.to_i
|
1675
|
+
if nth.positive?
|
1607
1676
|
# Set d to the 1st wday in month
|
1608
1677
|
d = ::Date.new(year, month, 1)
|
1609
1678
|
d += 1 while d.wday != wday
|
1610
1679
|
# Set d to the nth wday in month
|
1611
1680
|
nd = 1
|
1612
|
-
while nd !=
|
1681
|
+
while nd != nth
|
1613
1682
|
d += 7
|
1614
1683
|
nd += 1
|
1615
1684
|
end
|
1616
1685
|
d
|
1617
|
-
elsif
|
1618
|
-
|
1686
|
+
elsif nth.negative?
|
1687
|
+
nth = -nth
|
1619
1688
|
# Set d to the last wday in month
|
1620
1689
|
d = ::Date.new(year, month, 1).end_of_month
|
1621
1690
|
d -= 1 while d.wday != wday
|
1622
1691
|
# Set d to the nth wday in month
|
1623
1692
|
nd = 1
|
1624
|
-
while nd !=
|
1693
|
+
while nd != nth
|
1625
1694
|
d -= 7
|
1626
1695
|
nd += 1
|
1627
1696
|
end
|
1628
1697
|
d
|
1629
1698
|
else
|
1630
|
-
raise ArgumentError, 'Argument
|
1699
|
+
raise ArgumentError, 'Argument nth cannot be zero'
|
1631
1700
|
end
|
1632
1701
|
end
|
1633
1702
|
|
@@ -1669,7 +1738,6 @@ module FatCore
|
|
1669
1738
|
end
|
1670
1739
|
end
|
1671
1740
|
|
1672
|
-
# @private
|
1673
1741
|
def self.included(base)
|
1674
1742
|
base.extend(ClassMethods)
|
1675
1743
|
end
|