fat_core 1.0.3 → 1.2.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 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