arturop-chronic_duration 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "numerizer", "~> 0.1.1"
4
+
5
+ group :development do
6
+ gem "rspec", "~> 2.3.0"
7
+ gem "bundler", "~> 1.0.0"
8
+ gem "jeweler", "~> 1.5.2"
9
+ gem "rcov", ">= 0"
10
+ end
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.2)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ numerizer (0.1.1)
11
+ rake (0.8.7)
12
+ rcov (0.9.9)
13
+ rspec (2.3.0)
14
+ rspec-core (~> 2.3.0)
15
+ rspec-expectations (~> 2.3.0)
16
+ rspec-mocks (~> 2.3.0)
17
+ rspec-core (2.3.1)
18
+ rspec-expectations (2.3.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.3.0)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ bundler (~> 1.0.0)
27
+ jeweler (~> 1.5.2)
28
+ numerizer (~> 0.1.1)
29
+ rcov
30
+ rspec (~> 2.3.0)
@@ -0,0 +1,22 @@
1
+ Copyright (c) Henry Poydar
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,61 @@
1
+ = Chronic Duration
2
+
3
+ A simple Ruby natural language parser for elapsed time. (For example, 4 hours and 30 minutes, 6 minutes 4 seconds, 3 days, etc.) Returns all results in seconds. Will return an integer unless you get tricky and need a float. (4 minutes and 13.47 seconds, for example.)
4
+
5
+ The reverse can also be accomplished with the output method. So pass in seconds and you can get strings like 4 mins 31.51 secs (default format), 4h 3m 30s, or 4:01:29.
6
+
7
+ == Installation
8
+
9
+ $ sudo gem sources -a http://gemcutter.org
10
+ $ sudo gem install chronic_duration
11
+
12
+ == Usage
13
+
14
+ >> require 'chronic_duration'
15
+ => true
16
+ >> ChronicDuration.parse('4 minutes and 30 seconds')
17
+ => 270
18
+ >> ChronicDuration.output(270)
19
+ => 4 mins 30 secs
20
+ >> ChronicDuration.output(270, :format => :short)
21
+ => 4m 30s
22
+ >> ChronicDuration.output(270, :format => :long)
23
+ => 4 minutes 30 seconds
24
+ >> ChronicDuration.output(270, :format => :chrono)
25
+ => 4:30
26
+
27
+ Nil is returned if the string can't be parsed
28
+
29
+ Examples of parse-able strings:
30
+
31
+ * '12.4 secs'
32
+ * '1:20'
33
+ * '1:20.51'
34
+ * '4:01:01'
35
+ * '3 mins 4 sec'
36
+ * '2 hrs 20 min'
37
+ * '2h20min'
38
+ * '6 mos 1 day'
39
+ * '47 yrs 6 mos and 4d'
40
+ * 'two hours and twenty minutes'
41
+ * '3 weeks and 2 days'
42
+
43
+ ChronicDuration.raise_exceptions can be set to true to raise exceptions when the string can't be parsed.
44
+
45
+ >> ChronicDuration.raise_exceptions = true
46
+ => true
47
+ >> ChronicDuration.parse('4 elephants and 3 Astroids')
48
+ ChronicDuration::DurationParseError: An invalid word "elephants" was used in the string to be parsed.
49
+
50
+
51
+ == Contributors
52
+
53
+ brianjlandau, jduff, olauzon, roboman, ianlevesque
54
+
55
+ == TODO
56
+
57
+ * Benchmark, optimize
58
+ * Context specific matching (E.g., for '4m30s', assume 'm' is minutes not months)
59
+ * Smartly parse vacation-like durations (E.g., '4 days and 3 nights')
60
+ * :chrono output option should probably change to something like 4 days 4:00:12 instead of 4:04:00:12
61
+ * Other locale support
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "arturop-chronic_duration"
15
+ gem.summary = %Q{A Ruby natural language parser for elapsed time}
16
+ gem.license = "MIT"
17
+ gem.description = %Q{A simple Ruby natural language parser for elapsed time. (For example, 4 hours and 30 minutes, 6 minutes 4 seconds, 3 days, etc.) Returns all results in seconds. Will return an integer unless you get tricky and need a float. (4 minutes and 13.47 seconds, for example.) The reverse can also be performed via the output method.}
18
+ gem.email = "hpoydar@gmail.com"
19
+ gem.homepage = "http://github.com/hpoydar/chronic_duration"
20
+ gem.authors = ["hpoydar", "Arturo Pie"]
21
+ end
22
+ Jeweler::RubygemsDotOrgTasks.new
23
+
24
+ require 'rspec/core'
25
+ require 'rspec/core/rake_task'
26
+ RSpec::Core::RakeTask.new(:spec) do |spec|
27
+ spec.pattern = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "chronic_duration #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.7
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{chronic_duration}
8
+ s.version = "0.9.7"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["hpoydar", "Arturo Pie"]
12
+ s.date = %q{2011-07-06}
13
+ s.description = %q{A simple Ruby natural language parser for elapsed time. (For example, 4 hours and 30 minutes, 6 minutes 4 seconds, 3 days, etc.) Returns all results in seconds. Will return an integer unless you get tricky and need a float. (4 minutes and 13.47 seconds, for example.) The reverse can also be performed via the output method.}
14
+ s.email = %q{hpoydar@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "Gemfile",
20
+ "Gemfile.lock",
21
+ "MIT-LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "chronic_duration.gemspec",
26
+ "lib/chronic_duration.rb",
27
+ "spec/chronic_duration_spec.rb"
28
+ ]
29
+ s.homepage = %q{http://github.com/hpoydar/chronic_duration}
30
+ s.licenses = ["MIT"]
31
+ s.require_paths = ["lib"]
32
+ s.rubygems_version = %q{1.3.7}
33
+ s.summary = %q{A Ruby natural language parser for elapsed time}
34
+ s.test_files = [
35
+ "spec/chronic_duration_spec.rb"
36
+ ]
37
+
38
+ if s.respond_to? :specification_version then
39
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
40
+ s.specification_version = 3
41
+
42
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
+ s.add_runtime_dependency(%q<numerizer>, ["~> 0.1.1"])
44
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
45
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
46
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
47
+ s.add_development_dependency(%q<rcov>, [">= 0"])
48
+ else
49
+ s.add_dependency(%q<numerizer>, ["~> 0.1.1"])
50
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
51
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
52
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
53
+ s.add_dependency(%q<rcov>, [">= 0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<numerizer>, ["~> 0.1.1"])
57
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
58
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
60
+ s.add_dependency(%q<rcov>, [">= 0"])
61
+ end
62
+ end
63
+
@@ -0,0 +1,258 @@
1
+ require 'numerizer' unless defined?(Numerizer)
2
+ module ChronicDuration
3
+ extend self
4
+
5
+ class DurationParseError < StandardError
6
+ end
7
+
8
+ @@raise_exceptions = false
9
+
10
+ def self.raise_exceptions
11
+ !!@@raise_exceptions
12
+ end
13
+
14
+ def self.raise_exceptions=(value)
15
+ @@raise_exceptions = !!value
16
+ end
17
+
18
+ # Given a string representation of elapsed time,
19
+ # return an integer (or float, if fractions of a
20
+ # second are input)
21
+ def parse(string, opts = {})
22
+ result = calculate_from_words(cleanup(string), opts)
23
+ result == 0 ? nil : result
24
+ end
25
+
26
+ # Given an integer and an optional format,
27
+ # returns a formatted string representing elapsed time
28
+ def output(seconds, opts = {})
29
+
30
+ opts[:format] ||= :default
31
+ unit_of_measures = [:years, :months, :days, :hours, :minutes]
32
+ unit_of_measures << :seconds unless opts[:hide_seconds]
33
+
34
+ duration = Duration.new seconds
35
+
36
+ joiner = ' '
37
+ process = nil
38
+
39
+ case opts[:format]
40
+ when :micro
41
+ dividers = {
42
+ :years => 'y', :months => 'm', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
43
+ joiner = ''
44
+ when :short
45
+ dividers = {
46
+ :years => 'y', :months => 'm', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
47
+ when :default
48
+ dividers = {
49
+ :years => ' yr', :months => ' mo', :days => ' day', :hours => ' hr', :minutes => ' min', :seconds => ' sec',
50
+ :pluralize => true }
51
+ when :long
52
+ dividers = {
53
+ :years => ' year', :months => ' month', :days => ' day', :hours => ' hour', :minutes => ' minute', :seconds => ' second',
54
+ :pluralize => true }
55
+ when :chrono
56
+ dividers = {
57
+ :years => ':', :months => ':', :days => ':', :hours => ':', :minutes => ':', :seconds => ':', :keep_zero => true }
58
+ process = lambda do |str|
59
+ # Pad zeros
60
+ # Get rid of lead off times if they are zero
61
+ # Get rid of lead off zero
62
+ # Get rid of trailing :
63
+ str.gsub(/\b\d\b/) { |d| ("%02d" % d) }.gsub(/^(00:)+/, '').gsub(/^0/, '').gsub(/:$/, '')
64
+ end
65
+ joiner = ''
66
+ end
67
+
68
+ result = []
69
+ unit_of_measures.each do |t|
70
+ num = duration.send t
71
+ num = ("%.#{decimal_places seconds }f" % num) if num.is_a?(Float) && t == :seconds
72
+ result << humanize_time_unit( num, dividers[t], dividers[:pluralize], dividers[:keep_zero] )
73
+ end
74
+
75
+ result = result.join(joiner).squeeze(' ').strip
76
+
77
+ if process
78
+ result = process.call(result)
79
+ end
80
+
81
+ result.length == 0 ? nil : result
82
+
83
+ end
84
+
85
+ def decimal_places number
86
+ number.to_s.split('.').last.length if number.is_a? Float
87
+ end
88
+
89
+ class Duration
90
+ attr_accessor :seconds, :minutes, :hours, :days, :months, :years
91
+
92
+ def initialize seconds
93
+ @years = @months = @days = @hours = @minutes = 0
94
+ @seconds = seconds
95
+
96
+ compute_measures seconds
97
+ end
98
+
99
+ private
100
+ def compute_measures seconds
101
+ quotient = seconds
102
+ remainder = 0
103
+
104
+ unit_of_measures.each do |uom|
105
+ break unless quotient > uom[:amount]
106
+ remainder = quotient % uom[:amount]
107
+ quotient /= uom[:amount]
108
+ assign uom[:name], remainder
109
+ assign uom[:parent], quotient.to_i
110
+ end
111
+ end
112
+
113
+ def unit_of_measures
114
+ [ { :name => :seconds=, :parent => :minutes=, :amount => 60 },
115
+ { :name => :minutes=, :parent => :hours=, :amount => 60 },
116
+ { :name => :hours=, :parent => :days=, :amount => 24 },
117
+ { :name => :days=, :parent => :months=, :amount => 30 },
118
+ { :name => :months=, :parent => :years=, :amount => 12 }
119
+ ]
120
+ end
121
+
122
+ def assign setter, value
123
+ self.send setter, value
124
+ end
125
+ end
126
+ private
127
+
128
+ def humanize_time_unit(number, unit, pluralize, keep_zero)
129
+ return '' if number == 0 && !keep_zero
130
+ res = "#{number}#{unit}"
131
+ # A poor man's pluralizer
132
+ res << 's' if !(number == 1) && pluralize
133
+ res
134
+ end
135
+
136
+ def calculate_from_words(string, opts)
137
+ val = 0
138
+ words = string.split(' ')
139
+ words.each_with_index do |v, k|
140
+ if v =~ float_matcher
141
+ val += (convert_to_number(v) * duration_units_seconds_multiplier(words[k + 1] || (opts[:default_unit] || 'seconds')))
142
+ end
143
+ end
144
+ val
145
+ end
146
+
147
+ def cleanup(string)
148
+ res = string.downcase
149
+ res = filter_by_type(Numerizer.numerize(res))
150
+ res = res.gsub(float_matcher) {|n| " #{n} "}.squeeze(' ').strip
151
+ res = filter_through_white_list(res)
152
+ end
153
+
154
+ def convert_to_number(string)
155
+ string.to_f % 1 > 0 ? string.to_f : string.to_i
156
+ end
157
+
158
+ def duration_units_list
159
+ %w(seconds minutes hours days weeks months years)
160
+ end
161
+ def duration_units_seconds_multiplier(unit)
162
+ return 0 unless duration_units_list.include?(unit)
163
+ case unit
164
+ when 'years'; 31536000 # doesn't accounts for leap years
165
+ when 'months'; 3600 * 24 * 30
166
+ when 'weeks'; 3600 * 24 * 7
167
+ when 'days'; 3600 * 24
168
+ when 'hours'; 3600
169
+ when 'minutes'; 60
170
+ when 'seconds'; 1
171
+ end
172
+ end
173
+
174
+ def error_message
175
+ 'Sorry, that duration could not be parsed'
176
+ end
177
+
178
+ # Parse 3:41:59 and return 3 hours 41 minutes 59 seconds
179
+ def filter_by_type(string)
180
+ if string.gsub(' ', '') =~ /#{float_matcher}(:#{float_matcher})+/
181
+ res = []
182
+ string.gsub(' ', '').split(':').reverse.each_with_index do |v,k|
183
+ return unless duration_units_list[k]
184
+ res << "#{v} #{duration_units_list[k]}"
185
+ end
186
+ res = res.reverse.join(' ')
187
+ else
188
+ res = string
189
+ end
190
+ res
191
+ end
192
+
193
+ def float_matcher
194
+ /[0-9]*\.?[0-9]+/
195
+ end
196
+
197
+ # Get rid of unknown words and map found
198
+ # words to defined time units
199
+ def filter_through_white_list(string)
200
+ res = []
201
+ string.split(' ').each do |word|
202
+ if word =~ float_matcher
203
+ res << word.strip
204
+ next
205
+ end
206
+ stripped_word = word.strip.gsub(/^,/, '').gsub(/,$/, '')
207
+ if mappings.has_key?(stripped_word)
208
+ res << mappings[stripped_word]
209
+ elsif !join_words.include?(stripped_word) and ChronicDuration.raise_exceptions
210
+ raise DurationParseError, "An invalid word #{word.inspect} was used in the string to be parsed."
211
+ end
212
+ end
213
+ res.join(' ')
214
+ end
215
+
216
+ def mappings
217
+ {
218
+ 'seconds' => 'seconds',
219
+ 'second' => 'seconds',
220
+ 'secs' => 'seconds',
221
+ 'sec' => 'seconds',
222
+ 's' => 'seconds',
223
+ 'minutes' => 'minutes',
224
+ 'minute' => 'minutes',
225
+ 'mins' => 'minutes',
226
+ 'min' => 'minutes',
227
+ 'm' => 'minutes',
228
+ 'hours' => 'hours',
229
+ 'hour' => 'hours',
230
+ 'hrs' => 'hours',
231
+ 'hr' => 'hours',
232
+ 'h' => 'hours',
233
+ 'days' => 'days',
234
+ 'day' => 'days',
235
+ 'dy' => 'days',
236
+ 'd' => 'days',
237
+ 'weeks' => 'weeks',
238
+ 'week' => 'weeks',
239
+ 'w' => 'weeks',
240
+ 'months' => 'months',
241
+ 'mos' => 'months',
242
+ 'month' => 'months',
243
+ 'years' => 'years',
244
+ 'year' => 'years',
245
+ 'yrs' => 'years',
246
+ 'y' => 'years'
247
+ }
248
+ end
249
+
250
+ def join_words
251
+ ['and', 'with', 'plus']
252
+ end
253
+
254
+ def white_list
255
+ self.mappings.map {|k, v| k}
256
+ end
257
+
258
+ end
@@ -0,0 +1,200 @@
1
+ require 'chronic_duration'
2
+
3
+ describe ChronicDuration, '.parse' do
4
+
5
+ @exemplars = {
6
+ '1:20' => 60 + 20,
7
+ '1:20.51' => 60 + 20.51,
8
+ '4:01:01' => 4 * 3600 + 60 + 1,
9
+ '3 mins 4 sec' => 3 * 60 + 4,
10
+ '3 Mins 4 Sec' => 3 * 60 + 4,
11
+ 'three mins four sec' => 3 * 60 + 4,
12
+ '2 hrs 20 min' => 2 * 3600 + 20 * 60,
13
+ '2h20min' => 2 * 3600 + 20 * 60,
14
+ '6 mos 1 day' => 6 * 30 * 24 * 3600 + 24 * 3600,
15
+ '1 year 6 mos 1 day' => 1 * 31536000 + 6 * 30 * 24 * 3600 + 24 * 3600,
16
+ '2.5 hrs' => 2.5 * 3600,
17
+ '47 yrs 6 mos and 4.5d' => 47 * 31536000 + 6 * 30 * 24 * 3600 + 4.5 * 24 * 3600,
18
+ 'two hours and twenty minutes' => 2 * 3600 + 20 * 60,
19
+ 'four hours and forty minutes' => 4 * 3600 + 40 * 60,
20
+ 'four hours, and fourty minutes' => 4 * 3600 + 40 * 60,
21
+ '3 weeks and, 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
22
+ '3 weeks, plus 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
23
+ '3 weeks with 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
24
+ '1 month' => 3600 * 24 * 30,
25
+ '2 months' => 3600 * 24 * 30 * 2
26
+ }
27
+
28
+ it "should return nil if the string can't be parsed" do
29
+ ChronicDuration.parse('gobblygoo').should be_nil
30
+ end
31
+
32
+ it "should raise an exception if the string can't be parsed and @@raise_exceptions is set to true" do
33
+ ChronicDuration.raise_exceptions = true
34
+ lambda { ChronicDuration.parse('23 gobblygoos') }.should raise_exception(ChronicDuration::DurationParseError)
35
+ ChronicDuration.raise_exceptions = false
36
+ end
37
+
38
+ it "should return a float if seconds are in decimals" do
39
+ ChronicDuration.parse('12 mins 3.141 seconds').is_a?(Float).should be_true
40
+ end
41
+
42
+ it "should return an integer unless the seconds are in decimals" do
43
+ ChronicDuration.parse('12 mins 3 seconds').is_a?(Integer).should be_true
44
+ end
45
+
46
+ it "should be able to parse minutes by default" do
47
+ ChronicDuration.parse('5', :default_unit => "minutes").should == 300
48
+ end
49
+
50
+ @exemplars.each do |k, v|
51
+ it "should properly parse a duration like #{k}" do
52
+ ChronicDuration.parse(k).should == v
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ describe ChronicDuration, '.output' do
59
+
60
+ it "should return nil if the input can't be parsed" do
61
+ ChronicDuration.parse('gobblygoo').should be_nil
62
+ end
63
+
64
+ def self.exemplars
65
+ {
66
+ #(0) =>
67
+ #{
68
+ #:micro => '0s',
69
+ #:short => '0s',
70
+ #:default => '0 secs',
71
+ #:long => '0 seconds',
72
+ #:chrono => '0'
73
+ #},
74
+ (60 + 20) =>
75
+ {
76
+ :micro => '1m20s',
77
+ :short => '1m 20s',
78
+ :default => '1 min 20 secs',
79
+ :long => '1 minute 20 seconds',
80
+ :chrono => '1:20'
81
+ },
82
+ (60 + 20.51) =>
83
+ {
84
+ :micro => '1m20.51s',
85
+ :short => '1m 20.51s',
86
+ :default => '1 min 20.51 secs',
87
+ :long => '1 minute 20.51 seconds',
88
+ :chrono => '1:20.51'
89
+ },
90
+ (60 + 20.51928) =>
91
+ {
92
+ :micro => '1m20.51928s',
93
+ :short => '1m 20.51928s',
94
+ :default => '1 min 20.51928 secs',
95
+ :long => '1 minute 20.51928 seconds',
96
+ :chrono => '1:20.51928'
97
+ },
98
+ (4 * 3600 + 60 + 1) =>
99
+ {
100
+ :micro => '4h1m1s',
101
+ :short => '4h 1m 1s',
102
+ :default => '4 hrs 1 min 1 sec',
103
+ :long => '4 hours 1 minute 1 second',
104
+ :chrono => '4:01:01'
105
+ },
106
+ (2 * 3600 + 20 * 60) =>
107
+ {
108
+ :micro => '2h20m',
109
+ :short => '2h 20m',
110
+ :default => '2 hrs 20 mins',
111
+ :long => '2 hours 20 minutes',
112
+ :chrono => '2:20'
113
+ },
114
+ (2 * 3600 + 20 * 60) =>
115
+ {
116
+ :micro => '2h20m',
117
+ :short => '2h 20m',
118
+ :default => '2 hrs 20 mins',
119
+ :long => '2 hours 20 minutes',
120
+ :chrono => '2:20:00'
121
+ },
122
+ (6 * 30 * 24 * 3600 + 24 * 3600) =>
123
+ {
124
+ :micro => '6m1d',
125
+ :short => '6m 1d',
126
+ :default => '6 mos 1 day',
127
+ :long => '6 months 1 day',
128
+ :chrono => '6:01:00:00:00' # Yuck. FIXME
129
+ }
130
+ }
131
+ end
132
+
133
+ exemplars.each do |number_seconds, test_cases|
134
+ test_cases.each do |format, expected_output|
135
+ it "should properly output a duration of #{number_seconds} seconds as #{expected_output} using the #{format.to_s} format option" do
136
+ ChronicDuration.output(number_seconds, :format => format).should == expected_output
137
+ end
138
+ end
139
+ end
140
+
141
+ context "using :hide_seconds" do
142
+ it "should return nil if less than 60 seconds are passed" do
143
+ ChronicDuration.output(59, :hide_seconds => true).should be_nil
144
+ end
145
+
146
+ exemplars.each do |number_seconds, test_cases|
147
+ test_cases.each do |format, expected_output|
148
+ regex = (format == :chrono) ? /:\d\d\.?\d*$/ : /\ ?\d+\.?\d*\ ?(seconds*|secs*|s)/
149
+ expected_output_without_secs = expected_output.gsub(regex, "")
150
+ it "should properly output a duration of #{number_seconds} seconds as #{expected_output_without_secs} using the #{format.to_s} format option" do
151
+ ChronicDuration.output(number_seconds, :format => format, :hide_seconds => true).should == expected_output_without_secs
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ it "should use the default format when the format is not specified" do
158
+ ChronicDuration.output(2 * 3600 + 20 * 60).should == '2 hrs 20 mins'
159
+ end
160
+ end
161
+
162
+
163
+ # Some of the private methods deserve some spec'ing to aid
164
+ # us in development...
165
+
166
+ describe ChronicDuration, "private methods" do
167
+
168
+ describe ".filter_by_type" do
169
+
170
+ it "should take a chrono-formatted time like 3:14 and return a human time like 3 minutes 14 seconds" do
171
+ ChronicDuration.instance_eval("filter_by_type('3:14')").should == '3 minutes 14 seconds'
172
+ end
173
+
174
+ it "should take a chrono-formatted time like 12:10:14 and return a human time like 12 hours 10 minutes 14 seconds" do
175
+ ChronicDuration.instance_eval("filter_by_type('12:10:14')").should == '12 hours 10 minutes 14 seconds'
176
+ end
177
+
178
+ it "should return the input if it's not a chrono-formatted time" do
179
+ ChronicDuration.instance_eval("filter_by_type('4 hours')").should == '4 hours'
180
+ end
181
+
182
+ end
183
+
184
+ describe ".cleanup" do
185
+
186
+ it "should clean up extraneous words" do
187
+ ChronicDuration.instance_eval("cleanup('4 days and 11 hours')").should == '4 days 11 hours'
188
+ end
189
+
190
+ it "should cleanup extraneous spaces" do
191
+ ChronicDuration.instance_eval("cleanup(' 4 days and 11 hours')").should == '4 days 11 hours'
192
+ end
193
+
194
+ it "should insert spaces where there aren't any" do
195
+ ChronicDuration.instance_eval("cleanup('4m11.5s')").should == '4 minutes 11.5 seconds'
196
+ end
197
+
198
+ end
199
+
200
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arturop-chronic_duration
3
+ version: !ruby/object:Gem::Version
4
+ hash: 53
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 9
9
+ - 7
10
+ version: 0.9.7
11
+ platform: ruby
12
+ authors:
13
+ - hpoydar
14
+ - Arturo Pie
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-07-06 00:00:00 -04:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 25
29
+ segments:
30
+ - 0
31
+ - 1
32
+ - 1
33
+ version: 0.1.1
34
+ requirement: *id001
35
+ prerelease: false
36
+ type: :runtime
37
+ name: numerizer
38
+ - !ruby/object:Gem::Dependency
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 2
47
+ - 3
48
+ - 0
49
+ version: 2.3.0
50
+ requirement: *id002
51
+ prerelease: false
52
+ type: :development
53
+ name: rspec
54
+ - !ruby/object:Gem::Dependency
55
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 23
61
+ segments:
62
+ - 1
63
+ - 0
64
+ - 0
65
+ version: 1.0.0
66
+ requirement: *id003
67
+ prerelease: false
68
+ type: :development
69
+ name: bundler
70
+ - !ruby/object:Gem::Dependency
71
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 7
77
+ segments:
78
+ - 1
79
+ - 5
80
+ - 2
81
+ version: 1.5.2
82
+ requirement: *id004
83
+ prerelease: false
84
+ type: :development
85
+ name: jeweler
86
+ - !ruby/object:Gem::Dependency
87
+ version_requirements: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ requirement: *id005
97
+ prerelease: false
98
+ type: :development
99
+ name: rcov
100
+ description: A simple Ruby natural language parser for elapsed time. (For example, 4 hours and 30 minutes, 6 minutes 4 seconds, 3 days, etc.) Returns all results in seconds. Will return an integer unless you get tricky and need a float. (4 minutes and 13.47 seconds, for example.) The reverse can also be performed via the output method.
101
+ email: hpoydar@gmail.com
102
+ executables: []
103
+
104
+ extensions: []
105
+
106
+ extra_rdoc_files:
107
+ - README.rdoc
108
+ files:
109
+ - Gemfile
110
+ - Gemfile.lock
111
+ - MIT-LICENSE
112
+ - README.rdoc
113
+ - Rakefile
114
+ - VERSION
115
+ - chronic_duration.gemspec
116
+ - lib/chronic_duration.rb
117
+ - spec/chronic_duration_spec.rb
118
+ has_rdoc: true
119
+ homepage: http://github.com/hpoydar/chronic_duration
120
+ licenses:
121
+ - MIT
122
+ post_install_message:
123
+ rdoc_options: []
124
+
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ hash: 3
133
+ segments:
134
+ - 0
135
+ version: "0"
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ requirements: []
146
+
147
+ rubyforge_project:
148
+ rubygems_version: 1.3.7
149
+ signing_key:
150
+ specification_version: 3
151
+ summary: A Ruby natural language parser for elapsed time
152
+ test_files:
153
+ - spec/chronic_duration_spec.rb