Skip to content

Commit

Permalink
Ignore the right files
Browse files Browse the repository at this point in the history
  • Loading branch information
henrypoydar committed Feb 23, 2013
1 parent 74decec commit 3c51252
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 4 deletions.
18 changes: 15 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
*.gem
*.swp
*.rbc
.bundle
doc/*
.rake_tasks~
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 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.)

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.

## Usage

>> require 'chronic_duration'
=> true
>> ChronicDuration.parse('4 minutes and 30 seconds')
=> 270
>> ChronicDuration.output(270)
=> 4 mins 30 secs
>> ChronicDuration.output(270, :format => :short)
=> 4m 30s
>> ChronicDuration.output(270, :format => :long)
=> 4 minutes 30 seconds
>> ChronicDuration.output(270, :format => :chrono)
=> 4:30
>> 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.51'
* '4:01:01'
* '3 mins 4 sec'
* '2 hrs 20 min'
* '2h20min'
* '6 mos 1 day'
* '47 yrs 6 mos and 4d'
* 'two hours and twenty minutes'
* '3 weeks and 2 days'

ChronicDuration.raise_exceptions can be set to true to raise exceptions when the string can't be parsed.

>> ChronicDuration.raise_exceptions = true
=> true
>> 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
* Context specific matching (E.g., for '4m30s', assume 'm' is minutes not months)
* Smartly parse vacation-like durations (E.g., '4 days and 3 nights')
* :chrono output option should probably change to something like 4 days 4:00:12 instead of 4:04:00:12
* Other locale support
1 change: 0 additions & 1 deletion VERSION

This file was deleted.

3 changes: 3 additions & 0 deletions lib/chronic_duration/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module ChronicDuration
VERSION = '0.10.0'
end
215 changes: 215 additions & 0 deletions spec/lib/chronic_duration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
require 'spec_helper'

describe ChronicDuration, '.parse' do

@exemplars = {
'1:20' => 60 + 20,
'1:20.51' => 60 + 20.51,
'4:01:01' => 4 * 3600 + 60 + 1,
'3 mins 4 sec' => 3 * 60 + 4,
'3 Mins 4 Sec' => 3 * 60 + 4,
'three mins four sec' => 3 * 60 + 4,
'2 hrs 20 min' => 2 * 3600 + 20 * 60,
'2h20min' => 2 * 3600 + 20 * 60,
'6 mos 1 day' => 6 * 30 * 24 * 3600 + 24 * 3600,
'1 year 6 mos 1 day' => 1 * 31536000 + 6 * 30 * 24 * 3600 + 24 * 3600,
'2.5 hrs' => 2.5 * 3600,
'47 yrs 6 mos and 4.5d' => 47 * 31536000 + 6 * 30 * 24 * 3600 + 4.5 * 24 * 3600,
'two hours and twenty minutes' => 2 * 3600 + 20 * 60,
'four hours and forty minutes' => 4 * 3600 + 40 * 60,
'four hours, and fourty minutes' => 4 * 3600 + 40 * 60,
'3 weeks and, 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
'3 weeks, plus 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
'3 weeks with 2 days' => 3600 * 24 * 7 * 3 + 3600 * 24 * 2,
'1 month' => 3600 * 24 * 30,
'2 months' => 3600 * 24 * 30 * 2
}

it "should return nil if the string can't be parsed" do
ChronicDuration.parse('gobblygoo').should be_nil
end

it "should raise an exception if the string can't be parsed and @@raise_exceptions is set to true" do
ChronicDuration.raise_exceptions = true
lambda { ChronicDuration.parse('23 gobblygoos') }.should raise_exception(ChronicDuration::DurationParseError)
ChronicDuration.raise_exceptions = false
end

it "should return a float if seconds are in decimals" do
ChronicDuration.parse('12 mins 3.141 seconds').is_a?(Float).should be_true
end

it "should return an integer unless the seconds are in decimals" do
ChronicDuration.parse('12 mins 3 seconds').is_a?(Integer).should be_true
end

it "should be able to parse minutes by default" do
ChronicDuration.parse('5', :default_unit => "minutes").should == 300
end

@exemplars.each do |k, v|
it "should properly parse a duration like #{k}" do
ChronicDuration.parse(k).should == v
end
end

end

describe ChronicDuration, '.output' do

it "should return nil if the input can't be parsed" do
ChronicDuration.parse('gobblygoo').should be_nil
end

@exemplars = {
#(0) =>
#{
#:micro => '0s',
#:short => '0s',
#:default => '0 secs',
#:long => '0 seconds',
#:chrono => '0'
#},
(60 + 20) =>
{
:micro => '1m20s',
:short => '1m 20s',
:default => '1 min 20 secs',
:long => '1 minute 20 seconds',
:chrono => '1:20'
},
(60 + 20.51) =>
{
:micro => '1m20.51s',
:short => '1m 20.51s',
:default => '1 min 20.51 secs',
:long => '1 minute 20.51 seconds',
:chrono => '1:20.51'
},
(60 + 20.51928) =>
{
:micro => '1m20.51928s',
:short => '1m 20.51928s',
:default => '1 min 20.51928 secs',
:long => '1 minute 20.51928 seconds',
:chrono => '1:20.51928'
},
(4 * 3600 + 60 + 1) =>
{
:micro => '4h1m1s',
:short => '4h 1m 1s',
:default => '4 hrs 1 min 1 sec',
:long => '4 hours 1 minute 1 second',
:chrono => '4:01:01'
},
(2 * 3600 + 20 * 60) =>
{
:micro => '2h20m',
:short => '2h 20m',
:default => '2 hrs 20 mins',
:long => '2 hours 20 minutes',
:chrono => '2:20'
},
(2 * 3600 + 20 * 60) =>
{
:micro => '2h20m',
:short => '2h 20m',
:default => '2 hrs 20 mins',
:long => '2 hours 20 minutes',
:chrono => '2:20:00'
},
(6 * 30 * 24 * 3600 + 24 * 3600) =>
{
:micro => '6mo1d',
:short => '6mo 1d',
:default => '6 mos 1 day',
:long => '6 months 1 day',
:chrono => '6:01:00:00:00' # Yuck. FIXME
},
(365 * 24 * 3600 + 24 * 3600 ) =>
{
:micro => '1y1d',
:short => '1y 1d',
:default => '1 yr 1 day',
:long => '1 year 1 day',
:chrono => '1:00:01:00:00:00'
},
(3 * 365 * 24 * 3600 + 24 * 3600 ) =>
{
:micro => '3y1d',
:short => '3y 1d',
:default => '3 yrs 1 day',
:long => '3 years 1 day',
:chrono => '3:00:01:00:00:00'
},
}

@exemplars.each do |k, v|
v.each do |key, val|
it "should properly output a duration of #{k} seconds as #{val} using the #{key.to_s} format option" do
ChronicDuration.output(k, :format => key).should == val
end
end
end

it "should show weeks when needed" do
ChronicDuration.output(15*24*60*60, :weeks => true).should =~ /.*wk.*/
end

it "should show the specified number of units if provided" do
ChronicDuration.output(4 * 3600 + 60 + 1, units: 2).should == '4 hrs 1 min'
ChronicDuration.output(6 * 30 * 24 * 3600 + 24 * 3600 + 3600 + 60 + 1, units: 3, format: :long).should == '6 months 1 day 1 hour'
end

it "should use the default format when the format is not specified" do
ChronicDuration.output(2 * 3600 + 20 * 60).should == '2 hrs 20 mins'
end

@exemplars.each do |seconds,format_spec|
format_spec.each do |format,_|
it "it should properly output a duration for #{seconds} that parses back to the same thing when using the #{format.to_s} format" do
ChronicDuration.parse(ChronicDuration.output(seconds, :format => format)).should == seconds
end
end
end
end


# Some of the private methods deserve some spec'ing to aid
# us in development...

describe ChronicDuration, "private methods" do

describe ".filter_by_type" do

it "should take a chrono-formatted time like 3:14 and return a human time like 3 minutes 14 seconds" do
ChronicDuration.instance_eval("filter_by_type('3:14')").should == '3 minutes 14 seconds'
end

it "should take a chrono-formatted time like 12:10:14 and return a human time like 12 hours 10 minutes 14 seconds" do
ChronicDuration.instance_eval("filter_by_type('12:10:14')").should == '12 hours 10 minutes 14 seconds'
end

it "should return the input if it's not a chrono-formatted time" do
ChronicDuration.instance_eval("filter_by_type('4 hours')").should == '4 hours'
end

end

describe ".cleanup" do

it "should clean up extraneous words" do
ChronicDuration.instance_eval("cleanup('4 days and 11 hours')").should == '4 days 11 hours'
end

it "should cleanup extraneous spaces" do
ChronicDuration.instance_eval("cleanup(' 4 days and 11 hours')").should == '4 days 11 hours'
end

it "should insert spaces where there aren't any" do
ChronicDuration.instance_eval("cleanup('4m11.5s')").should == '4 minutes 11.5 seconds'
end

end

end
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require 'rubygems'
require 'bundler/setup'
require 'chronic_duration'

RSpec.configure do |config|
end


0 comments on commit 3c51252

Please sign in to comment.