Skip to content

Commit

Permalink
Adds :units option. Also:
Browse files Browse the repository at this point in the history
* Removes Jeweler, not needed (http://railscasts.com/episodes/245-new-gem-with-bundler)
* Remove duplicated piece of code
* Upgrades RSpec and Bundler
  • Loading branch information
mauriciozaffari committed Aug 14, 2012
1 parent 64d704f commit d6933fe
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 136 deletions.
6 changes: 3 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ source "http://rubygems.org"
gem "numerizer", "~> 0.1.1"

group :development do
gem "rspec", "~> 2.3.0"
gem "bundler", "~> 1.0.0"
gem "jeweler", "~> 1.5.2"
gem "rake"
gem "bundler"
gem "rspec", "~> 2.11.0"
gem "rcov", ">= 0"
end
31 changes: 13 additions & 18 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.2)
git (1.2.5)
jeweler (1.5.2)
bundler (~> 1.0.0)
git (>= 1.2.5)
rake
diff-lcs (1.1.3)
numerizer (0.1.1)
rake (0.8.7)
rake (0.9.2.2)
rcov (0.9.9)
rspec (2.3.0)
rspec-core (~> 2.3.0)
rspec-expectations (~> 2.3.0)
rspec-mocks (~> 2.3.0)
rspec-core (2.3.1)
rspec-expectations (2.3.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.3.0)
rspec (2.11.0)
rspec-core (~> 2.11.0)
rspec-expectations (~> 2.11.0)
rspec-mocks (~> 2.11.0)
rspec-core (2.11.1)
rspec-expectations (2.11.2)
diff-lcs (~> 1.1.3)
rspec-mocks (2.11.2)

PLATFORMS
ruby

DEPENDENCIES
bundler (~> 1.0.0)
jeweler (~> 1.5.2)
bundler
numerizer (~> 0.1.1)
rake
rcov
rspec (~> 2.3.0)
rspec (~> 2.11.0)
14 changes: 8 additions & 6 deletions README.rdoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
= Chronic Duration

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

Expand All @@ -23,17 +23,19 @@ The reverse can also be accomplished with the output method. So pass in seconds
=> 4 minutes 30 seconds
>> ChronicDuration.output(270, :format => :chrono)
=> 4:30
>> ChronicDuration.output(1296000, :weeks => true)
>> ChronicDuration.output(1299600, :weeks => true)
=> 2 wks 1 day 1 hr
>> ChronicDuration.output(1299600, :weeks => true, :units => 2)
=> 2 wks 1 day
>> ChronicDuration.output(1296000)
=> 15 days

Nil is returned if the string can't be parsed

Examples of parse-able strings:

* '12.4 secs'
* '1:20'
* '1:20'
* '1:20.51'
* '4:01:01'
* '3 mins 4 sec'
Expand All @@ -51,11 +53,11 @@ ChronicDuration.raise_exceptions can be set to true to raise exceptions when the
>> ChronicDuration.parse('4 elephants and 3 Astroids')
ChronicDuration::DurationParseError: An invalid word "elephants" was used in the string to be parsed.


== Contributors

brianjlandau, jduff, olauzon, roboman, ianlevesque

== TODO

* Benchmark, optimize
Expand Down
14 changes: 1 addition & 13 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@ rescue Bundler::BundlerError => e
end
require 'rake'

require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = "chronic_duration"
gem.summary = %Q{A Ruby natural language parser for elapsed time}
gem.license = "MIT"
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.}
gem.email = "[email protected]"
gem.homepage = "http://github.com/hpoydar/chronic_duration"
gem.authors = ["hpoydar"]
end
Jeweler::RubygemsDotOrgTasks.new

require 'rspec/core'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
Expand All @@ -34,7 +22,7 @@ end

task :default => :spec

require 'rake/rdoctask'
require 'rdoc/task'
Rake::RDocTask.new do |rdoc|
version = File.exist?('VERSION') ? File.read('VERSION') : ""

Expand Down
101 changes: 49 additions & 52 deletions lib/chronic_duration.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
require 'numerizer' unless defined?(Numerizer)
module ChronicDuration
extend self

class DurationParseError < StandardError
end

@@raise_exceptions = false

def self.raise_exceptions
!!@@raise_exceptions
end

def self.raise_exceptions=(value)
@@raise_exceptions = !!value
end

# Given a string representation of elapsed time,
# return an integer (or float, if fractions of a
# second are input)
def parse(string, opts = {})
result = calculate_from_words(cleanup(string), opts)
result == 0 ? nil : result
end
end

# Given an integer and an optional format,
# returns a formatted string representing elapsed time
def output(seconds, opts = {})

opts[:format] ||= :default

years = months = weeks = days = hours = minutes = 0

decimal_places = seconds.to_s.split('.').last.length if seconds.is_a?(Float)

if seconds >= 60
minutes = (seconds / 60).to_i
minutes = (seconds / 60).to_i
seconds = seconds % 60
if minutes >= 60
hours = (minutes / 60).to_i
Expand All @@ -49,44 +49,40 @@ def output(seconds, opts = {})
if weeks >= 4
months = (weeks / 4).to_i
weeks = (weeks % 4).to_i
if months >= 12
years = (months / 12).to_i
months = (months % 12).to_i
end
end
end
else
else
if days >= 30
months = (days / 30).to_i
days = (days % 30).to_i
if months >= 12
years = (months / 12).to_i
months = (months % 12).to_i
end
end
end
if months >= 12
years = (months / 12).to_i
months = (months % 12).to_i
end
end
end
end

joiner = ' '
process = nil

case opts[:format]
when :micro
dividers = {
dividers = {
:years => 'y', :months => 'm', :weeks => 'w', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
joiner = ''
when :short
dividers = {
dividers = {
:years => 'y', :months => 'm', :weeks => 'w', :days => 'd', :hours => 'h', :minutes => 'm', :seconds => 's' }
when :default
when :default
dividers = {
:years => ' yr', :months => ' mo', :weeks => ' wk', :days => ' day', :hours => ' hr', :minutes => ' min', :seconds => ' sec',
:pluralize => true }
when :long
when :long
dividers = {
:years => ' year', :months => ' month', :weeks => ' week', :days => ' day', :hours => ' hour', :minutes => ' minute', :seconds => ' second',
:years => ' year', :months => ' month', :weeks => ' week', :days => ' day', :hours => ' hour', :minutes => ' minute', :seconds => ' second',
:pluralize => true }
when :chrono
dividers = {
Expand All @@ -100,35 +96,36 @@ def output(seconds, opts = {})
end
joiner = ''
end

result = []
[:years, :months, :weeks, :days, :hours, :minutes, :seconds].each do |t|

result = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].map do |t|
next if t == :weeks && !opts[:weeks]
num = eval(t.to_s)
num = ("%.#{decimal_places}f" % num) if num.is_a?(Float) && t == :seconds
result << humanize_time_unit( num, dividers[t], dividers[:pluralize], dividers[:keep_zero] )
end
num = ("%.#{decimal_places}f" % num) if num.is_a?(Float) && t == :seconds
humanize_time_unit( num, dividers[t], dividers[:pluralize], dividers[:keep_zero] )
end.compact!

result = result[0...opts[:units]] if opts[:units]

result = result.join(joiner)

result = result.join(joiner).squeeze(' ').strip

if process
result = process.call(result)
end

result.length == 0 ? nil : result

end

private

def humanize_time_unit(number, unit, pluralize, keep_zero)
return '' if number == 0 && !keep_zero
return nil if number == 0 && !keep_zero
res = "#{number}#{unit}"
# A poor man's pluralizer
res << 's' if !(number == 1) && pluralize
res
end

def calculate_from_words(string, opts)
val = 0
words = string.split(' ')
Expand All @@ -139,18 +136,18 @@ def calculate_from_words(string, opts)
end
val
end

def cleanup(string)
res = string.downcase
res = filter_by_type(Numerizer.numerize(res))
res = res.gsub(float_matcher) {|n| " #{n} "}.squeeze(' ').strip
res = filter_through_white_list(res)
end

def convert_to_number(string)
string.to_f % 1 > 0 ? string.to_f : string.to_i
end

def duration_units_list
%w(seconds minutes hours days weeks months years)
end
Expand All @@ -166,11 +163,11 @@ def duration_units_seconds_multiplier(unit)
when 'seconds'; 1
end
end

def error_message
'Sorry, that duration could not be parsed'
end

# Parse 3:41:59 and return 3 hours 41 minutes 59 seconds
def filter_by_type(string)
if string.gsub(' ', '') =~ /#{float_matcher}(:#{float_matcher})+/
Expand All @@ -185,11 +182,11 @@ def filter_by_type(string)
end
res
end

def float_matcher
/[0-9]*\.?[0-9]+/
end

# Get rid of unknown words and map found
# words to defined time units
def filter_through_white_list(string)
Expand All @@ -208,9 +205,9 @@ def filter_through_white_list(string)
end
res.join(' ')
end

def mappings
{
{
'seconds' => 'seconds',
'second' => 'seconds',
'secs' => 'seconds',
Expand Down Expand Up @@ -242,13 +239,13 @@ def mappings
'y' => 'years'
}
end

def join_words
['and', 'with', 'plus']
end

def white_list
self.mappings.map {|k, v| k}
end

end
Loading

0 comments on commit d6933fe

Please sign in to comment.