fat_core 1.0.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b32e345632ac5b6bccac049d806eed45e6a2074b
4
- data.tar.gz: e585990b0c1b6102e0d96f010107a5ec79b093a0
3
+ metadata.gz: e60f8326fe791c7c937d2bf069c82312f1a42d73
4
+ data.tar.gz: 74562c504f4b8104d8f4d5ad3915076f11475ba7
5
5
  SHA512:
6
- metadata.gz: d57d6d4df7e207a6aec6801ce6af4dff27b696a9e1fa76d301b1d550c7904fa95bc6cd5e275f39ff9e9455d242c965bd6bbf5d04d31414a9f2f67ae4eb9fca9a
7
- data.tar.gz: 804231e68d96590a6a898eb0de9e1fd206f1f8e1fbdaa95b59904f25702e971e8db6ba510f02fad2a4116e8e1cac94171dcd0f3f970077cdc4c222daca8a20e0
6
+ metadata.gz: 16619652648490f700cec9bd6f3f956d501e200e5a5f7606853bf35ec2a5eaa03c40a57dff89aff19d8233893c46b3e11b5cca44bc2c4c57eff873c554acf345
7
+ data.tar.gz: 2a873b09a9b550c3b58117241134ecc3d9a65e35d77f98fe4621aa52614c1c574a3fbf3ff67f1c5aa4e2eb0f2bcd74f267f5d46329555d2bc8bd6e9eb86124cb
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ tmp
18
18
  /GPATH
19
19
  /GRTAGS
20
20
  /GTAGS
21
+ /.rubocop_todo.yml
data/.rubocop.yml ADDED
@@ -0,0 +1,3 @@
1
+ inherit_from:
2
+ - '~/.rubocop.yml'
3
+ - '.rubocop_todo.yml'
data/Rakefile CHANGED
@@ -1,9 +1,9 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
 
3
3
  require 'rspec/core/rake_task'
4
4
 
5
5
  RSpec::Core::RakeTask.new(:spec, :tag) do |t|
6
- t.rspec_opts = "--tag ~online -f p"
6
+ t.rspec_opts = '--tag ~online -f p'
7
7
  end
8
8
 
9
9
  task :default => :spec
data/fat_core.gemspec CHANGED
@@ -4,31 +4,32 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'fat_core/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "fat_core"
7
+ spec.name = 'fat_core'
8
8
  spec.version = FatCore::VERSION
9
- spec.authors = ["Daniel E. Doherty"]
10
- spec.email = ["ded@ddoherty.net"]
11
- spec.summary = %q{fat_core provides some useful core extensions}
12
- spec.description = %q{Write a longer description. Optional.}
13
- spec.homepage = ""
14
- spec.license = "MIT"
9
+ spec.authors = ['Daniel E. Doherty']
10
+ spec.email = ['ded@ddoherty.net']
11
+ spec.summary = 'fat_core provides some useful core extensions'
12
+ spec.description = 'Write a longer description. Optional.'
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.files.reject! { |fn| fn =~ /^NYSE_closings.pdf/ }
18
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
21
21
 
22
22
  spec.add_development_dependency 'simplecov'
23
- spec.add_development_dependency "bundler", "~> 1.5"
24
- spec.add_development_dependency "rake"
25
- spec.add_development_dependency "rspec"
26
- spec.add_development_dependency "byebug"
27
- spec.add_development_dependency "pry"
28
- spec.add_development_dependency "pry-byebug"
29
- spec.add_development_dependency "rcodetools"
23
+ spec.add_development_dependency 'bundler', '~> 1.5'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'byebug'
27
+ spec.add_development_dependency 'pry'
28
+ spec.add_development_dependency 'pry-doc'
29
+ spec.add_development_dependency 'pry-byebug'
30
+ spec.add_development_dependency 'rcodetools'
30
31
 
31
- spec.add_runtime_dependency "activesupport"
32
- spec.add_runtime_dependency "erubis"
33
- spec.add_runtime_dependency "damerau-levenshtein"
32
+ spec.add_runtime_dependency 'activesupport'
33
+ spec.add_runtime_dependency 'erubis'
34
+ spec.add_runtime_dependency 'damerau-levenshtein'
34
35
  end
@@ -1,7 +1,6 @@
1
1
  module CoreExtensions
2
2
  module Date
3
3
  module FatCore
4
-
5
4
  end
6
5
  end
7
6
  end
data/lib/fat_core.rb CHANGED
@@ -2,8 +2,9 @@
2
2
  require 'date'
3
3
  require 'active_support'
4
4
  require 'active_support/core_ext'
5
+ require 'csv'
5
6
 
6
- require "fat_core/version"
7
+ require 'fat_core/version'
7
8
 
8
9
  require 'fat_core/array'
9
10
  require 'fat_core/date'
@@ -17,3 +18,6 @@ require 'fat_core/period'
17
18
  require 'fat_core/range'
18
19
  require 'fat_core/string'
19
20
  require 'fat_core/symbol'
21
+ require 'fat_core/evaluator'
22
+ require 'fat_core/column'
23
+ require 'fat_core/table'
@@ -0,0 +1,197 @@
1
+ module FatCore
2
+ # Column objects are just a thin wrapper around an Array to allow columns to
3
+ # be summed and have other operations performed on them, but compacting out
4
+ # nils before proceeding. My original attempt to do this by monkey-patching
5
+ # Array turned out badly. This works much nicer.
6
+ class Column
7
+ attr_reader :header, :type, :items
8
+
9
+ TYPES = %w(NilClass TrueClass FalseClass Date DateTime Numeric String)
10
+
11
+ def initialize(header:, type: 'NilClass', items: [])
12
+ @header = header.as_sym
13
+ @type = type
14
+ raise "Unknown column type '#{type}" unless TYPES.include?(@type.to_s)
15
+ @items = items
16
+ end
17
+
18
+ def <<(itm)
19
+ items << convert_to_type(itm)
20
+ end
21
+
22
+ def [](k)
23
+ items[k]
24
+ end
25
+
26
+ def to_a
27
+ items
28
+ end
29
+
30
+ def size
31
+ items.size
32
+ end
33
+
34
+ def last_i
35
+ size - 1
36
+ end
37
+
38
+ # Return a new Column appending the items of other to our items, checking
39
+ # for type compatibility.
40
+ def +(other)
41
+ raise 'Cannot combine columns with different types' unless type == other.type
42
+ Column.new(header: header, type: type, items: items + other.items)
43
+ end
44
+
45
+ def first
46
+ items.compact.first
47
+ end
48
+
49
+ def last
50
+ items.compact.last
51
+ end
52
+
53
+ # Return a string that of the first and last values.
54
+ def rng_s
55
+ "#{first}..#{last}"
56
+ end
57
+
58
+ def sum
59
+ items.compact.sum
60
+ end
61
+
62
+ def min
63
+ items.compact.min
64
+ end
65
+
66
+ def max
67
+ items.compact.max
68
+ end
69
+
70
+ def avg
71
+ sum / items.compact.size.to_d
72
+ end
73
+
74
+ # Convert val to the type of key, a ruby class constant, such as Date,
75
+ # Numeric, etc. If type is NilClass, the type is open, and a non-blank val
76
+ # will attempt conversion to one of the allowed types, typing it as a String
77
+ # if no other type is recognized. If the val is blank, and the type is nil,
78
+ # the column type remains open. If the val is nil or a blank and the type is
79
+ # already determined, the val is set to nil, and should be filtered from any
80
+ # column computations. If the val is non-blank and the column type
81
+ # determined, raise an error if the val cannot be converted to the column
82
+ # type. Otherwise, returns the converted val as an object of the correct
83
+ # class.
84
+ def convert_to_type(val)
85
+ case type
86
+ when 'NilClass'
87
+ if val.blank?
88
+ # Leave the type of the column open
89
+ val = nil
90
+ else
91
+ # Only non-blank values are allowed to set the type of the column
92
+ val_class = val.class
93
+ val = convert_to_boolean(val) ||
94
+ convert_to_date_time(val) ||
95
+ convert_to_numeric(val) ||
96
+ convert_to_string(val)
97
+ @type =
98
+ if val.is_a?(Numeric)
99
+ 'Numeric'
100
+ else
101
+ val.class.name
102
+ end
103
+ val
104
+ end
105
+ when 'TrueClass', 'FalseClass'
106
+ val_class = val.class
107
+ val = convert_to_boolean(val)
108
+ unless val
109
+ raise "Inconsistent value in a Boolean column #{key} has class #{val_class}"
110
+ end
111
+ val
112
+ when 'DateTime', 'Date'
113
+ val_class = val.class
114
+ val = convert_to_date_time(val)
115
+ unless val
116
+ raise "Inconsistent value in a DateTime column #{key} has class #{val_class}"
117
+ end
118
+ val
119
+ when 'Numeric'
120
+ val_class = val.class
121
+ val = convert_to_numeric(val)
122
+ unless val
123
+ raise "Inconsistent value in a Numeric column #{key} has class #{val_class}"
124
+ end
125
+ val
126
+ when 'String'
127
+ val_class = val.class
128
+ val = convert_to_string(val)
129
+ unless val
130
+ raise "Inconsistent value in a String column #{key} has class #{val_class}"
131
+ end
132
+ val
133
+ else
134
+ raise "Unknown object of class #{type} in Table"
135
+ end
136
+ end
137
+
138
+ # Convert the val to a boolean if it looks like one, otherwise return nil.
139
+ # Any boolean or a string of t, f, true, false, y, n, yes, or no, regardless
140
+ # of case is assumed to be a boolean.
141
+ def convert_to_boolean(val)
142
+ return val if val.is_a?(TrueClass) || val.is_a?(FalseClass)
143
+ val = val.to_s.clean
144
+ return nil if val.blank?
145
+ if val =~ /\Afalse|f|n|no/i
146
+ false
147
+ elsif val =~ /\Atrue|t|y|yes\z/i
148
+ true
149
+ end
150
+ end
151
+
152
+ # Convert the val to a DateTime if it is either a DateTime, a Date, or a
153
+ # String that can be parsed as a DateTime, otherwise return nil. It only
154
+ # recognizes strings that contain a something like '2016-01-14' or
155
+ # '2/12/1985' within them, otherwise DateTime.parse would treat many bare
156
+ # numbers as dates, such as '2841381', which it would recognize as a valid
157
+ # date, but the user probably does not intend it to be so treated.
158
+ def convert_to_date_time(val)
159
+ return val if val.is_a?(DateTime)
160
+ return val.to_datetime if val.is_a?(Date) && type == 'DateTime'
161
+ return val if val.is_a?(Date)
162
+ begin
163
+ val = val.to_s.clean
164
+ return nil if val.blank?
165
+ return nil unless val =~ %r{\b\d\d\d\d[-/]\d\d?[-/]\d\d?\b}
166
+ val = DateTime.parse(val.to_s.clean)
167
+ val = val.to_date if val.seconds_since_midnight.zero?
168
+ val
169
+ rescue ArgumentError
170
+ return nil
171
+ end
172
+ end
173
+
174
+ # Convert the val to a Numeric if is already a Numberic or is a String that
175
+ # looks like one. Any Float is promoted to a BigDecimal. Otherwise return
176
+ # nil.
177
+ def convert_to_numeric(val)
178
+ return BigDecimal.new(val, Float::DIG) if val.is_a?(Float)
179
+ return val if val.is_a?(Numeric)
180
+ # Eliminate any commas, $'s, or _'s.
181
+ val = val.to_s.clean.gsub(/[,_$]/, '')
182
+ return nil if val.blank?
183
+ case val
184
+ when /\A(\d+\.\d*)|(\d*\.\d+)\z/
185
+ BigDecimal.new(val.to_s.clean)
186
+ when /\A[\d]+\z/
187
+ val.to_i
188
+ when %r{\A(\d+)\s*[:/]\s*(\d+)\z}
189
+ Rational($1, $2)
190
+ end
191
+ end
192
+
193
+ def convert_to_string(val)
194
+ val.to_s
195
+ end
196
+ end
197
+ end
data/lib/fat_core/date.rb CHANGED
@@ -23,45 +23,41 @@ class Date
23
23
  # @param str [#to_s] a stringling of the form MM/DD/YYYY
24
24
  # @return [Date] the date represented by the string paramenter.
25
25
  def self.parse_american(str)
26
- if str.to_s =~ %r{\A\s*(\d\d?)\s*/\s*(\d\d?)\s*/\s*(\d?\d?\d\d)\s*\z}
27
- year, month, day = $3.to_i, $1.to_i, $2.to_i
28
- if year < 100
29
- year += 2000
30
- end
31
- Date.new(year, month, day)
32
- else
26
+ unless str.to_s =~ %r{\A\s*(\d\d?)\s*/\s*(\d\d?)\s*/\s*(\d?\d?\d\d)\s*\z}
33
27
  raise ArgumentError, "date string must be of form 'MM?/DD?/YY(YY)?'"
34
28
  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)
35
34
  end
36
35
 
37
- =begin
38
- Convert a 'date spec' to a Date. A date spec is a short-hand way of
39
- specifying a date, relative to the computer clock. A date spec can
40
- interpreted as either a 'from spec' or a 'to spec'.
41
-
42
- @example
43
- Assuming that Date.current at the time of execution is 2014-07-26 and
44
- using the default spec_type of :from. The return values are actually Date
45
- objects, but are shown below as textual dates.
46
-
47
- A fully specified date returns that date:
48
- Date.parse_spec('2001-09-11') # =>
49
-
50
- Commercial weeks can be specified using, for example W32 or 32W, with the
51
- week beginning on Monday, ending on Sunday.
52
- Date.parse_spec('2012-W32') # =>
53
- Date.parse_spec('2012-W32', :to) # =>
54
- Date.parse_spec('W32') # =>
55
-
56
- A spec of the form Q3 or 3Q returns the beginning or end of calendar
57
- quarters.
58
- Date.parse_spec('Q3') # =>
59
-
60
- @param spec [#to_s] a stringling containing the spec to be interpreted
61
- @param spec_type [:from, :to] interpret the spec as a from- or to-spec
62
- respectively, defaulting to interpretation as a to-spec.
63
- @return [Date] a date object equivalent to the date spec
64
- =end
36
+ # Convert a 'date spec' to a Date. A date spec is a short-hand way of
37
+ # specifying a date, relative to the computer clock. A date spec can
38
+ # interpreted as either a 'from spec' or a 'to spec'.
39
+ # @example
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
65
61
  def self.parse_spec(spec, spec_type = :from)
66
62
  spec = spec.to_s.strip
67
63
  unless [:from, :to].include?(spec_type)
@@ -78,16 +74,22 @@ class Date
78
74
  if week_num < 1 || week_num > 53
79
75
  raise ArgumentError, "invalid week number (1-53): 'W#{week_num}'"
80
76
  end
81
- spec_type == :from ? Date.commercial(today.year, week_num).beginning_of_week :
77
+ if spec_type == :from
78
+ Date.commercial(today.year, week_num).beginning_of_week
79
+ else
82
80
  Date.commercial(today.year, week_num).end_of_week
81
+ end
83
82
  when /\A(\d\d\d\d)-W(\d\d?)\z/, /\A(\d\d\d\d)-(\d\d?)W\z/
84
83
  year = $1.to_i
85
84
  week_num = $2.to_i
86
85
  if week_num < 1 || week_num > 53
87
86
  raise ArgumentError, "invalid week number (1-53): 'W#{week_num}'"
88
87
  end
89
- spec_type == :from ? Date.commercial(year, week_num).beginning_of_week :
88
+ if spec_type == :from
89
+ Date.commercial(year, week_num).beginning_of_week
90
+ else
90
91
  Date.commercial(year, week_num).end_of_week
92
+ end
91
93
  when /^(\d\d\d\d)-(\d)[Qq]$/, /^(\d\d\d\d)-[Qq](\d)$/
92
94
  # Year-Quarter
93
95
  year = $1.to_i
@@ -96,14 +98,21 @@ class Date
96
98
  raise ArgumentError, "bad date format: #{spec}"
97
99
  end
98
100
  month = quarter * 3
99
- spec_type == :from ? Date.new(year, month, 1).beginning_of_quarter :
101
+ if spec_type == :from
102
+ Date.new(year, month, 1).beginning_of_quarter
103
+ else
100
104
  Date.new(year, month, 1).end_of_quarter
105
+ end
101
106
  when /^([1234])[qQ]$/, /^[qQ]([1234])$/
102
107
  # Quarter only
103
108
  this_year = today.year
104
109
  quarter = $1.to_i
105
110
  date = Date.new(this_year, quarter * 3, 15)
106
- spec_type == :from ? date.beginning_of_quarter : date.end_of_quarter
111
+ if spec_type == :from
112
+ date.beginning_of_quarter
113
+ else
114
+ date.end_of_quarter
115
+ end
107
116
  when /^(\d\d\d\d)-(\d)[Hh]$/, /^(\d\d\d\d)-[Hh](\d)$/
108
117
  # Year-Half
109
118
  year = $1.to_i
@@ -112,25 +121,42 @@ class Date
112
121
  raise ArgumentError, "bad date format: #{spec}"
113
122
  end
114
123
  month = half * 6
115
- spec_type == :from ? Date.new(year, month, 15).beginning_of_half :
124
+ if spec_type == :from
125
+ Date.new(year, month, 15).beginning_of_half
126
+ else
116
127
  Date.new(year, month, 1).end_of_half
128
+ end
117
129
  when /^([12])[hH]$/, /^[hH]([12])$/
118
130
  # Half only
119
131
  this_year = today.year
120
132
  half = $1.to_i
121
133
  date = Date.new(this_year, half * 6, 15)
122
- spec_type == :from ? date.beginning_of_half : date.end_of_half
134
+ if spec_type == :from
135
+ date.beginning_of_half
136
+ else
137
+ date.end_of_half
138
+ end
123
139
  when /^(\d\d\d\d)-(\d\d?)*$/
124
140
  # Year-Month only
125
- spec_type == :from ? Date.new($1.to_i, $2.to_i, 1) :
141
+ if spec_type == :from
142
+ Date.new($1.to_i, $2.to_i, 1)
143
+ else
126
144
  Date.new($1.to_i, $2.to_i, 1).end_of_month
145
+ end
127
146
  when /\A(\d\d?)\z/
128
147
  # Month only
129
- spec_type == :from ? Date.new(today.year, $1.to_i, 1) :
148
+ if spec_type == :from
149
+ Date.new(today.year, $1.to_i, 1)
150
+ else
130
151
  Date.new(today.year, $1.to_i, 1).end_of_month
152
+ end
131
153
  when /^(\d\d\d\d)$/
132
154
  # Year only
133
- spec_type == :from ? Date.new($1.to_i, 1, 1) : Date.new($1.to_i, 12, 31)
155
+ if spec_type == :from
156
+ Date.new($1.to_i, 1, 1)
157
+ else
158
+ Date.new($1.to_i, 12, 31)
159
+ end
134
160
  when /^(to|this_?)?day/
135
161
  today
136
162
  when /^(yester|last_?)?day/
@@ -138,45 +164,97 @@ class Date
138
164
  when /^(this_?)?week/
139
165
  spec_type == :from ? today.beginning_of_week : today.end_of_week
140
166
  when /last_?week/
141
- spec_type == :from ? (today - 1.week).beginning_of_week :
167
+ if spec_type == :from
168
+ (today - 1.week).beginning_of_week
169
+ else
142
170
  (today - 1.week).end_of_week
171
+ end
143
172
  when /^(this_?)?biweek/
144
- spec_type == :from ? today.beginning_of_biweek : today.end_of_biweek
173
+ if spec_type == :from
174
+ today.beginning_of_biweek
175
+ else
176
+ today.end_of_biweek
177
+ end
145
178
  when /last_?biweek/
146
- spec_type == :from ? (today - 2.week).beginning_of_biweek :
179
+ if spec_type == :from
180
+ (today - 2.week).beginning_of_biweek
181
+ else
147
182
  (today - 2.week).end_of_biweek
183
+ end
148
184
  when /^(this_?)?semimonth/
149
185
  spec_type == :from ? today.beginning_of_semimonth : today.end_of_semimonth
150
186
  when /^last_?semimonth/
151
- spec_type == :from ? (today - 15.days).beginning_of_semimonth :
187
+ if spec_type == :from
188
+ (today - 15.days).beginning_of_semimonth
189
+ else
152
190
  (today - 15.days).end_of_semimonth
191
+ end
153
192
  when /^(this_?)?month/
154
- spec_type == :from ? today.beginning_of_month : today.end_of_month
193
+ if spec_type == :from
194
+ today.beginning_of_month
195
+ else
196
+ today.end_of_month
197
+ end
155
198
  when /^last_?month/
156
- spec_type == :from ? (today - 1.month).beginning_of_month :
199
+ if spec_type == :from
200
+ (today - 1.month).beginning_of_month
201
+ else
157
202
  (today - 1.month).end_of_month
203
+ end
158
204
  when /^(this_?)?bimonth/
159
- spec_type == :from ? today.beginning_of_bimonth : today.end_of_bimonth
205
+ if spec_type == :from
206
+ today.beginning_of_bimonth
207
+ else
208
+ today.end_of_bimonth
209
+ end
160
210
  when /^last_?bimonth/
161
- spec_type == :from ? (today - 2.month).beginning_of_bimonth :
211
+ if spec_type == :from
212
+ (today - 2.month).beginning_of_bimonth
213
+ else
162
214
  (today - 2.month).end_of_bimonth
215
+ end
163
216
  when /^(this_?)?quarter/
164
- spec_type == :from ? today.beginning_of_quarter : today.end_of_quarter
217
+ if spec_type == :from
218
+ today.beginning_of_quarter
219
+ else
220
+ today.end_of_quarter
221
+ end
165
222
  when /^last_?quarter/
166
- spec_type == :from ? (today - 3.months).beginning_of_quarter :
223
+ if spec_type == :from
224
+ (today - 3.months).beginning_of_quarter
225
+ else
167
226
  (today - 3.months).end_of_quarter
227
+ end
168
228
  when /^(this_?)?half/
169
- spec_type == :from ? today.beginning_of_half : today.end_of_half
229
+ if spec_type == :from
230
+ today.beginning_of_half
231
+ else
232
+ today.end_of_half
233
+ end
170
234
  when /^last_?half/
171
- spec_type == :from ? (today - 6.months).beginning_of_half :
235
+ if spec_type == :from
236
+ (today - 6.months).beginning_of_half
237
+ else
172
238
  (today - 6.months).end_of_half
239
+ end
173
240
  when /^(this_?)?year/
174
- spec_type == :from ? today.beginning_of_year : today.end_of_year
241
+ if spec_type == :from
242
+ today.beginning_of_year
243
+ else
244
+ today.end_of_year
245
+ end
175
246
  when /^last_?year/
176
- spec_type == :from ? (today - 1.year).beginning_of_year :
247
+ if spec_type == :from
248
+ (today - 1.year).beginning_of_year
249
+ else
177
250
  (today - 1.year).end_of_year
251
+ end
178
252
  when /^forever/
179
- spec_type == :from ? Date::BOT : Date::EOT
253
+ if spec_type == :from
254
+ Date::BOT
255
+ else
256
+ Date::EOT
257
+ end
180
258
  when /^never/
181
259
  nil
182
260
  else
@@ -198,7 +276,7 @@ class Date
198
276
 
199
277
  # Format as an ISO string.
200
278
  def iso
201
- strftime("%Y-%m-%d")
279
+ strftime('%Y-%m-%d')
202
280
  end
203
281
 
204
282
  # Format date to TeX documents as ISO strings
@@ -208,23 +286,28 @@ class Date
208
286
 
209
287
  # Format as an all-numeric string, i.e. 'YYYYMMDD'
210
288
  def num
211
- strftime("%Y%m%d")
289
+ strftime('%Y%m%d')
290
+ end
291
+
292
+ def format_by(fmt = '%Y-%m-%d')
293
+ fmt ||= '%Y-%m-%d'
294
+ strftime(fmt)
212
295
  end
213
296
 
214
297
  # Format as an inactive Org date (see emacs org-mode)
215
298
  def org
216
- strftime("[%Y-%m-%d %a]")
299
+ strftime('[%Y-%m-%d %a]')
217
300
  end
218
301
 
219
302
  # Format as an English string
220
303
  def eng
221
- strftime("%B %e, %Y")
304
+ strftime('%B %e, %Y')
222
305
  end
223
306
 
224
307
  # Format date in MM/DD/YYYY form, as typical for the short American
225
308
  # form.
226
309
  def american
227
- strftime "%-m/%-d/%Y"
310
+ strftime '%-m/%-d/%Y'
228
311
  end
229
312
 
230
313
  # Does self fall on a weekend?
@@ -288,7 +371,7 @@ class Date
288
371
  # first day of the odd-numbered months. E.g., 2014-01-01 to
289
372
  # 2014-02-28 is the first bimonth of 2014.
290
373
  def beginning_of_bimonth
291
- if month % 2 == 1
374
+ if month.odd?
292
375
  beginning_of_month
293
376
  else
294
377
  (self - 1.month).beginning_of_month
@@ -300,7 +383,7 @@ class Date
300
383
  # day of the odd-numbered months. E.g., 2014-01-01 to 2014-02-28 is
301
384
  # the first bimonth of 2014.
302
385
  def end_of_bimonth
303
- if month % 2 == 1
386
+ if month.odd?
304
387
  (self + 1.month).end_of_month
305
388
  else
306
389
  end_of_month
@@ -334,7 +417,7 @@ class Date
334
417
  # Note: we use a Monday start of the week in the next two methods because
335
418
  # commercial week counting assumes a Monday start.
336
419
  def beginning_of_biweek
337
- if cweek % 2 == 1
420
+ if cweek.odd?
338
421
  beginning_of_week(:monday)
339
422
  else
340
423
  (self - 1.week).beginning_of_week(:monday)
@@ -342,7 +425,7 @@ class Date
342
425
  end
343
426
 
344
427
  def end_of_biweek
345
- if cweek % 2 == 1
428
+ if cweek.odd?
346
429
  (self + 1.week).end_of_week(:monday)
347
430
  else
348
431
  end_of_week(:monday)
@@ -374,13 +457,11 @@ class Date
374
457
  end
375
458
 
376
459
  def beginning_of_bimonth?
377
- month % 2 == 1 &&
378
- beginning_of_month == self
460
+ month.odd? && beginning_of_month == self
379
461
  end
380
462
 
381
463
  def end_of_bimonth?
382
- month % 2 == 0 &&
383
- end_of_month == self
464
+ month.even? && end_of_month == self
384
465
  end
385
466
 
386
467
  def beginning_of_month?
@@ -500,13 +581,11 @@ class Date
500
581
  # executive-order-closing-executive-departments-and-agencies-federal-gover
501
582
  FED_DECREED_HOLIDAYS =
502
583
  [
503
- Date.parse('2012-12-24')
504
- ]
584
+ Date.parse('2012-12-24')
585
+ ].freeze
505
586
 
506
587
  def self.days_in_month(y, m)
507
- if m < 1 || m > 12
508
- raise ArgumentError, "illegal month number"
509
- end
588
+ raise ArgumentError, 'illegal month number' if m < 1 || m > 12
510
589
  days = Time::COMMON_YEAR_DAYS_IN_MONTH[m]
511
590
  if m == 2
512
591
  Date.new(y, m, 1).leap? ? 29 : 28
@@ -519,20 +598,14 @@ class Date
519
598
  # Return the nth weekday in the given month
520
599
  # If n is negative, count from last day of month
521
600
  wday = wday.to_i
522
- if wday < 0 || wday > 6
523
- raise ArgumentError, "illegal weekday number"
524
- end
601
+ raise ArgumentError, 'illegal weekday number' if wday < 0 || wday > 6
525
602
  month = month.to_i
526
- if month < 1 || month > 12
527
- raise ArgumentError, "illegal month number"
528
- end
603
+ raise ArgumentError, 'illegal month number' if month < 1 || month > 12
529
604
  n = n.to_i
530
605
  if n > 0
531
606
  # Set d to the 1st wday in month
532
607
  d = Date.new(year, month, 1)
533
- while d.wday != wday
534
- d += 1
535
- end
608
+ d += 1 while d.wday != wday
536
609
  # Set d to the nth wday in month
537
610
  nd = 1
538
611
  while nd != n
@@ -544,9 +617,7 @@ class Date
544
617
  n = -n
545
618
  # Set d to the last wday in month
546
619
  d = Date.new(year, month, 1).end_of_month
547
- while d.wday != wday;
548
- d -= 1
549
- end
620
+ d -= 1 while d.wday != wday
550
621
  # Set d to the nth wday in month
551
622
  nd = 1
552
623
  while nd != n
@@ -576,11 +647,11 @@ class Date
576
647
  g = (b - f + 1) / 3
577
648
  h = (19 * a + b - d - g + 15) % 30
578
649
  i, k = c.divmod(4)
579
- l = (32 + 2*e + 2*i - h - k) % 7
580
- m = (a + 11*h + 22*l) / 451
581
- n, p = (h + l - 7*m + 114).divmod(31)
650
+ l = (32 + 2 * e + 2 * i - h - k) % 7
651
+ m = (a + 11 * h + 22 * l) / 451
652
+ n, p = (h + l - 7 * m + 114).divmod(31)
582
653
  Date.new(y, n, p + 1)
583
- end
654
+ end
584
655
 
585
656
  def easter_this_year
586
657
  # Return the date of Easter in self's year
@@ -595,7 +666,7 @@ class Date
595
666
  def nth_wday_in_month?(n, wday, month)
596
667
  # Is self the nth weekday in the given month of its year?
597
668
  # If n is negative, count from last day of month
598
- self == Date.nth_wday_in_year_month(n, wday, self.year, month)
669
+ self == Date.nth_wday_in_year_month(n, wday, year, month)
599
670
  end
600
671
 
601
672
  #######################################################
@@ -626,7 +697,7 @@ class Date
626
697
  # rigged to fall on Monday except Thanksgiving
627
698
 
628
699
  # No moveable feasts in certain months
629
- if [ 3, 4, 6, 7, 8, 12 ].include?(month)
700
+ if [3, 4, 6, 7, 8, 12].include?(month)
630
701
  false
631
702
  elsif monday?
632
703
  moveable_mondays = []
@@ -658,7 +729,7 @@ class Date
658
729
  return true if FED_DECREED_HOLIDAYS.include?(self)
659
730
 
660
731
  # Is self a fixed holiday
661
- return true if (fed_fixed_holiday? || fed_moveable_feast?)
732
+ return true if fed_fixed_holiday? || fed_moveable_feast?
662
733
 
663
734
  if friday? && month == 12 && day == 26
664
735
  # If Christmas falls on a Thursday, apparently, the Friday after is
@@ -717,7 +788,7 @@ class Date
717
788
  # rigged to fall on Monday except Thanksgiving
718
789
 
719
790
  # No moveable feasts in certain months
720
- return false if [ 6, 7, 8, 10, 12 ].include?(month)
791
+ return false if [6, 7, 8, 10, 12].include?(month)
721
792
 
722
793
  case month
723
794
  when 1
@@ -733,13 +804,11 @@ class Date
733
804
  # Good Friday
734
805
  if !friday?
735
806
  false
736
- else
807
+ elsif [1898, 1906, 1907].include?(year)
737
808
  # Good Friday, the Friday before Easter, except certain years
738
- if [1898, 1906, 1907].include?(year)
739
- false
740
- else
741
- (self + 2).easter?
742
- end
809
+ false
810
+ else
811
+ (self + 2).easter?
743
812
  end
744
813
  when 5
745
814
  # Memorial Day (Last Monday in May)
@@ -760,17 +829,16 @@ class Date
760
829
  if year <= 1968
761
830
  is_election_day
762
831
  elsif year <= 1980
763
- is_election_day && (year % 4 == 0)
832
+ is_election_day && (year % 4).zero?
764
833
  else
765
834
  false
766
835
  end
767
836
  elsif thursday?
768
- # Historically Thanksgiving (NYSE closed all day) had been declared to be
769
- # the last Thursday in November until 1938;
770
- # the next-to-last Thursday in November from 1939 to 1941
771
- # (therefore the 3rd Thursday in 1940 and 1941);
772
- # the last Thursday in November in 1942;
773
- # the fourth Thursday in November since 1943;
837
+ # Historically Thanksgiving (NYSE closed all day) had been declared to
838
+ # be the last Thursday in November until 1938; the next-to-last
839
+ # Thursday in November from 1939 to 1941 (therefore the 3rd Thursday
840
+ # in 1940 and 1941); the last Thursday in November in 1942; the fourth
841
+ # Thursday in November since 1943;
774
842
  if year < 1938
775
843
  nth_wday_in_month?(-1, 4, 11)
776
844
  elsif year <= 1941
@@ -895,19 +963,17 @@ class Date
895
963
  def nyse_workday?
896
964
  !nyse_holiday?
897
965
  end
898
- alias :trading_day? :nyse_workday?
966
+ alias trading_day? nyse_workday?
899
967
 
900
968
  def add_fed_business_days(n)
901
- d = self.dup
902
- return d if n == 0
969
+ d = dup
970
+ return d if n.zero?
903
971
  incr = n < 0 ? -1 : 1
904
972
  n = n.abs
905
973
  while n > 0
906
974
  d += incr
907
- if d.fed_workday?
908
- n -= 1
909
- end
910
- end
975
+ n -= 1 if d.fed_workday?
976
+ end
911
977
  d
912
978
  end
913
979
 
@@ -920,37 +986,33 @@ class Date
920
986
  end
921
987
 
922
988
  def add_nyse_business_days(n)
923
- d = self.dup
924
- return d if n == 0
989
+ d = dup
990
+ return d if n.zero?
925
991
  incr = n < 0 ? -1 : 1
926
992
  n = n.abs
927
993
  while n > 0
928
994
  d += incr
929
- if d.nyse_workday?
930
- n -= 1
931
- end
932
- end
995
+ n -= 1 if d.nyse_workday?
996
+ end
933
997
  d
934
998
  end
935
- alias :add_trading_days :add_nyse_business_days
999
+ alias add_trading_days add_nyse_business_days
936
1000
 
937
1001
  def next_nyse_workday
938
1002
  add_nyse_business_days(1)
939
1003
  end
940
- alias :next_trading_day :next_nyse_workday
1004
+ alias next_trading_day next_nyse_workday
941
1005
 
942
1006
  def prior_nyse_workday
943
1007
  add_nyse_business_days(-1)
944
1008
  end
945
- alias :prior_trading_day :prior_nyse_workday
1009
+ alias prior_trading_day prior_nyse_workday
946
1010
 
947
1011
  # Return self if its a trading day, otherwise skip back to the first prior
948
1012
  # trading day.
949
1013
  def prior_until_trading_day
950
1014
  date = self
951
- while !date.trading_day?
952
- date -= 1
953
- end
1015
+ date -= 1 until date.trading_day?
954
1016
  date
955
1017
  end
956
1018
 
@@ -958,9 +1020,7 @@ class Date
958
1020
  # later trading day.
959
1021
  def next_until_trading_day
960
1022
  date = self
961
- while !date.trading_day?
962
- date += 1
963
- end
1023
+ date += 1 until date.trading_day?
964
1024
  date
965
1025
  end
966
1026
  end