arturop-chronic_duration 0.9.7

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.
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