Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for weeks #68

Merged
merged 1 commit into from
Mar 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

dotiw is a gem for Rails that overrides the default `distance_of_time_in_words` and provides a more accurate output. Do you crave accuracy down to the second? So do I. That's why I made this gem. Take this for a totally kickass example:

>> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, true)
=> "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"
>> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds, true)
=> "1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds"

Also if one of the measurement is zero it will not output it:

>> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 4.hours + 5.minutes + 6.seconds, true)
=> "1 year, 2 months, 4 hours, 5 minutes, and 6 seconds"
>> distance_of_time_in_words(Time.now, Time.now + 1.year + 2.months + 5.hours + 6.minutes + 7.seconds, true)
=> "1 year, 2 months, 4 days, 6 minutes, and 7 seconds"

Better than "about 1 year", am I right? Of course I am.

"But Ryan!", you say, "What happens if the time is only in seconds but because of the default the seconds aren't shown? Won't it be blank?"
Expand All @@ -21,7 +21,7 @@ Better than "about 1 year", am I right? Of course I am.
The third argument for this method is whether or not to include seconds. By default this is `false` (because in Rails' `distance_of_time_in_words` it is), you can turn it on though by passing `true` as the third argument:

>> distance_of_time_in_words(Time.now, Time.now + 1.year + 1.second, true)
=> "1 year, and 1 second"
=> "1 year, and 1 second"

Yes this could just be merged into the options hash but I'm leaving it here to ensure "backwards-compatibility",
because that's just an insanely radical thing to do. \m/
Expand Down Expand Up @@ -58,7 +58,7 @@ You can pass in a locale and it'll output it in whatever language you want (prov

>> distance_of_time_in_words(Time.now, Time.now + 1.minute, false, :locale => :es)
=> "1 minuto"

This will also be passed to `to_sentence`

#### :vague
Expand Down Expand Up @@ -95,7 +95,7 @@ Culling a whole group of measurements of time:

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.day + 1.minute, false, :except => [:minutes, :hours])
=> "1 day"

#### :highest\_measure\_only

For times when Rails `distance_of_time_in_words` is not precise enough and `DOTIW` is too precise. For instance, if you only want to know the highest time part (measure) that elapsed between two dates.
Expand Down Expand Up @@ -125,7 +125,7 @@ Using something other than a comma:

>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute + 1.second, true, :words_connector => ' - ')
=> "1 hour - 1 minute, and 1 second"

#### :two\_words\_connector

**This is an option for `to_sentence`, defaults to ' and '**
Expand All @@ -135,7 +135,7 @@ Using something other than 'and':
>> distance_of_time_in_words(Time.now, Time.now + 1.hour + 1.minute, true, :two_words_connector => ' plus ')
=> "1 hour plus 1 minute"

#### :last\_word\_connector
#### :last\_word\_connector

**This is an option for `to_sentence`, defaults to ', and '**

Expand All @@ -155,17 +155,17 @@ If you have simply a number of seconds you can get the "stringified" version of

Don't like any format you're given? That's cool too! Here, have an indifferent hash version:

>> distance_of_time_in_words_hash(Time.now, Time.now + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds)
=> {:days => 3, :seconds => 6, :minutes => 5, :years => 1, :hours => 4, :months => 2}
>> distance_of_time_in_words_hash(Time.now, Time.now + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds)
=> {:days => 4, :weeks => 3, :seconds => 7, :minutes => 6, :years => 1, :hours => 5, :months => 2}

Indifferent means that you can access all keys by their `String` or `Symbol` version.

## distance\_of\_time\_in\_percent

If you want to calculate a distance of time in percent, use `distance_of_time_in_percent`. The first argument is the beginning time, the second argument the "current" time and the third argument is the end time. This method takes the same options as [`number_with_precision`](http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_precision).

distance_of_time_in_percent("04-12-2009".to_time, "29-01-2010".to_time, "04-12-2010".to_time, options)


## Contributors

Expand Down
37 changes: 29 additions & 8 deletions lib/dotiw/time_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module DOTIW
class TimeHash
TIME_FRACTIONS = [:seconds, :minutes, :hours, :days, :months, :years]
TIME_FRACTIONS = [:seconds, :minutes, :hours, :days, :weeks, :months, :years]

attr_accessor :distance, :smallest, :largest, :from_time, :to_time

Expand Down Expand Up @@ -48,10 +48,12 @@ def build_time_hash
build_minutes
elsif distance < 1.day
build_hours
elsif distance < 28.days
elsif distance < 7.days
build_days
else # greater than a month
build_years_months_days
elsif distance < 28.days
build_weeks
else # greater than a week
build_years_months_weeks_days
end
end
end
Expand All @@ -76,28 +78,46 @@ def build_days
output[:days], self.distance = distance.divmod(1.day) if output[:days].nil?
end

def build_weeks
output[:weeks], self.distance = distance.divmod(1.week) if output[:weeks].nil?
end

def build_months
build_years_months_days
build_years_months_weeks_days

if (years = output.delete(:years)) > 0
output[:months] += (years * 12)
end
end

def build_years_months_days
def build_years_months_weeks_days
months = (largest.year - smallest.year) * 12 + (largest.month - smallest.month)
years, months = months.divmod(12)

days = largest.day - smallest.day

weeks, days = days.divmod(7)

# Will otherwise incorrectly say one more day if our range goes over a day.
days -= 1 if largest.hour < smallest.hour

if days < 0
# Convert the last month to days and add to total
# Convert a week to days and add to total
weeks -= 1
days += 7
end

if weeks < 0
# Convert the last month to a week and add to total
months -= 1
last_month = largest.advance(:months => -1)
days += Time.days_in_month(last_month.month, last_month.year)
days_in_month = Time.days_in_month(last_month.month, last_month.year)
weeks += days_in_month / 7
days += days_in_month % 7
if days >= 7
days -= 7
weeks += 1
end
end

if months < 0
Expand All @@ -108,6 +128,7 @@ def build_years_months_days

output[:years] = years
output[:months] = months
output[:weeks] = weeks
output[:days] = days

total_days, self.distance = distance.abs.divmod(1.day)
Expand Down
65 changes: 36 additions & 29 deletions spec/lib/dotiw_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
[10.minutes.to_i, "10 minutes"],
[1.hour.to_i, "1 hour"],
[1.hour + 30.seconds, "1 hour and 30 seconds"],
[4.weeks.to_i, "28 days"],
[24.weeks.to_i, "5 months and 15 days"]
[4.weeks.to_i, "4 weeks"],
[4.weeks + 2.days, "4 weeks and 2 days"],
[24.weeks.to_i, "5 months, 2 weeks, and 1 day"]
]
fragments.each do |number, result|
it "#{number} == #{result}" do
Expand All @@ -38,7 +39,7 @@
expect(distance_of_time(2.5.hours + 30.seconds, except: 'seconds')).to eq("2 hours and 30 minutes")
end

it "except:seconds har higher presedence than include_seconds:true" do
it "except:seconds has higher precedence than include_seconds:true" do
expect(distance_of_time(1.2.minute, include_seconds: true, except: 'seconds')).to eq('1 minute')
end
end
Expand All @@ -48,7 +49,7 @@
describe "hash version" do
describe "giving correct numbers of" do

[:years, :months, :days, :minutes, :seconds].each do |name|
[:years, :months, :weeks, :days, :minutes, :seconds].each do |name|
describe name do
it "exactly" do
hash = distance_of_time_in_words_hash(START_TIME, START_TIME + 1.send(name))
Expand All @@ -64,13 +65,14 @@

it "should be happy with lots of measurements" do
hash = distance_of_time_in_words_hash(START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds)
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds)
expect(hash[:years]).to eq(1)
expect(hash[:months]).to eq(2)
expect(hash[:days]).to eq(3)
expect(hash[:hours]).to eq(4)
expect(hash[:minutes]).to eq(5)
expect(hash[:seconds]).to eq(6)
expect(hash[:weeks]).to eq(3)
expect(hash[:days]).to eq(4)
expect(hash[:hours]).to eq(5)
expect(hash[:minutes]).to eq(6)
expect(hash[:seconds]).to eq(7)
end
end
end
Expand All @@ -91,14 +93,14 @@
[START_TIME, START_TIME + 1.minute, "1 minute"],
[START_TIME, START_TIME + 3.years, "3 years"],
[START_TIME, START_TIME + 10.years, "10 years"],
[START_TIME, START_TIME + 10.years, "10 years"],
[START_TIME, START_TIME + 8.months, "8 months"],
[START_TIME, START_TIME + 3.hour, "3 hours"],
[START_TIME, START_TIME + 13.months, "1 year and 1 month"],
# Any numeric sequence is merely coincidental.
[START_TIME, START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds, "1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"],
[START_TIME, START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds, "1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds"],
["2009-3-16".to_time, "2008-4-14".to_time, "11 months and 2 days"],
["2009-3-16".to_time + 1.minute, "2008-4-14".to_time, "11 months, 2 days, and 1 minute"],
["2009-4-14".to_time, "2008-3-16".to_time, "1 year and 29 days"],
["2009-4-14".to_time, "2008-3-16".to_time, "1 year, 4 weeks, and 1 day"],
["2009-2-01".to_time, "2009-3-01".to_time, "1 month"],
["2008-2-01".to_time, "2008-3-01".to_time, "1 month"]
]
Expand Down Expand Up @@ -126,10 +128,14 @@
START_TIME + 2.day + 10000.hour + 10.second,
:days,
"418 days, 16 hours, and 10 seconds"],
[START_TIME,
START_TIME + 2.day + 10000.hour + 10.second,
:weeks,
"59 weeks, 5 days, 16 hours, and 10 seconds"],
[START_TIME,
START_TIME + 2.day + 10000.hour + 10.second,
:months,
"13 months, 22 days, 16 hours, and 10 seconds"],
"13 months, 3 weeks, 1 day, 16 hours, and 10 seconds"],
["2015-1-15".to_time, "2016-3-15".to_time, :months, "14 months"]

]
Expand All @@ -148,8 +154,9 @@
[5.minutes.to_i, "5 minutes"],
[10.minutes.to_i, "10 minutes"],
[1.hour.to_i, "1 hour"],
[4.weeks.to_i, "28 days"],
[24.weeks.to_i, "5 months and 15 days"]
[6.days.to_i, "6 days"],
[4.weeks.to_i, "4 weeks"],
[24.weeks.to_i, "5 months, 2 weeks, and 1 day"]
]
fragments.each do |start, output|
it "should be #{output}" do
Expand All @@ -164,9 +171,9 @@
fragments = [
# Any numeric sequence is merely coincidental.
[START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
{ :words_connector => " - " },
"1 year - 2 months - 3 days - 4 hours - 5 minutes, and 6 seconds"],
"1 year - 2 months - 3 weeks - 4 days - 5 hours - 6 minutes, and 7 seconds"],
[START_TIME,
START_TIME + 5.minutes + 6.seconds,
{ :two_words_connector => " - " },
Expand All @@ -191,25 +198,25 @@
{ :only => ["minutes", "hours"]},
"1 hour and 1 minute"],
[START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
{ :vague => true },
"about 1 year"],
[START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
{ :vague => "Yes please" },
"about 1 year"],
[START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
{ :vague => false },
"1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"],
"1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds"],
[START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
{ :vague => nil },
"1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds"],
"1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, and 7 seconds"],
[START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
{ :except => "minutes" },
"1 year, 2 months, 3 days, 4 hours, and 6 seconds"],
"1 year, 2 months, 3 weeks, 4 days, 5 hours, and 7 seconds"],
[START_TIME,
START_TIME + 1.hour + 2.minutes + 3.seconds,
{ :highest_measure_only => true },
Expand All @@ -225,15 +232,15 @@
[START_TIME,
START_TIME + 2.year + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
{ :highest_measures => 2 },
"2 years and 25 days"],
"2 years and 3 weeks"],
[START_TIME,
START_TIME + 4.days + 6.minutes + 7.seconds,
{ :highest_measures => 3 },
"4 days, 6 minutes, and 7 seconds"],
[START_TIME,
START_TIME + 1.year + 2.weeks,
{ :highest_measures => 3 },
"1 year and 14 days"],
"1 year and 2 weeks"],
[START_TIME,
START_TIME + 1.days,
{ :only => [:years, :months] },
Expand All @@ -260,8 +267,8 @@

it "removes seconds in all other cases" do
expect(distance_of_time_in_words(START_TIME,
START_TIME + 1.year + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds,
false)).to eq("1 year, 2 months, 3 days, 4 hours, and 5 minutes")
START_TIME + 1.year + 2.months + 3.weeks + 4.days + 5.hours + 6.minutes + 7.seconds,
false)).to eq("1 year, 2 months, 3 weeks, 4 days, 5 hours, and 6 minutes")
end
end # include_seconds
end
Expand Down