chronic_duration 0.9.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile CHANGED
@@ -1,10 +1,2 @@
1
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
2
+ gemspec
@@ -1,15 +1,10 @@
1
- = Chronic Duration
1
+ # Chronic Duration
2
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.)
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
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.
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
6
 
7
- == Installation
8
-
9
- $ sudo gem sources -a http://gemcutter.org
10
- $ sudo gem install chronic_duration
11
-
12
- == Usage
7
+ ## Usage
13
8
 
14
9
  >> require 'chronic_duration'
15
10
  => true
@@ -23,13 +18,19 @@ The reverse can also be accomplished with the output method. So pass in seconds
23
18
  => 4 minutes 30 seconds
24
19
  >> ChronicDuration.output(270, :format => :chrono)
25
20
  => 4:30
26
-
21
+ >> ChronicDuration.output(1299600, :weeks => true)
22
+ => 2 wks 1 day 1 hr
23
+ >> ChronicDuration.output(1299600, :weeks => true, :units => 2)
24
+ => 2 wks 1 day
25
+ >> ChronicDuration.output(1296000)
26
+ => 15 days
27
+
27
28
  Nil is returned if the string can't be parsed
28
29
 
29
30
  Examples of parse-able strings:
30
31
 
31
32
  * '12.4 secs'
32
- * '1:20'
33
+ * '1:20'
33
34
  * '1:20.51'
34
35
  * '4:01:01'
35
36
  * '3 mins 4 sec'
@@ -47,12 +48,12 @@ ChronicDuration.raise_exceptions can be set to true to raise exceptions when the
47
48
  >> ChronicDuration.parse('4 elephants and 3 Astroids')
48
49
  ChronicDuration::DurationParseError: An invalid word "elephants" was used in the string to be parsed.
49
50
 
50
-
51
- == Contributors
51
+
52
+ ## Contributors
52
53
 
53
54
  brianjlandau, jduff, olauzon, roboman, ianlevesque
54
-
55
- == TODO
55
+
56
+ ## TODO
56
57
 
57
58
  * Benchmark, optimize
58
59
  * Context specific matching (E.g., for '4m30s', assume 'm' is minutes not months)
data/Rakefile CHANGED
@@ -1,45 +1,6 @@
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 = "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"]
21
- end
22
- Jeweler::RubygemsDotOrgTasks.new
23
-
24
- require 'rspec/core'
1
+ require "bundler/gem_tasks"
25
2
  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
3
 
4
+ RSpec::Core::RakeTask.new('spec')
35
5
  task :default => :spec
36
6
 
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
@@ -1,62 +1,26 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
1
  # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'chronic_duration/version'
5
5
 
6
- Gem::Specification.new do |s|
7
- s.name = %q{chronic_duration}
8
- s.version = "0.9.6"
6
+ Gem::Specification.new do |gem|
9
7
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["hpoydar"]
12
- s.date = %q{2011-06-04}
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.5.2}
33
- s.summary = %q{A Ruby natural language parser for elapsed time}
34
- s.test_files = [
35
- "spec/chronic_duration_spec.rb"
36
- ]
8
+ gem.name = "chronic_duration"
9
+ gem.version = ChronicDuration::VERSION
10
+ gem.authors = ["hpoydar"]
11
+ gem.email = ["henry@poydar.com"]
12
+ 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.}
13
+ gem.summary = %q{A simple Ruby natural language parser for elapsed time}
14
+ gem.homepage = "https://github.com/hpoydar/chronic_duration"
37
15
 
38
- if s.respond_to? :specification_version then
39
- s.specification_version = 3
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
40
20
 
41
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
- s.add_runtime_dependency(%q<numerizer>, ["~> 0.1.1"])
43
- s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
44
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
45
- s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
46
- s.add_development_dependency(%q<rcov>, [">= 0"])
47
- else
48
- s.add_dependency(%q<numerizer>, ["~> 0.1.1"])
49
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
50
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
51
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
52
- s.add_dependency(%q<rcov>, [">= 0"])
53
- end
54
- else
55
- s.add_dependency(%q<numerizer>, ["~> 0.1.1"])
56
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
57
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
58
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
59
- s.add_dependency(%q<rcov>, [">= 0"])
60
- end
61
- end
21
+ gem.add_runtime_dependency "numerizer", "~> 0.1.1"
22
+
23
+ gem.add_development_dependency "rake", "~> 10.0.3"
24
+ gem.add_development_dependency "rspec", "~> 2.12.0"
62
25
 
26
+ end
@@ -1,40 +1,42 @@
1
1
  require 'numerizer' unless defined?(Numerizer)
2
+
2
3
  module ChronicDuration
4
+
3
5
  extend self
4
-
6
+
5
7
  class DurationParseError < StandardError
6
8
  end
7
-
9
+
8
10
  @@raise_exceptions = false
9
-
11
+
10
12
  def self.raise_exceptions
11
13
  !!@@raise_exceptions
12
14
  end
13
-
15
+
14
16
  def self.raise_exceptions=(value)
15
17
  @@raise_exceptions = !!value
16
18
  end
17
-
19
+
18
20
  # Given a string representation of elapsed time,
19
21
  # return an integer (or float, if fractions of a
20
22
  # second are input)
21
23
  def parse(string, opts = {})
22
24
  result = calculate_from_words(cleanup(string), opts)
23
25
  result == 0 ? nil : result
24
- end
25
-
26
+ end
27
+
26
28
  # Given an integer and an optional format,
27
29
  # returns a formatted string representing elapsed time
28
30
  def output(seconds, opts = {})
29
-
31
+
30
32
  opts[:format] ||= :default
31
-
32
- years = months = days = hours = minutes = 0
33
-
33
+
34
+ years = months = weeks = days = hours = minutes = 0
35
+
34
36
  decimal_places = seconds.to_s.split('.').last.length if seconds.is_a?(Float)
35
37
 
36
38
  if seconds >= 60
37
- minutes = (seconds / 60).to_i
39
+ minutes = (seconds / 60).to_i
38
40
  seconds = seconds % 60
39
41
  if minutes >= 60
40
42
  hours = (minutes / 60).to_i
@@ -42,40 +44,52 @@ module ChronicDuration
42
44
  if hours >= 24
43
45
  days = (hours / 24).to_i
44
46
  hours = (hours % 24).to_i
45
- if days >= 30
46
- months = (days / 30).to_i
47
- days = (days % 30).to_i
48
- if months >= 12
49
- years = (months / 12).to_i
50
- months = (months % 12).to_i
47
+ if opts[:weeks]
48
+ if days >= 7
49
+ weeks = (days / 7).to_i
50
+ days = (days % 7).to_i
51
+ if weeks >= 4
52
+ months = (weeks / 4).to_i
53
+ weeks = (weeks % 4).to_i
54
+ end
55
+ end
56
+ else
57
+ if days >= 30
58
+ months = (days / 30).to_i
59
+ days = (days % 30).to_i
51
60
  end
52
61
  end
62
+ if months >= 12
63
+ years = (months / 12).to_i
64
+ months = (months % 12).to_i
65
+ days = days - (5 * years)
66
+ end
53
67
  end
54
68
  end
55
69
  end
56
-
70
+
57
71
  joiner = ' '
58
72
  process = nil
59
-
73
+
60
74
  case opts[:format]
61
75
  when :micro
62
- dividers = {
63
- :years => 'y', :months => 'm', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
76
+ dividers = {
77
+ :years => 'y', :months => 'mo', :weeks => 'w', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
64
78
  joiner = ''
65
79
  when :short
66
- dividers = {
67
- :years => 'y', :months => 'm', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
68
- when :default
69
80
  dividers = {
70
- :years => ' yr', :months => ' mo', :days => ' day', :hours => ' hr', :minutes => ' min', :seconds => ' sec',
81
+ :years => 'y', :months => 'mo', :weeks => 'w', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
82
+ when :default
83
+ dividers = {
84
+ :years => ' yr', :months => ' mo', :weeks => ' wk', :days => ' day', :hours => ' hr', :minutes => ' min', :seconds => ' sec',
71
85
  :pluralize => true }
72
- when :long
86
+ when :long
73
87
  dividers = {
74
- :years => ' year', :months => ' month', :days => ' day', :hours => ' hour', :minutes => ' minute', :seconds => ' second',
88
+ :years => ' year', :months => ' month', :weeks => ' week', :days => ' day', :hours => ' hour', :minutes => ' minute', :seconds => ' second',
75
89
  :pluralize => true }
76
90
  when :chrono
77
91
  dividers = {
78
- :years => ':', :months => ':', :days => ':', :hours => ':', :minutes => ':', :seconds => ':', :keep_zero => true }
92
+ :years => ':', :months => ':', :weeks => ':', :days => ':', :hours => ':', :minutes => ':', :seconds => ':', :keep_zero => true }
79
93
  process = lambda do |str|
80
94
  # Pad zeros
81
95
  # Get rid of lead off times if they are zero
@@ -85,34 +99,36 @@ module ChronicDuration
85
99
  end
86
100
  joiner = ''
87
101
  end
88
-
89
- result = []
90
- [:years, :months, :days, :hours, :minutes, :seconds].each do |t|
102
+
103
+ result = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].map do |t|
104
+ next if t == :weeks && !opts[:weeks]
91
105
  num = eval(t.to_s)
92
- num = ("%.#{decimal_places}f" % num) if num.is_a?(Float) && t == :seconds
93
- result << humanize_time_unit( num, dividers[t], dividers[:pluralize], dividers[:keep_zero] )
94
- end
106
+ num = ("%.#{decimal_places}f" % num) if num.is_a?(Float) && t == :seconds
107
+ humanize_time_unit( num, dividers[t], dividers[:pluralize], dividers[:keep_zero] )
108
+ end.compact!
109
+
110
+ result = result[0...opts[:units]] if opts[:units]
111
+
112
+ result = result.join(joiner)
95
113
 
96
- result = result.join(joiner).squeeze(' ').strip
97
-
98
114
  if process
99
115
  result = process.call(result)
100
116
  end
101
-
117
+
102
118
  result.length == 0 ? nil : result
103
119
 
104
120
  end
105
-
121
+
106
122
  private
107
-
123
+
108
124
  def humanize_time_unit(number, unit, pluralize, keep_zero)
109
- return '' if number == 0 && !keep_zero
125
+ return nil if number == 0 && !keep_zero
110
126
  res = "#{number}#{unit}"
111
127
  # A poor man's pluralizer
112
128
  res << 's' if !(number == 1) && pluralize
113
129
  res
114
130
  end
115
-
131
+
116
132
  def calculate_from_words(string, opts)
117
133
  val = 0
118
134
  words = string.split(' ')
@@ -123,18 +139,18 @@ private
123
139
  end
124
140
  val
125
141
  end
126
-
142
+
127
143
  def cleanup(string)
128
144
  res = string.downcase
129
145
  res = filter_by_type(Numerizer.numerize(res))
130
146
  res = res.gsub(float_matcher) {|n| " #{n} "}.squeeze(' ').strip
131
147
  res = filter_through_white_list(res)
132
148
  end
133
-
149
+
134
150
  def convert_to_number(string)
135
151
  string.to_f % 1 > 0 ? string.to_f : string.to_i
136
152
  end
137
-
153
+
138
154
  def duration_units_list
139
155
  %w(seconds minutes hours days weeks months years)
140
156
  end
@@ -150,18 +166,19 @@ private
150
166
  when 'seconds'; 1
151
167
  end
152
168
  end
153
-
169
+
154
170
  def error_message
155
171
  'Sorry, that duration could not be parsed'
156
172
  end
157
-
173
+
158
174
  # Parse 3:41:59 and return 3 hours 41 minutes 59 seconds
159
175
  def filter_by_type(string)
176
+ chrono_units_list = duration_units_list.reject {|v| v == "weeks"}
160
177
  if string.gsub(' ', '') =~ /#{float_matcher}(:#{float_matcher})+/
161
178
  res = []
162
179
  string.gsub(' ', '').split(':').reverse.each_with_index do |v,k|
163
- return unless duration_units_list[k]
164
- res << "#{v} #{duration_units_list[k]}"
180
+ return unless chrono_units_list[k]
181
+ res << "#{v} #{chrono_units_list[k]}"
165
182
  end
166
183
  res = res.reverse.join(' ')
167
184
  else
@@ -169,11 +186,11 @@ private
169
186
  end
170
187
  res
171
188
  end
172
-
189
+
173
190
  def float_matcher
174
191
  /[0-9]*\.?[0-9]+/
175
192
  end
176
-
193
+
177
194
  # Get rid of unknown words and map found
178
195
  # words to defined time units
179
196
  def filter_through_white_list(string)
@@ -190,11 +207,13 @@ private
190
207
  raise DurationParseError, "An invalid word #{word.inspect} was used in the string to be parsed."
191
208
  end
192
209
  end
210
+ # add '1' at front if string starts with something recognizable but not with a number, like 'day' or 'minute 30sec'
211
+ res.unshift(1) if res.length > 0 && mappings[res[0]]
193
212
  res.join(' ')
194
213
  end
195
-
214
+
196
215
  def mappings
197
- {
216
+ {
198
217
  'seconds' => 'seconds',
199
218
  'second' => 'seconds',
200
219
  'secs' => 'seconds',
@@ -218,21 +237,23 @@ private
218
237
  'week' => 'weeks',
219
238
  'w' => 'weeks',
220
239
  'months' => 'months',
240
+ 'mo' => 'months',
221
241
  'mos' => 'months',
222
242
  'month' => 'months',
223
243
  'years' => 'years',
224
244
  'year' => 'years',
225
245
  'yrs' => 'years',
246
+ 'yr' => 'years',
226
247
  'y' => 'years'
227
248
  }
228
249
  end
229
-
250
+
230
251
  def join_words
231
252
  ['and', 'with', 'plus']
232
253
  end
233
-
254
+
234
255
  def white_list
235
- self.mappings.map {|k, v| k}
256
+ self.mappings.keys
236
257
  end
237
-
258
+
238
259
  end
@@ -0,0 +1,3 @@
1
+ module ChronicDuration
2
+ VERSION = '0.10.0'
3
+ end
@@ -1,8 +1,8 @@
1
1
  require 'chronic_duration'
2
2
 
3
3
  describe ChronicDuration, '.parse' do
4
-
5
- @exemplars = {
4
+
5
+ @exemplars = {
6
6
  '1:20' => 60 + 20,
7
7
  '1:20.51' => 60 + 20.51,
8
8
  '4:01:01' => 4 * 3600 + 60 + 1,
@@ -22,47 +22,49 @@ describe ChronicDuration, '.parse' do
22
22
  '3 weeks, plus 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
23
23
  '3 weeks with 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
24
24
  '1 month' => 3600 * 24 * 30,
25
- '2 months' => 3600 * 24 * 30 * 2
25
+ '2 months' => 3600 * 24 * 30 * 2,
26
+ 'day' => 3600 * 24,
27
+ 'minute 30s' => 90
26
28
  }
27
-
29
+
28
30
  it "should return nil if the string can't be parsed" do
29
31
  ChronicDuration.parse('gobblygoo').should be_nil
30
32
  end
31
-
33
+
32
34
  it "should raise an exception if the string can't be parsed and @@raise_exceptions is set to true" do
33
35
  ChronicDuration.raise_exceptions = true
34
36
  lambda { ChronicDuration.parse('23 gobblygoos') }.should raise_exception(ChronicDuration::DurationParseError)
35
37
  ChronicDuration.raise_exceptions = false
36
38
  end
37
-
39
+
38
40
  it "should return a float if seconds are in decimals" do
39
41
  ChronicDuration.parse('12 mins 3.141 seconds').is_a?(Float).should be_true
40
42
  end
41
-
43
+
42
44
  it "should return an integer unless the seconds are in decimals" do
43
45
  ChronicDuration.parse('12 mins 3 seconds').is_a?(Integer).should be_true
44
46
  end
45
-
47
+
46
48
  it "should be able to parse minutes by default" do
47
49
  ChronicDuration.parse('5', :default_unit => "minutes").should == 300
48
50
  end
49
-
51
+
50
52
  @exemplars.each do |k, v|
51
53
  it "should properly parse a duration like #{k}" do
52
54
  ChronicDuration.parse(k).should == v
53
55
  end
54
56
  end
55
-
57
+
56
58
  end
57
59
 
58
60
  describe ChronicDuration, '.output' do
59
-
61
+
60
62
  it "should return nil if the input can't be parsed" do
61
63
  ChronicDuration.parse('gobblygoo').should be_nil
62
64
  end
63
-
64
- @exemplars = {
65
- #(0) =>
65
+
66
+ @exemplars = {
67
+ #(0) =>
66
68
  #{
67
69
  #:micro => '0s',
68
70
  #:short => '0s',
@@ -70,64 +72,80 @@ describe ChronicDuration, '.output' do
70
72
  #:long => '0 seconds',
71
73
  #:chrono => '0'
72
74
  #},
73
- (60 + 20) =>
74
- {
75
+ (60 + 20) =>
76
+ {
75
77
  :micro => '1m20s',
76
78
  :short => '1m 20s',
77
79
  :default => '1 min 20 secs',
78
80
  :long => '1 minute 20 seconds',
79
81
  :chrono => '1:20'
80
82
  },
81
- (60 + 20.51) =>
82
- {
83
+ (60 + 20.51) =>
84
+ {
83
85
  :micro => '1m20.51s',
84
86
  :short => '1m 20.51s',
85
87
  :default => '1 min 20.51 secs',
86
88
  :long => '1 minute 20.51 seconds',
87
89
  :chrono => '1:20.51'
88
90
  },
89
- (60 + 20.51928) =>
90
- {
91
+ (60 + 20.51928) =>
92
+ {
91
93
  :micro => '1m20.51928s',
92
94
  :short => '1m 20.51928s',
93
95
  :default => '1 min 20.51928 secs',
94
96
  :long => '1 minute 20.51928 seconds',
95
97
  :chrono => '1:20.51928'
96
98
  },
97
- (4 * 3600 + 60 + 1) =>
98
- {
99
+ (4 * 3600 + 60 + 1) =>
100
+ {
99
101
  :micro => '4h1m1s',
100
102
  :short => '4h 1m 1s',
101
103
  :default => '4 hrs 1 min 1 sec',
102
104
  :long => '4 hours 1 minute 1 second',
103
105
  :chrono => '4:01:01'
104
106
  },
105
- (2 * 3600 + 20 * 60) =>
106
- {
107
+ (2 * 3600 + 20 * 60) =>
108
+ {
107
109
  :micro => '2h20m',
108
110
  :short => '2h 20m',
109
111
  :default => '2 hrs 20 mins',
110
112
  :long => '2 hours 20 minutes',
111
113
  :chrono => '2:20'
112
114
  },
113
- (2 * 3600 + 20 * 60) =>
114
- {
115
+ (2 * 3600 + 20 * 60) =>
116
+ {
115
117
  :micro => '2h20m',
116
118
  :short => '2h 20m',
117
119
  :default => '2 hrs 20 mins',
118
120
  :long => '2 hours 20 minutes',
119
121
  :chrono => '2:20:00'
120
122
  },
121
- (6 * 30 * 24 * 3600 + 24 * 3600) =>
122
- {
123
- :micro => '6m1d',
124
- :short => '6m 1d',
123
+ (6 * 30 * 24 * 3600 + 24 * 3600) =>
124
+ {
125
+ :micro => '6mo1d',
126
+ :short => '6mo 1d',
125
127
  :default => '6 mos 1 day',
126
128
  :long => '6 months 1 day',
127
129
  :chrono => '6:01:00:00:00' # Yuck. FIXME
128
- }
130
+ },
131
+ (365 * 24 * 3600 + 24 * 3600 ) =>
132
+ {
133
+ :micro => '1y1d',
134
+ :short => '1y 1d',
135
+ :default => '1 yr 1 day',
136
+ :long => '1 year 1 day',
137
+ :chrono => '1:00:01:00:00:00'
138
+ },
139
+ (3 * 365 * 24 * 3600 + 24 * 3600 ) =>
140
+ {
141
+ :micro => '3y1d',
142
+ :short => '3y 1d',
143
+ :default => '3 yrs 1 day',
144
+ :long => '3 years 1 day',
145
+ :chrono => '3:00:01:00:00:00'
146
+ },
129
147
  }
130
-
148
+
131
149
  @exemplars.each do |k, v|
132
150
  v.each do |key, val|
133
151
  it "should properly output a duration of #{k} seconds as #{val} using the #{key.to_s} format option" do
@@ -135,12 +153,27 @@ describe ChronicDuration, '.output' do
135
153
  end
136
154
  end
137
155
  end
138
-
156
+
157
+ it "should show weeks when needed" do
158
+ ChronicDuration.output(15*24*60*60, :weeks => true).should =~ /.*wk.*/
159
+ end
160
+
161
+ it "should show the specified number of units if provided" do
162
+ ChronicDuration.output(4 * 3600 + 60 + 1, units: 2).should == '4 hrs 1 min'
163
+ ChronicDuration.output(6 * 30 * 24 * 3600 + 24 * 3600 + 3600 + 60 + 1, units: 3, format: :long).should == '6 months 1 day 1 hour'
164
+ end
165
+
139
166
  it "should use the default format when the format is not specified" do
140
167
  ChronicDuration.output(2 * 3600 + 20 * 60).should == '2 hrs 20 mins'
141
168
  end
142
-
143
-
169
+
170
+ @exemplars.each do |seconds,format_spec|
171
+ format_spec.each do |format,_|
172
+ it "it should properly output a duration for #{seconds} that parses back to the same thing when using the #{format.to_s} format" do
173
+ ChronicDuration.parse(ChronicDuration.output(seconds, :format => format)).should == seconds
174
+ end
175
+ end
176
+ end
144
177
  end
145
178
 
146
179
 
@@ -148,37 +181,37 @@ end
148
181
  # us in development...
149
182
 
150
183
  describe ChronicDuration, "private methods" do
151
-
184
+
152
185
  describe ".filter_by_type" do
153
-
186
+
154
187
  it "should take a chrono-formatted time like 3:14 and return a human time like 3 minutes 14 seconds" do
155
188
  ChronicDuration.instance_eval("filter_by_type('3:14')").should == '3 minutes 14 seconds'
156
189
  end
157
-
190
+
158
191
  it "should take a chrono-formatted time like 12:10:14 and return a human time like 12 hours 10 minutes 14 seconds" do
159
192
  ChronicDuration.instance_eval("filter_by_type('12:10:14')").should == '12 hours 10 minutes 14 seconds'
160
193
  end
161
-
194
+
162
195
  it "should return the input if it's not a chrono-formatted time" do
163
196
  ChronicDuration.instance_eval("filter_by_type('4 hours')").should == '4 hours'
164
197
  end
165
-
198
+
166
199
  end
167
-
200
+
168
201
  describe ".cleanup" do
169
-
202
+
170
203
  it "should clean up extraneous words" do
171
204
  ChronicDuration.instance_eval("cleanup('4 days and 11 hours')").should == '4 days 11 hours'
172
205
  end
173
-
206
+
174
207
  it "should cleanup extraneous spaces" do
175
208
  ChronicDuration.instance_eval("cleanup(' 4 days and 11 hours')").should == '4 days 11 hours'
176
209
  end
177
-
210
+
178
211
  it "should insert spaces where there aren't any" do
179
212
  ChronicDuration.instance_eval("cleanup('4m11.5s')").should == '4 minutes 11.5 seconds'
180
213
  end
181
-
214
+
182
215
  end
183
-
184
- end
216
+
217
+ end
@@ -0,0 +1,212 @@
1
+ require 'spec_helper'
2
+
3
+ describe ChronicDuration do
4
+
5
+ describe ".parse" do
6
+
7
+ @exemplars = {
8
+ '1:20' => 60 + 20,
9
+ '1:20.51' => 60 + 20.51,
10
+ '4:01:01' => 4 * 3600 + 60 + 1,
11
+ '3 mins 4 sec' => 3 * 60 + 4,
12
+ '3 Mins 4 Sec' => 3 * 60 + 4,
13
+ 'three mins four sec' => 3 * 60 + 4,
14
+ '2 hrs 20 min' => 2 * 3600 + 20 * 60,
15
+ '2h20min' => 2 * 3600 + 20 * 60,
16
+ '6 mos 1 day' => 6 * 30 * 24 * 3600 + 24 * 3600,
17
+ '1 year 6 mos 1 day' => 1 * 31536000 + 6 * 30 * 24 * 3600 + 24 * 3600,
18
+ '2.5 hrs' => 2.5 * 3600,
19
+ '47 yrs 6 mos and 4.5d' => 47 * 31536000 + 6 * 30 * 24 * 3600 + 4.5 * 24 * 3600,
20
+ 'two hours and twenty minutes' => 2 * 3600 + 20 * 60,
21
+ 'four hours and forty minutes' => 4 * 3600 + 40 * 60,
22
+ 'four hours, and fourty minutes' => 4 * 3600 + 40 * 60,
23
+ '3 weeks and, 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
24
+ '3 weeks, plus 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
25
+ '3 weeks with 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
26
+ '1 month' => 3600 * 24 * 30,
27
+ '2 months' => 3600 * 24 * 30 * 2
28
+ }
29
+
30
+ context "when string can't be parsed" do
31
+
32
+ it "returns nil" do
33
+ ChronicDuration.parse('gobblygoo').should be_nil
34
+ end
35
+
36
+ context "when @@raise_exceptions set to true" do
37
+
38
+ it "raises with ChronicDuration::DurationParseError" do
39
+ ChronicDuration.raise_exceptions = true
40
+ lambda { ChronicDuration.parse('23 gobblygoos') }.should raise_exception(ChronicDuration::DurationParseError)
41
+ ChronicDuration.raise_exceptions = false
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ it "should return a float if seconds are in decimals" do
49
+ ChronicDuration.parse('12 mins 3.141 seconds').is_a?(Float).should be_true
50
+ end
51
+
52
+ it "should return an integer unless the seconds are in decimals" do
53
+ ChronicDuration.parse('12 mins 3 seconds').is_a?(Integer).should be_true
54
+ end
55
+
56
+ it "should be able to parse minutes by default" do
57
+ ChronicDuration.parse('5', :default_unit => "minutes").should == 300
58
+ end
59
+
60
+ @exemplars.each do |k, v|
61
+ it "parses a duration like #{k}" do
62
+ ChronicDuration.parse(k).should == v
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ describe '.output' do
69
+
70
+ @exemplars = {
71
+ (60 + 20) =>
72
+ {
73
+ :micro => '1m20s',
74
+ :short => '1m 20s',
75
+ :default => '1 min 20 secs',
76
+ :long => '1 minute 20 seconds',
77
+ :chrono => '1:20'
78
+ },
79
+ (60 + 20.51) =>
80
+ {
81
+ :micro => '1m20.51s',
82
+ :short => '1m 20.51s',
83
+ :default => '1 min 20.51 secs',
84
+ :long => '1 minute 20.51 seconds',
85
+ :chrono => '1:20.51'
86
+ },
87
+ (60 + 20.51928) =>
88
+ {
89
+ :micro => '1m20.51928s',
90
+ :short => '1m 20.51928s',
91
+ :default => '1 min 20.51928 secs',
92
+ :long => '1 minute 20.51928 seconds',
93
+ :chrono => '1:20.51928'
94
+ },
95
+ (4 * 3600 + 60 + 1) =>
96
+ {
97
+ :micro => '4h1m1s',
98
+ :short => '4h 1m 1s',
99
+ :default => '4 hrs 1 min 1 sec',
100
+ :long => '4 hours 1 minute 1 second',
101
+ :chrono => '4:01:01'
102
+ },
103
+ (2 * 3600 + 20 * 60) =>
104
+ {
105
+ :micro => '2h20m',
106
+ :short => '2h 20m',
107
+ :default => '2 hrs 20 mins',
108
+ :long => '2 hours 20 minutes',
109
+ :chrono => '2:20'
110
+ },
111
+ (2 * 3600 + 20 * 60) =>
112
+ {
113
+ :micro => '2h20m',
114
+ :short => '2h 20m',
115
+ :default => '2 hrs 20 mins',
116
+ :long => '2 hours 20 minutes',
117
+ :chrono => '2:20:00'
118
+ },
119
+ (6 * 30 * 24 * 3600 + 24 * 3600) =>
120
+ {
121
+ :micro => '6mo1d',
122
+ :short => '6mo 1d',
123
+ :default => '6 mos 1 day',
124
+ :long => '6 months 1 day',
125
+ :chrono => '6:01:00:00:00' # Yuck. FIXME
126
+ },
127
+ (365 * 24 * 3600 + 24 * 3600 ) =>
128
+ {
129
+ :micro => '1y1d',
130
+ :short => '1y 1d',
131
+ :default => '1 yr 1 day',
132
+ :long => '1 year 1 day',
133
+ :chrono => '1:00:01:00:00:00'
134
+ },
135
+ (3 * 365 * 24 * 3600 + 24 * 3600 ) =>
136
+ {
137
+ :micro => '3y1d',
138
+ :short => '3y 1d',
139
+ :default => '3 yrs 1 day',
140
+ :long => '3 years 1 day',
141
+ :chrono => '3:00:01:00:00:00'
142
+ },
143
+ }
144
+
145
+ @exemplars.each do |k, v|
146
+ v.each do |key, val|
147
+ it "properly outputs a duration of #{k} seconds as #{val} using the #{key.to_s} format option" do
148
+ ChronicDuration.output(k, :format => key).should == val
149
+ end
150
+ end
151
+ end
152
+
153
+ it "returns weeks when needed" do
154
+ ChronicDuration.output(15*24*60*60, :weeks => true).should =~ /.*wk.*/
155
+ end
156
+
157
+ it "returns the specified number of units if provided" do
158
+ ChronicDuration.output(4 * 3600 + 60 + 1, units: 2).should == '4 hrs 1 min'
159
+ ChronicDuration.output(6 * 30 * 24 * 3600 + 24 * 3600 + 3600 + 60 + 1, units: 3, format: :long).should == '6 months 1 day 1 hour'
160
+ end
161
+
162
+ context "when the format is not specified" do
163
+
164
+ it "uses the default format" do
165
+ ChronicDuration.output(2 * 3600 + 20 * 60).should == '2 hrs 20 mins'
166
+ end
167
+
168
+ end
169
+
170
+ @exemplars.each do |seconds, format_spec|
171
+ format_spec.each do |format, _|
172
+ it "outputs a duration for #{seconds} that parses back to the same thing when using the #{format.to_s} format" do
173
+ ChronicDuration.parse(ChronicDuration.output(seconds, :format => format)).should == seconds
174
+ end
175
+ end
176
+ end
177
+
178
+ end
179
+
180
+ describe ".filter_by_type" do
181
+
182
+ it "receives a chrono-formatted time like 3:14 and return a human time like 3 minutes 14 seconds" do
183
+ ChronicDuration.instance_eval("filter_by_type('3:14')").should == '3 minutes 14 seconds'
184
+ end
185
+
186
+ it "receives chrono-formatted time like 12:10:14 and return a human time like 12 hours 10 minutes 14 seconds" do
187
+ ChronicDuration.instance_eval("filter_by_type('12:10:14')").should == '12 hours 10 minutes 14 seconds'
188
+ end
189
+
190
+ it "returns the input if it's not a chrono-formatted time" do
191
+ ChronicDuration.instance_eval("filter_by_type('4 hours')").should == '4 hours'
192
+ end
193
+
194
+ end
195
+
196
+ describe ".cleanup" do
197
+
198
+ it "cleans up extraneous words" do
199
+ ChronicDuration.instance_eval("cleanup('4 days and 11 hours')").should == '4 days 11 hours'
200
+ end
201
+
202
+ it "cleans up extraneous spaces" do
203
+ ChronicDuration.instance_eval("cleanup(' 4 days and 11 hours')").should == '4 days 11 hours'
204
+ end
205
+
206
+ it "inserts spaces where there aren't any" do
207
+ ChronicDuration.instance_eval("cleanup('4m11.5s')").should == '4 minutes 11.5 seconds'
208
+ end
209
+
210
+ end
211
+
212
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'chronic_duration'
4
+
5
+ RSpec.configure do |config|
6
+ end
7
+
8
+
metadata CHANGED
@@ -1,121 +1,111 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: chronic_duration
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.0
4
5
  prerelease:
5
- version: 0.9.6
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - hpoydar
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-06-04 00:00:00 -04:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2013-02-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: numerizer
18
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
19
17
  none: false
20
- requirements:
18
+ requirements:
21
19
  - - ~>
22
- - !ruby/object:Gem::Version
20
+ - !ruby/object:Gem::Version
23
21
  version: 0.1.1
24
22
  type: :runtime
25
23
  prerelease: false
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
30
25
  none: false
31
- requirements:
26
+ requirements:
32
27
  - - ~>
33
- - !ruby/object:Gem::Version
34
- version: 2.3.0
35
- type: :development
36
- prerelease: false
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
39
- name: bundler
40
- requirement: &id003 !ruby/object:Gem::Requirement
28
+ - !ruby/object:Gem::Version
29
+ version: 0.1.1
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
41
33
  none: false
42
- requirements:
34
+ requirements:
43
35
  - - ~>
44
- - !ruby/object:Gem::Version
45
- version: 1.0.0
36
+ - !ruby/object:Gem::Version
37
+ version: 10.0.3
46
38
  type: :development
47
39
  prerelease: false
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
50
- name: jeweler
51
- requirement: &id004 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
52
41
  none: false
53
- requirements:
42
+ requirements:
54
43
  - - ~>
55
- - !ruby/object:Gem::Version
56
- version: 1.5.2
57
- type: :development
58
- prerelease: false
59
- version_requirements: *id004
60
- - !ruby/object:Gem::Dependency
61
- name: rcov
62
- requirement: &id005 !ruby/object:Gem::Requirement
44
+ - !ruby/object:Gem::Version
45
+ version: 10.0.3
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
63
49
  none: false
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: "0"
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.12.0
68
54
  type: :development
69
55
  prerelease: false
70
- version_requirements: *id005
71
- 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.
72
- email: hpoydar@gmail.com
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.12.0
62
+ description: A simple Ruby natural language parser for elapsed time. (For example,
63
+ 4 hours and 30 minutes, 6 minutes 4 seconds, 3 days, etc.) Returns all results in
64
+ seconds. Will return an integer unless you get tricky and need a float. (4 minutes
65
+ and 13.47 seconds, for example.) The reverse can also be performed via the output
66
+ method.
67
+ email:
68
+ - henry@poydar.com
73
69
  executables: []
74
-
75
70
  extensions: []
76
-
77
- extra_rdoc_files:
78
- - README.rdoc
79
- files:
71
+ extra_rdoc_files: []
72
+ files:
73
+ - .gitignore
80
74
  - Gemfile
81
- - Gemfile.lock
82
75
  - MIT-LICENSE
83
- - README.rdoc
76
+ - README.md
84
77
  - Rakefile
85
- - VERSION
86
78
  - chronic_duration.gemspec
87
79
  - lib/chronic_duration.rb
80
+ - lib/chronic_duration/version.rb
88
81
  - spec/chronic_duration_spec.rb
89
- has_rdoc: true
90
- homepage: http://github.com/hpoydar/chronic_duration
91
- licenses:
92
- - MIT
82
+ - spec/lib/chronic_duration_spec.rb
83
+ - spec/spec_helper.rb
84
+ homepage: https://github.com/hpoydar/chronic_duration
85
+ licenses: []
93
86
  post_install_message:
94
87
  rdoc_options: []
95
-
96
- require_paths:
88
+ require_paths:
97
89
  - lib
98
- required_ruby_version: !ruby/object:Gem::Requirement
90
+ required_ruby_version: !ruby/object:Gem::Requirement
99
91
  none: false
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- hash: 2762452318022251891
104
- segments:
105
- - 0
106
- version: "0"
107
- required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
97
  none: false
109
- requirements:
110
- - - ">="
111
- - !ruby/object:Gem::Version
112
- version: "0"
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
113
102
  requirements: []
114
-
115
103
  rubyforge_project:
116
- rubygems_version: 1.5.2
104
+ rubygems_version: 1.8.23
117
105
  signing_key:
118
106
  specification_version: 3
119
- summary: A Ruby natural language parser for elapsed time
120
- test_files:
107
+ summary: A simple Ruby natural language parser for elapsed time
108
+ test_files:
121
109
  - spec/chronic_duration_spec.rb
110
+ - spec/lib/chronic_duration_spec.rb
111
+ - spec/spec_helper.rb
data/Gemfile.lock DELETED
@@ -1,30 +0,0 @@
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)
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.9.6