fat_core 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +46 -0
- data/Rakefile +1 -0
- data/fat_core.gemspec +29 -0
- data/lib/fat_core.rb +19 -0
- data/lib/fat_core/array.rb +14 -0
- data/lib/fat_core/date.rb +757 -0
- data/lib/fat_core/enumerable.rb +7 -0
- data/lib/fat_core/hash.rb +33 -0
- data/lib/fat_core/kernel.rb +9 -0
- data/lib/fat_core/latex_eruby.rb +11 -0
- data/lib/fat_core/nil.rb +9 -0
- data/lib/fat_core/numeric.rb +87 -0
- data/lib/fat_core/period.rb +410 -0
- data/lib/fat_core/range.rb +192 -0
- data/lib/fat_core/string.rb +184 -0
- data/lib/fat_core/symbol.rb +17 -0
- data/lib/fat_core/version.rb +3 -0
- data/spec/lib/date_spec.rb +320 -0
- data/spec/lib/kernel_spec.rb +11 -0
- data/spec/lib/numeric_spec.rb +34 -0
- data/spec/lib/period_spec.rb +294 -0
- data/spec/lib/range_spec.rb +246 -0
- data/spec/lib/string_spec.rb +128 -0
- data/spec/spec_helper.rb +23 -0
- metadata +178 -0
@@ -0,0 +1,192 @@
|
|
1
|
+
class Range
|
2
|
+
# Return a range that concatenates this range with other; return nil
|
3
|
+
# if the ranges are not contiguous.
|
4
|
+
def join(other)
|
5
|
+
if left_contiguous?(other)
|
6
|
+
Range.new(min, other.max)
|
7
|
+
elsif right_contiguous?(other)
|
8
|
+
Range.new(other.min, max)
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Is self on the left of and contiguous to other?
|
15
|
+
def left_contiguous?(other)
|
16
|
+
if max.respond_to?(:succ)
|
17
|
+
max.succ == other.min
|
18
|
+
else
|
19
|
+
max == other.min
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Is self on the right of and contiguous to other?
|
24
|
+
def right_contiguous?(other)
|
25
|
+
if other.max.respond_to?(:succ)
|
26
|
+
other.max.succ == min
|
27
|
+
else
|
28
|
+
other.max == min
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def contiguous?(other)
|
33
|
+
left_contiguous?(other) || right_contiguous?(other)
|
34
|
+
end
|
35
|
+
|
36
|
+
def subset_of?(other)
|
37
|
+
min >= other.min && max <= other.max
|
38
|
+
end
|
39
|
+
|
40
|
+
def proper_subset_of?(other)
|
41
|
+
min > other.min && max < other.max
|
42
|
+
end
|
43
|
+
|
44
|
+
def superset_of?(other)
|
45
|
+
min <= other.min && max >= other.max
|
46
|
+
end
|
47
|
+
|
48
|
+
def proper_superset_of?(other)
|
49
|
+
min < other.min && max > other.max
|
50
|
+
end
|
51
|
+
|
52
|
+
def overlaps?(other)
|
53
|
+
cover?(other.min) || cover?(other.max)
|
54
|
+
end
|
55
|
+
|
56
|
+
def intersection(other)
|
57
|
+
return nil unless self.overlaps?(other)
|
58
|
+
([self.min, other.min].max..[self.max, other.max].min)
|
59
|
+
end
|
60
|
+
alias_method :&, :intersection
|
61
|
+
|
62
|
+
def union(other)
|
63
|
+
return nil unless self.overlaps?(other)
|
64
|
+
([self.min, other.min].min..[self.max, other.max].max)
|
65
|
+
end
|
66
|
+
alias_method :+, :union
|
67
|
+
|
68
|
+
# The difference method, -, removes the overlapping part of the other
|
69
|
+
# argument from self. Because in the case where self is a superset of the
|
70
|
+
# other range, this will result in the difference being two non-contiguous
|
71
|
+
# ranges, this returns an array of ranges. If there is no overlap or if
|
72
|
+
# self is a subset of the other range, return an empty array
|
73
|
+
def difference(other)
|
74
|
+
unless max.respond_to?(:succ) && min.respond_to?(:pred) &&
|
75
|
+
other.max.respond_to?(:succ) && other.min.respond_to?(:pred)
|
76
|
+
raise "Range difference operation requires objects have pred and succ methods"
|
77
|
+
end
|
78
|
+
# return [] unless self.overlaps?(other)
|
79
|
+
if proper_superset_of?(other)
|
80
|
+
[(min..other.min.pred),
|
81
|
+
(other.max.succ..max)]
|
82
|
+
elsif subset_of?(other)
|
83
|
+
[]
|
84
|
+
elsif overlaps?(other) && other.min <= min
|
85
|
+
[(other.max.succ .. max)]
|
86
|
+
elsif overlaps?(other) && other.max >= max
|
87
|
+
[(min .. other.min.pred)]
|
88
|
+
else
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
alias_method :-, :difference
|
93
|
+
|
94
|
+
# Return whether any of the ranges that are within self overlap one
|
95
|
+
# another
|
96
|
+
def has_overlaps_within?(ranges)
|
97
|
+
result = false
|
98
|
+
unless ranges.empty?
|
99
|
+
ranges.each do |r1|
|
100
|
+
next unless overlaps?(r1)
|
101
|
+
result =
|
102
|
+
ranges.any? do |r2|
|
103
|
+
r1.object_id != r2.object_id && overlaps?(r2) &&
|
104
|
+
r1.overlaps?(r2)
|
105
|
+
end
|
106
|
+
return true if result
|
107
|
+
end
|
108
|
+
end
|
109
|
+
result
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return true if the given ranges collectively cover this range
|
113
|
+
# without overlaps.
|
114
|
+
def spanned_by?(ranges)
|
115
|
+
joined_range = nil
|
116
|
+
ranges.sort_by {|r| r.min}.each do |r|
|
117
|
+
unless joined_range
|
118
|
+
joined_range = r
|
119
|
+
next
|
120
|
+
end
|
121
|
+
joined_range = joined_range.join(r)
|
122
|
+
break if joined_range.nil?
|
123
|
+
end
|
124
|
+
if !joined_range.nil?
|
125
|
+
joined_range.min <= min && joined_range.max >= max
|
126
|
+
else
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# If this range is not spanned by the ranges collectively, return an array
|
132
|
+
# of ranges representing the gaps in coverage. Otherwise return an empty
|
133
|
+
# array.
|
134
|
+
def gaps(ranges)
|
135
|
+
if ranges.empty?
|
136
|
+
[self.clone]
|
137
|
+
elsif spanned_by?(ranges)
|
138
|
+
[]
|
139
|
+
else
|
140
|
+
ranges = ranges.sort_by {|r| r.min}
|
141
|
+
gaps = []
|
142
|
+
cur_point = min
|
143
|
+
ranges.each do |rr|
|
144
|
+
if rr.min > cur_point
|
145
|
+
start_point = cur_point
|
146
|
+
end_point = rr.min.pred
|
147
|
+
gaps << (start_point..end_point)
|
148
|
+
end
|
149
|
+
cur_point = rr.max.succ
|
150
|
+
end
|
151
|
+
if cur_point < max
|
152
|
+
gaps << (cur_point..max)
|
153
|
+
end
|
154
|
+
gaps
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Similar to gaps, but within this range return the /overlaps/ among the
|
159
|
+
# given ranges. If there are no overlaps, return an empty array. Don't
|
160
|
+
# consider overlaps in the ranges that occur outside of self.
|
161
|
+
def overlaps(ranges)
|
162
|
+
if ranges.empty? || spanned_by?(ranges)
|
163
|
+
[]
|
164
|
+
else
|
165
|
+
ranges = ranges.sort_by {|r| r.min}
|
166
|
+
overlaps = []
|
167
|
+
cur_point = nil
|
168
|
+
ranges.each do |rr|
|
169
|
+
# Skip ranges outside of self
|
170
|
+
next if rr.max < min || rr.min > max
|
171
|
+
# Initialize cur_point to max of first range
|
172
|
+
if cur_point.nil?
|
173
|
+
cur_point = rr.max
|
174
|
+
next
|
175
|
+
end
|
176
|
+
# We are on the second or later range
|
177
|
+
if rr.min < cur_point
|
178
|
+
start_point = rr.min
|
179
|
+
end_point = cur_point
|
180
|
+
overlaps << (start_point..end_point)
|
181
|
+
end
|
182
|
+
cur_point = rr.max
|
183
|
+
end
|
184
|
+
overlaps
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Allow erb documents can directly interpolate ranges
|
189
|
+
def tex_quote
|
190
|
+
to_s
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
class String
|
2
|
+
# See if self contains colon- or space-separated words that include
|
3
|
+
# the colon- or space-separated words of other. Return the matched
|
4
|
+
# portion of self. Other cannot be a regex embedded in a string.
|
5
|
+
def fuzzy_match(other)
|
6
|
+
# Remove periods, commas, and apostrophes
|
7
|
+
other = other.gsub(/[.,']/, '')
|
8
|
+
target = self.gsub(/[.,']/, '')
|
9
|
+
matched_text = nil
|
10
|
+
matchers = other.split(/[: ]+/)
|
11
|
+
regexp_string = matchers.map {|m| ".*?#{Regexp.escape(m)}.*?"}.join('[: ]')
|
12
|
+
regexp_string.sub!(/^\.\*\?/, '')
|
13
|
+
regexp_string.sub!(/\.\*\?$/, '')
|
14
|
+
regexp = /#{regexp_string}/i
|
15
|
+
if match = regexp.match(target)
|
16
|
+
matched_text = match[0]
|
17
|
+
else
|
18
|
+
matched_text = nil
|
19
|
+
end
|
20
|
+
matched_text
|
21
|
+
end
|
22
|
+
|
23
|
+
# Here are instance methods for the class that includes Matchable
|
24
|
+
# This tries to convert the receiver object into a string, then
|
25
|
+
# matches against the given matcher, either via regex or a fuzzy
|
26
|
+
# string matcher.
|
27
|
+
def matches_with(str)
|
28
|
+
if str.nil?
|
29
|
+
nil
|
30
|
+
elsif str =~ /^\s*\//
|
31
|
+
re = str.to_regexp
|
32
|
+
if self.to_s =~ re
|
33
|
+
$&
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
else
|
38
|
+
self.to_s.fuzzy_match(str)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Convert a string of the form '/.../Iixm' to a regular expression. However,
|
43
|
+
# make the regular expression case-insensitive by default and extend the
|
44
|
+
# modifier syntax to allow '/I' to indicate case-sensitive.
|
45
|
+
def to_regexp
|
46
|
+
if self =~ /^\s*\/([^\/]*)\/([Iixm]*)\s*$/
|
47
|
+
body = $1
|
48
|
+
opts = $2
|
49
|
+
flags = Regexp::IGNORECASE
|
50
|
+
unless opts.blank?
|
51
|
+
flags = 0 if opts.include?('I')
|
52
|
+
flags |= Regexp::IGNORECASE if opts.include?('i')
|
53
|
+
flags |= Regexp::EXTENDED if opts.include?('x')
|
54
|
+
flags |= Regexp::MULTILINE if opts.include?('m')
|
55
|
+
end
|
56
|
+
flags = nil if flags == 0
|
57
|
+
Regexp.new(body, flags)
|
58
|
+
else
|
59
|
+
Regexp.new(self)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Convert to symbol "Hello World" -> :hello_world
|
64
|
+
def as_sym
|
65
|
+
strip.squeeze(' ').gsub(/\s+/, '_').downcase.to_sym
|
66
|
+
end
|
67
|
+
|
68
|
+
def as_string
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def wrap(width=70, hang=0)
|
73
|
+
offset = 0
|
74
|
+
trip = 1
|
75
|
+
result = ''
|
76
|
+
while (s = slice(offset, width))
|
77
|
+
offset += width
|
78
|
+
if trip == 1
|
79
|
+
width -= hang
|
80
|
+
else
|
81
|
+
s = (' ' * hang) + s
|
82
|
+
end
|
83
|
+
result << s + "\n"
|
84
|
+
trip += 1
|
85
|
+
end
|
86
|
+
# Remove the final newline before exiting
|
87
|
+
result.strip
|
88
|
+
end
|
89
|
+
|
90
|
+
def tex_quote
|
91
|
+
r = self.dup
|
92
|
+
r = r.gsub(/[{]/, 'XzXzXobXzXzX')
|
93
|
+
r = r.gsub(/[}]/, 'XzXzXcbXzXzX')
|
94
|
+
r = r.gsub(/\\/, '\textbackslash{}')
|
95
|
+
r = r.gsub(/\^/, '\textasciicircum{}')
|
96
|
+
r = r.gsub(/~/, '\textasciitilde{}')
|
97
|
+
r = r.gsub(/\|/, '\textbar{}')
|
98
|
+
r = r.gsub(/\</, '\textless{}')
|
99
|
+
r = r.gsub(/\>/, '\textgreater{}')
|
100
|
+
r = r.gsub(/([_$&%#])/) { |m| '\\' + m }
|
101
|
+
r = r.gsub('XzXzXobXzXzX', '\\{')
|
102
|
+
r = r.gsub('XzXzXcbXzXzX', '\\}')
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.random(size = 8)
|
106
|
+
"abcdefghijklmnopqrstuvwxyz".split('').shuffle[0..size].join('')
|
107
|
+
end
|
108
|
+
|
109
|
+
# Convert a string with an all-digit date to an iso string
|
110
|
+
# E.g., "20090923" -> "2009-09-23"
|
111
|
+
def digdate2iso
|
112
|
+
self.sub(/(\d\d\d\d)(\d\d)(\d\d)/, '\1-\2-\3')
|
113
|
+
end
|
114
|
+
|
115
|
+
def entitle!
|
116
|
+
little_words = %w[ a an the and or in on under of from as by to ]
|
117
|
+
newwords = []
|
118
|
+
words = split(/\s+/)
|
119
|
+
first_word = true
|
120
|
+
num_words = words.length
|
121
|
+
words.each_with_index do |w, k|
|
122
|
+
last_word = (k + 1 == num_words)
|
123
|
+
if w =~ %r[c/o]i
|
124
|
+
# Care of
|
125
|
+
newwords.push("c/o")
|
126
|
+
elsif w =~ %r[^p\.?o\.?$]i
|
127
|
+
# Post office
|
128
|
+
newwords.push("P.O.")
|
129
|
+
elsif w =~ %r[^[0-9]+(st|nd|rd|th)$]i
|
130
|
+
# Ordinals
|
131
|
+
newwords.push(w.downcase)
|
132
|
+
elsif w =~ %r[^(cr|dr|st|rd|ave|pk|cir)$]i
|
133
|
+
# Common abbrs to capitalize
|
134
|
+
newwords.push(w.capitalize)
|
135
|
+
elsif w =~ %r[^(us|ne|se|rr)$]i
|
136
|
+
# Common 2-letter abbrs to upcase
|
137
|
+
newwords.push(w.upcase)
|
138
|
+
elsif w =~ %r[^[0-9].*$]i
|
139
|
+
# Other runs starting with numbers,
|
140
|
+
# like 3-A
|
141
|
+
newwords.push(w.upcase)
|
142
|
+
elsif w =~ %r[^[^aeiouy]*$]i
|
143
|
+
# All consonants, probably abbr
|
144
|
+
newwords.push(w.upcase)
|
145
|
+
elsif w =~ %r[^(\w+)-(\w+)$]i
|
146
|
+
# Hypenated double word
|
147
|
+
newwords.push($1.capitalize + '-' + $2.capitalize)
|
148
|
+
elsif little_words.include?(w.downcase)
|
149
|
+
# Only capitalize at beginning or end
|
150
|
+
newwords.push((first_word or last_word) ? w.capitalize : w.downcase)
|
151
|
+
else
|
152
|
+
# All else
|
153
|
+
newwords.push(w.capitalize)
|
154
|
+
end
|
155
|
+
first_word = false
|
156
|
+
end
|
157
|
+
self[0..-1] = newwords.join(' ')
|
158
|
+
end
|
159
|
+
|
160
|
+
def entitle
|
161
|
+
self.dup.entitle!
|
162
|
+
end
|
163
|
+
|
164
|
+
# Thanks to Eugene at stackoverflow for the following.
|
165
|
+
# http://stackoverflow.com/questions/8806643/
|
166
|
+
# colorized-output-breaks-linewrapping-with-readline
|
167
|
+
# These color strings without confusing readline about the length of
|
168
|
+
# the prompt string in the shell. (Unlike the rainbow routines)
|
169
|
+
def console_red; colorize(self, "\001\e[1m\e[31m\002"); end
|
170
|
+
def console_dark_red; colorize(self, "\001\e[31m\002"); end
|
171
|
+
def console_green; colorize(self, "\001\e[1m\e[32m\002"); end
|
172
|
+
def console_dark_green; colorize(self, "\001\e[32m\002"); end
|
173
|
+
def console_yellow; colorize(self, "\001\e[1m\e[33m\002"); end
|
174
|
+
def console_dark_yellow; colorize(self, "\001\e[33m\002"); end
|
175
|
+
def console_blue; colorize(self, "\001\e[1m\e[34m\002"); end
|
176
|
+
def console_dark_blue; colorize(self, "\001\e[34m\002"); end
|
177
|
+
def console_purple; colorize(self, "\001\e[1m\e[35m\002"); end
|
178
|
+
|
179
|
+
def console_def; colorize(self, "\001\e[1m\002"); end
|
180
|
+
def console_bold; colorize(self, "\001\e[1m\002"); end
|
181
|
+
def console_blink; colorize(self, "\001\e[5m\002"); end
|
182
|
+
|
183
|
+
def colorize(text, color_code) "#{color_code}#{text}\001\e[0m\002" end
|
184
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Symbol
|
2
|
+
# Convert to capitalized string: :hello_world -> "Hello World"
|
3
|
+
def entitle
|
4
|
+
to_s.gsub('_', ' ').split(' ')
|
5
|
+
.join(' ')
|
6
|
+
.entitle
|
7
|
+
end
|
8
|
+
alias :to_string :entitle
|
9
|
+
|
10
|
+
def as_sym
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def tex_quote
|
15
|
+
to_s.tex_quote
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Date do
|
4
|
+
before :each do
|
5
|
+
# Pretend it is this date. Not at beg or end of year, quarter,
|
6
|
+
# month, or week. It is a Wednesday
|
7
|
+
Date.stub(:current).and_return(Date.parse('2012-07-18'))
|
8
|
+
Date.stub(:today).and_return(Date.parse('2012-07-18'))
|
9
|
+
@test_today = Date.parse('2012-07-18')
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "class methods" do
|
13
|
+
|
14
|
+
describe "parse_spec" do
|
15
|
+
|
16
|
+
it "should choke if spec type is neither :from or :to" do
|
17
|
+
expect {
|
18
|
+
Date.parse_spec('2011-07-15', :form)
|
19
|
+
}.to raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should parse plain iso dates correctly" do
|
23
|
+
Date.parse_spec('2011-07-15').should eq Date.parse('2011-07-15')
|
24
|
+
Date.parse_spec('2011-08-05').should eq Date.parse('2011-08-05')
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should parse week numbers such as 'W23' or '23W' correctly" do
|
28
|
+
Date.parse_spec('W1').should eq Date.parse('2012-01-02')
|
29
|
+
Date.parse_spec('W23').should eq Date.parse('2012-06-04')
|
30
|
+
Date.parse_spec('W23', :to).should eq Date.parse('2012-06-10')
|
31
|
+
Date.parse_spec('23W').should eq Date.parse('2012-06-04')
|
32
|
+
Date.parse_spec('23W', :to).should eq Date.parse('2012-06-10')
|
33
|
+
expect {
|
34
|
+
Date.parse_spec('W83', :to)
|
35
|
+
}.to raise_error
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should parse year-week numbers such as 'YYYY-W23' or 'YYYY-23W' correctly" do
|
39
|
+
Date.parse_spec('2003-W1').should eq Date.parse('2002-12-30')
|
40
|
+
Date.parse_spec('2003-W1', :to).should eq Date.parse('2003-01-05')
|
41
|
+
Date.parse_spec('2003-W23').should eq Date.parse('2003-06-02')
|
42
|
+
Date.parse_spec('2003-W23', :to).should eq Date.parse('2003-06-08')
|
43
|
+
Date.parse_spec('2003-23W').should eq Date.parse('2003-06-02')
|
44
|
+
Date.parse_spec('2003-23W', :to).should eq Date.parse('2003-06-08')
|
45
|
+
expect {
|
46
|
+
Date.parse_spec('2003-W83', :to)
|
47
|
+
}.to raise_error
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should parse year-quarter specs such as YYYY-NQ or YYYY-QN" do
|
51
|
+
Date.parse_spec('2011-4Q', :from).should eq Date.parse('2011-10-01')
|
52
|
+
Date.parse_spec('2011-4Q', :to).should eq Date.parse('2011-12-31')
|
53
|
+
Date.parse_spec('2011-Q4', :from).should eq Date.parse('2011-10-01')
|
54
|
+
Date.parse_spec('2011-Q4', :to).should eq Date.parse('2011-12-31')
|
55
|
+
expect { Date.parse_spec('2011-5Q') }.to raise_error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should parse quarter-only specs such as NQ or QN" do
|
59
|
+
Date.parse_spec('4Q', :from).should eq Date.parse('2012-10-01')
|
60
|
+
Date.parse_spec('4Q', :to).should eq Date.parse('2012-12-31')
|
61
|
+
Date.parse_spec('Q4', :from).should eq Date.parse('2012-10-01')
|
62
|
+
Date.parse_spec('Q4', :to).should eq Date.parse('2012-12-31')
|
63
|
+
expect { Date.parse_spec('5Q') }.to raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should parse year-month specs such as YYYY-MM" do
|
67
|
+
Date.parse_spec('2010-5', :from).should eq Date.parse('2010-05-01')
|
68
|
+
Date.parse_spec('2010-5', :to).should eq Date.parse('2010-05-31')
|
69
|
+
expect { Date.parse_spec('2010-13') }.to raise_error
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should parse month-only specs such as MM" do
|
73
|
+
Date.parse_spec('10', :from).should eq Date.parse('2012-10-01')
|
74
|
+
Date.parse_spec('10', :to).should eq Date.parse('2012-10-31')
|
75
|
+
expect { Date.parse_spec('99') }.to raise_error
|
76
|
+
expect { Date.parse_spec('011') }.to raise_error
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should parse year-only specs such as YYYY" do
|
80
|
+
Date.parse_spec('2010', :from).should eq Date.parse('2010-01-01')
|
81
|
+
Date.parse_spec('2010', :to).should eq Date.parse('2010-12-31')
|
82
|
+
expect { Date.parse_spec('99999') }.to raise_error
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should parse relative day names: today, yesterday" do
|
86
|
+
Date.parse_spec('today').should eq Date.current
|
87
|
+
Date.parse_spec('this_day').should eq Date.current
|
88
|
+
Date.parse_spec('yesterday').should eq Date.current - 1.day
|
89
|
+
Date.parse_spec('last_day').should eq Date.current - 1.day
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should parse relative weeks: this_week, last_week" do
|
93
|
+
Date.parse_spec('this_week').should eq Date.parse('2012-07-16')
|
94
|
+
Date.parse_spec('this_week', :to).should eq Date.parse('2012-07-22')
|
95
|
+
Date.parse_spec('last_week').should eq Date.parse('2012-07-09')
|
96
|
+
Date.parse_spec('last_week', :to).should eq Date.parse('2012-07-15')
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should parse relative biweeks: this_biweek, last_biweek" do
|
100
|
+
Date.parse_spec('this_biweek').should eq Date.parse('2012-07-16')
|
101
|
+
Date.parse_spec('this_biweek', :to).should eq Date.parse('2012-07-29')
|
102
|
+
Date.parse_spec('last_biweek').should eq Date.parse('2012-07-02')
|
103
|
+
Date.parse_spec('last_biweek', :to).should eq Date.parse('2012-07-15')
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should parse relative months: this_semimonth, last_semimonth" do
|
107
|
+
Date.parse_spec('this_semimonth').should eq Date.parse('2012-07-16')
|
108
|
+
Date.parse_spec('this_semimonth', :to).should eq Date.parse('2012-07-31')
|
109
|
+
Date.parse_spec('last_semimonth').should eq Date.parse('2012-07-01')
|
110
|
+
Date.parse_spec('last_semimonth', :to).should eq Date.parse('2012-07-15')
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should parse relative months: this_month, last_month" do
|
114
|
+
Date.parse_spec('this_month').should eq Date.parse('2012-07-01')
|
115
|
+
Date.parse_spec('this_month', :to).should eq Date.parse('2012-07-31')
|
116
|
+
Date.parse_spec('last_month').should eq Date.parse('2012-06-01')
|
117
|
+
Date.parse_spec('last_month', :to).should eq Date.parse('2012-06-30')
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should parse relative bimonths: this_bimonth, last_bimonth" do
|
121
|
+
Date.parse_spec('this_bimonth').should eq Date.parse('2012-07-01')
|
122
|
+
Date.parse_spec('this_bimonth', :to).should eq Date.parse('2012-08-31')
|
123
|
+
Date.parse_spec('last_bimonth').should eq Date.parse('2012-05-01')
|
124
|
+
Date.parse_spec('last_bimonth', :to).should eq Date.parse('2012-06-30')
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should parse relative quarters: this_quarter, last_quarter" do
|
128
|
+
Date.parse_spec('this_quarter').should eq Date.parse('2012-07-01')
|
129
|
+
Date.parse_spec('this_quarter', :to).should eq Date.parse('2012-09-30')
|
130
|
+
Date.parse_spec('last_quarter').should eq Date.parse('2012-04-01')
|
131
|
+
Date.parse_spec('last_quarter', :to).should eq Date.parse('2012-06-30')
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should parse relative years: this_year, last_year" do
|
135
|
+
Date.parse_spec('this_year').should eq Date.parse('2012-01-01')
|
136
|
+
Date.parse_spec('this_year', :to).should eq Date.parse('2012-12-31')
|
137
|
+
Date.parse_spec('last_year').should eq Date.parse('2011-01-01')
|
138
|
+
Date.parse_spec('last_year', :to).should eq Date.parse('2011-12-31')
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should parse forever and never" do
|
142
|
+
Date.parse_spec('forever').should eq Date::BOT
|
143
|
+
Date.parse_spec('forever', :to).should eq Date::EOT
|
144
|
+
Date.parse_spec('never').should be_nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should be able to parse an American-style date" do
|
149
|
+
Date.parse_american('2/12/2011').iso.should eq('2011-02-12')
|
150
|
+
Date.parse_american('2 / 12/ 2011').iso.should eq('2011-02-12')
|
151
|
+
Date.parse_american('2 / 1 / 2011').iso.should eq('2011-02-01')
|
152
|
+
Date.parse_american(' 2 / 1 / 2011 ').iso.should eq('2011-02-01')
|
153
|
+
Date.parse_american(' 2 / 1 / 15 ').iso.should eq('2015-02-01')
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe "instance methods" do
|
158
|
+
|
159
|
+
it "should know if its a weekend of a weekday" do
|
160
|
+
expect(Date.parse('2014-05-17')).to be_weekend
|
161
|
+
expect(Date.parse('2014-05-17')).to_not be_weekday
|
162
|
+
expect(Date.parse('2014-05-18')).to be_weekend
|
163
|
+
expect(Date.parse('2014-05-18')).to_not be_weekday
|
164
|
+
|
165
|
+
expect(Date.parse('2014-05-22')).to be_weekday
|
166
|
+
expect(Date.parse('2014-05-22')).to_not be_weekend
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should know its pred and succ (for Range)" do
|
170
|
+
Date.today.pred.should eq (Date.today - 1)
|
171
|
+
Date.today.succ.should eq (Date.today + 1)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should be able to print itself as an American-style date" do
|
175
|
+
Date.parse('2011-02-12').american.should eq('2/12/2011')
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should be able to print itself in iso form" do
|
179
|
+
Date.today.iso.should == '2012-07-18'
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should be able to print itself in org form" do
|
183
|
+
Date.today.org.should eq('[2012-07-18 Wed]')
|
184
|
+
(Date.today + 1.day).org.should eq('[2012-07-19 Thu]')
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should be able to print itself in eng form" do
|
188
|
+
Date.today.eng.should eq('July 18, 2012')
|
189
|
+
(Date.today + 1.day).eng.should eq('July 19, 2012')
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should be able to state its quarter" do
|
193
|
+
Date.today.quarter.should eq(3)
|
194
|
+
Date.parse('2012-02-29').quarter.should eq(1)
|
195
|
+
Date.parse('2012-01-01').quarter.should eq(1)
|
196
|
+
Date.parse('2012-03-31').quarter.should eq(1)
|
197
|
+
Date.parse('2012-04-01').quarter.should eq(2)
|
198
|
+
Date.parse('2012-05-15').quarter.should eq(2)
|
199
|
+
Date.parse('2012-06-30').quarter.should eq(2)
|
200
|
+
Date.parse('2012-07-01').quarter.should eq(3)
|
201
|
+
Date.parse('2012-08-15').quarter.should eq(3)
|
202
|
+
Date.parse('2012-09-30').quarter.should eq(3)
|
203
|
+
Date.parse('2012-10-01').quarter.should eq(4)
|
204
|
+
Date.parse('2012-11-15').quarter.should eq(4)
|
205
|
+
Date.parse('2012-12-31').quarter.should eq(4)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should know about years" do
|
209
|
+
Date.parse('2013-01-01').should be_beginning_of_year
|
210
|
+
Date.parse('2013-12-31').should be_end_of_year
|
211
|
+
Date.parse('2013-04-01').should_not be_beginning_of_year
|
212
|
+
Date.parse('2013-12-30').should_not be_end_of_year
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should know about quarters" do
|
216
|
+
Date.parse('2013-01-01').should be_beginning_of_quarter
|
217
|
+
Date.parse('2013-12-31').should be_end_of_quarter
|
218
|
+
Date.parse('2013-04-01').should be_beginning_of_quarter
|
219
|
+
Date.parse('2013-06-30').should be_end_of_quarter
|
220
|
+
Date.parse('2013-05-01').should_not be_beginning_of_quarter
|
221
|
+
Date.parse('2013-07-31').should_not be_end_of_quarter
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should know about bimonths" do
|
225
|
+
Date.parse('2013-11-04').beginning_of_bimonth.should eq Date.parse('2013-11-01')
|
226
|
+
Date.parse('2013-11-04').end_of_bimonth.should eq Date.parse('2013-12-31')
|
227
|
+
Date.parse('2013-03-01').should be_beginning_of_bimonth
|
228
|
+
Date.parse('2013-04-30').should be_end_of_bimonth
|
229
|
+
Date.parse('2013-01-01').should be_beginning_of_bimonth
|
230
|
+
Date.parse('2013-12-31').should be_end_of_bimonth
|
231
|
+
Date.parse('2013-05-01').should be_beginning_of_bimonth
|
232
|
+
Date.parse('2013-06-30').should be_end_of_bimonth
|
233
|
+
Date.parse('2013-06-01').should_not be_beginning_of_bimonth
|
234
|
+
Date.parse('2013-07-31').should_not be_end_of_bimonth
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should know about months" do
|
238
|
+
Date.parse('2013-01-01').should be_beginning_of_month
|
239
|
+
Date.parse('2013-12-31').should be_end_of_month
|
240
|
+
Date.parse('2013-05-01').should be_beginning_of_month
|
241
|
+
Date.parse('2013-07-31').should be_end_of_month
|
242
|
+
Date.parse('2013-05-02').should_not be_beginning_of_month
|
243
|
+
Date.parse('2013-07-30').should_not be_end_of_month
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should know about semimonths" do
|
247
|
+
Date.parse('2013-11-24').beginning_of_semimonth.should eq Date.parse('2013-11-16')
|
248
|
+
Date.parse('2013-11-04').beginning_of_semimonth.should eq Date.parse('2013-11-01')
|
249
|
+
Date.parse('2013-11-04').end_of_semimonth.should eq Date.parse('2013-11-15')
|
250
|
+
Date.parse('2013-11-24').end_of_semimonth.should eq Date.parse('2013-11-30')
|
251
|
+
Date.parse('2013-03-01').should be_beginning_of_semimonth
|
252
|
+
Date.parse('2013-03-16').should be_beginning_of_semimonth
|
253
|
+
Date.parse('2013-04-15').should be_end_of_semimonth
|
254
|
+
Date.parse('2013-04-30').should be_end_of_semimonth
|
255
|
+
end
|
256
|
+
|
257
|
+
it "should know about biweeks" do
|
258
|
+
Date.parse('2013-11-07').beginning_of_biweek.should eq Date.parse('2013-11-04')
|
259
|
+
Date.parse('2013-11-07').end_of_biweek.should eq Date.parse('2013-11-17')
|
260
|
+
Date.parse('2013-03-11').should be_beginning_of_biweek
|
261
|
+
Date.parse('2013-03-24').should be_end_of_biweek
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should know about weeks" do
|
265
|
+
Date.parse('2013-11-04').should be_beginning_of_week
|
266
|
+
Date.parse('2013-11-10').should be_end_of_week
|
267
|
+
Date.parse('2013-12-02').should be_beginning_of_week
|
268
|
+
Date.parse('2013-12-08').should be_end_of_week
|
269
|
+
Date.parse('2013-10-13').should_not be_beginning_of_week
|
270
|
+
Date.parse('2013-10-19').should_not be_end_of_week
|
271
|
+
end
|
272
|
+
|
273
|
+
it "should know the beginning of chunks" do
|
274
|
+
Date.parse('2013-11-04').beginning_of_chunk(:year).should eq Date.parse('2013-01-01')
|
275
|
+
Date.parse('2013-11-04').beginning_of_chunk(:quarter).should eq Date.parse('2013-10-01')
|
276
|
+
Date.parse('2013-12-04').beginning_of_chunk(:bimonth).should eq Date.parse('2013-11-01')
|
277
|
+
Date.parse('2013-11-04').beginning_of_chunk(:month).should eq Date.parse('2013-11-01')
|
278
|
+
Date.parse('2013-11-04').beginning_of_chunk(:semimonth).should eq Date.parse('2013-11-01')
|
279
|
+
Date.parse('2013-11-24').beginning_of_chunk(:semimonth).should eq Date.parse('2013-11-16')
|
280
|
+
Date.parse('2013-11-08').beginning_of_chunk(:biweek).should eq Date.parse('2013-11-04')
|
281
|
+
Date.parse('2013-11-08').beginning_of_chunk(:week).should eq Date.parse('2013-11-04')
|
282
|
+
expect {
|
283
|
+
Date.parse('2013-11-04').beginning_of_chunk(:wek)
|
284
|
+
}.to raise_error
|
285
|
+
end
|
286
|
+
|
287
|
+
it "should know the end of chunks" do
|
288
|
+
Date.parse('2013-07-04').end_of_chunk(:year).should eq Date.parse('2013-12-31')
|
289
|
+
Date.parse('2013-07-04').end_of_chunk(:quarter).should eq Date.parse('2013-09-30')
|
290
|
+
Date.parse('2013-12-04').end_of_chunk(:bimonth).should eq Date.parse('2013-12-31')
|
291
|
+
Date.parse('2013-07-04').end_of_chunk(:month).should eq Date.parse('2013-07-31')
|
292
|
+
Date.parse('2013-11-04').end_of_chunk(:semimonth).should eq Date.parse('2013-11-15')
|
293
|
+
Date.parse('2013-11-24').end_of_chunk(:semimonth).should eq Date.parse('2013-11-30')
|
294
|
+
Date.parse('2013-11-08').end_of_chunk(:biweek).should eq Date.parse('2013-11-17')
|
295
|
+
Date.parse('2013-07-04').end_of_chunk(:week).should eq Date.parse('2013-07-07')
|
296
|
+
expect {
|
297
|
+
Date.parse('2013-11-04').end_of_chunk(:wek)
|
298
|
+
}.to raise_error
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should know how to expand to chunk periods" do
|
302
|
+
Date.parse('2013-07-04').expand_to_period(:year).
|
303
|
+
should eq Period.new(Date.parse('2013-01-01'), Date.parse('2013-12-31'))
|
304
|
+
Date.parse('2013-07-04').expand_to_period(:quarter).
|
305
|
+
should eq Period.new(Date.parse('2013-07-01'), Date.parse('2013-09-30'))
|
306
|
+
Date.parse('2013-07-04').expand_to_period(:bimonth).
|
307
|
+
should eq Period.new(Date.parse('2013-07-01'), Date.parse('2013-08-31'))
|
308
|
+
Date.parse('2013-07-04').expand_to_period(:month).
|
309
|
+
should eq Period.new(Date.parse('2013-07-01'), Date.parse('2013-07-31'))
|
310
|
+
Date.parse('2013-07-04').expand_to_period(:semimonth).
|
311
|
+
should eq Period.new(Date.parse('2013-07-01'), Date.parse('2013-07-15'))
|
312
|
+
Date.parse('2013-07-04').expand_to_period(:biweek).
|
313
|
+
should eq Period.new(Date.parse('2013-07-01'), Date.parse('2013-07-14'))
|
314
|
+
Date.parse('2013-07-04').expand_to_period(:week).
|
315
|
+
should eq Period.new(Date.parse('2013-07-01'), Date.parse('2013-07-07'))
|
316
|
+
Date.parse('2013-07-04').expand_to_period(:day).
|
317
|
+
should eq Period.new(Date.parse('2013-07-04'), Date.parse('2013-07-04'))
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|