fat_core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|