-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
74decec
commit 3c51252
Showing
6 changed files
with
303 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module ChronicDuration | ||
VERSION = '0.10.0' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
|