samayres1992
samayres1992

Reputation: 779

distance_of_time_in_words, smarter formatting?

Currently I'm using the following:

distance_of_time_in_words(tweet.created_at, Time.now + 30.minutes, include_seconds: false)

This works fine in most scenarios, but I'd like certain exceptions to be formatted differently, and the documentation is rather confusing for this scenario. (I also couldn't find any questions on here about it)

As an example, currently the function converts the time to this before 1 day has passed.

5 hours and 17 minutes ago

I'm using it for tweets posted and times such as

Tweeted 4 days, 8 hours, and 3 minutes 

Seem a little long winded and unnecessarily specific.

Ideally I'd be like to be able to display:

less than a minute = the number of seconds ago

less than 1 hour = the amount of minutes

less than a day = the amount of hours

more than a day = the number of days

Is there a way of doing this?

Upvotes: 1

Views: 1273

Answers (3)

Craig Sniffen
Craig Sniffen

Reputation: 46

The output of distance_of_time_in_words (which is just an alias for time_ago_in_words) should look like this:

0 <-> 29 secs                                                             # => less than a minute
30 secs <-> 1 min, 29 secs                                                # => 1 minute
1 min, 30 secs <-> 44 mins, 29 secs                                       # => [2..44] minutes
44 mins, 30 secs <-> 89 mins, 29 secs                                     # => about 1 hour
89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs                             # => about [2..24] hours
23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs                     # => 1 day
41 hrs, 59 mins, 30 secs  <-> 29 days, 23 hrs, 59 mins, 29 secs           # => [2..29] days
29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs   # => about 1 month
44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs   # => about 2 months
59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec                    # => [2..12] months
1 yr <-> 1 yr, 3 months                                                   # => about 1 year
1 yr, 3 months <-> 1 yr, 9 months                                         # => over 1 year
1 yr, 9 months <-> 2 yr minus 1 sec                                       # => almost 2 years
2 yrs <-> max time or date                                                # => (same rules as 1 yr)

http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words

That looks like what you want. Something must be overriding distance_of_time_in_words? I'm not sure why anything would do that, but that seems to be the case.

Upvotes: 1

Cary Swoveland
Cary Swoveland

Reputation: 110685

Here's a non-Rails solution, though it appears you are better off using Rails' distance_of_time_in_words helper. Looking through the documentation, the latter seems to offer tremendous flexibility.

require 'time'

SECS_PER_MIN  =  60
SECS_PER_HOUR =  60 * SECS_PER_MIN
SECS_PER_DAY  =  24 * SECS_PER_HOUR
SECS_PER_WEEK =   7 * SECS_PER_DAY
SECS_PER_YEAR = 365 * SECS_PER_DAY
SECS_PER_CENT = 100 * SECS_PER_YEAR
SECS_PER_MLNM =  10 * SECS_PER_CENT
SECS_PER_EON  =  1_000_000 * SECS_PER_MLNM

def message(secs)
  case secs
  when (0...SECS_PER_MIN)
    "#{secs} seconds"
  when (SECS_PER_MIN...SECS_PER_HOUR)
    "#{secs/SECS_PER_MIN} minutes"
  when (SECS_PER_HOUR...SECS_PER_DAY)
    "#{secs/SECS_PER_HOUR} hours"
  when (SECS_PER_DAY...SECS_PER_WEEK)
    "#{secs/SECS_PER_DAY} days"
  when (SECS_PER_WEEK...SECS_PER_YEAR)
    "#{secs/SECS_PER_WEEK} weeks"
  when (SECS_PER_YEAR...SECS_PER_CENT)
      "#{secs/SECS_PER_YEAR} years"
  when (SECS_PER_CENT...SECS_PER_MLNM)
    "#{secs/SECS_PER_CENT} centuries"
  when (SECS_PER_MLNM...SECS_PER_EON)
    "#{secs/SECS_PER_MLNM} millenia"
  else
    "#{secs/SECS_PER_EON} eons"
  end << " ago"
end

You would call message with something like:

message(Time.now-tweet_time)

Let's try it.

message(10)                      #=> "10 seconds ago"
message(1_000)                   #=> "16 minutes ago"
message(20_000)                  #=> "5 hours ago"
message(300_000)                 #=> "3 days ago"
message(10_000_000)              #=> "16 weeks ago"
message(1_000_000_000)           #=> "31 years ago"
message(25_000_000_000)          #=> "7 centuries ago"
message(1_300_000_000_000)       #=> "41 millenia ago"
message(100_000_000_000_000_000) #=> "3 eons ago"

You could prettify it by changing, for example,

when (SECS_PER_DAY...SECS_PER_WEEK)
  "#{secs/SECS_PER_DAY} days"

to

when (SECS_PER_DAY...SECS_PER_WEEK)
  n = secs/SECS_PER_DAY
  n == 1 ? "1 day" : "#{n} days"

Upvotes: 1

spickermann
spickermann

Reputation: 106932

Perhaps you want to use the distance_of_time_in_words helper:

0 <-> 29 secs                                  # => less than a minute
30 secs <-> 1 min, 29 secs                     # => 1 minute
1 min, 30 secs <-> 44 mins, 29 secs            # => [2..44] minutes
44 mins, 30 secs <-> 89 mins, 29 secs          # => about 1 hour
89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs  # => about [2..24] hours
and so on...

You can use that helper with just one argument. Something like this in your view should do the job:

 Tweeted <%= time_ago_in_words(tweet.created_at) ago

Assuming tweet.created_at is about 29 hours ago it would render to:

Tweeted 1 day ago

see: http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words

Upvotes: 1

Related Questions