Convert duration to hours:minutes:seconds (or similar) in Rails 3 or Ruby

Ruby on-RailsRubyDatetimeRuby on-Rails-3

Ruby on-Rails Problem Overview


I have a feeling there is a simple/built-in way to do this but I can't find it.

I have a duration (in seconds) in an integer and I want to display it in a friendly format.

e.g. 3600 would be displayed as "01:00:00" or "1 hour" or something.

I can do it with time_ago_in_words(Time.zone.now+3600) but that feels like a bit of a hack, there is no reason to add/subtract from the current time just to format this value. Is there a duration_in_words() or something?

Thanks

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Summing up:

assuming that total_seconds = 3600

Option 1:

distance_of_time_in_words(total_seconds) #=> "about 1 hour"

Option 2:

Time.at(total_seconds).utc.strftime("%H:%M:%S") #=> "01:00:00"

Note: it overflows, eg. for total_seconds = 25.hours.to_i it'll return "01:00:00" also

Option 3:

seconds = total_seconds % 60
minutes = (total_seconds / 60) % 60
hours = total_seconds / (60 * 60)

format("%02d:%02d:%02d", hours, minutes, seconds) #=> "01:00:00"

Option 4:

ActiveSupport::Duration.build(total_seconds).inspect #=> "1 hour"

# OR

parts = ActiveSupport::Duration.build(total_seconds).parts
"%02d:%02d:%02d" % [parts.fetch(:hours, 0),
                    parts.fetch(:minutes, 0),
                    parts.fetch(:seconds, 0)] #=> "01:00:00"

Solution 2 - Ruby on-Rails

See: http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html

distance_of_time_in_words(3600)
 => "about 1 hour"

Solution 3 - Ruby on-Rails

Ruby's string % operator is too unappreciated and oft forgotten.

"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]

Given t is a duration in seconds, this emits a zero-padded colon-separated string including days. Example:

t = 123456
"%02d:%02d:%02d:%02d" % [t/86400, t/3600%24, t/60%60, t%60]
=> "01:10:17:36"

Lovely.

Solution 4 - Ruby on-Rails

I guess you could do also something like:

(Time.mktime(0)+3600).strftime("%H:%M:%S")

To format it as you wish.

BTW, originally I thought of using Time.at() but seems that EPOCH time on my Ubuntu is Thu Jan 01 01:00:00 +0100 1970 and not 00:00:00 hours as I expected, and therefore if I do:

Time.at(3600).strftime("%H:%M:%S")

Gives me 1 hour more than wanted.

Solution 5 - Ruby on-Rails

I use this to show time durations in my Rails Project:
  1. Add a custom method to the Integer class. You can create a new file called pretty_duration.rb in the initializers folder:

     class Integer
         def pretty_duration
             parse_string = 
                 if self < 3600
                     '%M:%S'
                 else
                     '%H:%M:%S'
                 end
    
             Time.at(self).utc.strftime(parse_string)
         end
     end
    
  2. Call seconds.pretty_duration anywhere in your project:

     275.pretty_duration     # => "04:35"
     9823.pretty_duration    # => "02:43:43"
    

This answer builds up on Lev Lukomsky's Code

Solution 6 - Ruby on-Rails

This one uses the obscure divmod method to divide and modulo at the same time, so it handles Float seconds properly:

def duration(seconds)
  minutes, seconds = seconds.divmod(60)
  hours, minutes = minutes.divmod(60)
  days, hours = hours.divmod(24)
  
  "#{days.to_s.rjust(3)}d #{hours.to_s.rjust(2)}h #{minutes.to_s.rjust(2)}m #{seconds}s"
end

Solution 7 - Ruby on-Rails

ActiveSupport::Duration.build + inspect gives you valid results

 >> ActiveSupport::Duration.build(125557).inspect
 => "1 day, 10 hours, 52 minutes, and 37 seconds"

Solution 8 - Ruby on-Rails

Using Time.utc.strftime works only for values when total number of hours is less then 24:

2.2.2 :004 > Time.at(60 * 60).utc.strftime('%H h %M m')
=> "01 h 00 m"

For greater values it returns incorrect results:

2.2.2 :006 > Time.at(60 * 60 * 24).utc.strftime('%H h %M m')
 => "00 h 00 m"

I suggest using the simplest method I found for this problem:

  def formatted_duration total_seconds
    hours = total_seconds / (60 * 60)
    minutes = (total_seconds / 60) % 60
    seconds = total_seconds % 60
    "#{ hours } h #{ minutes } m #{ seconds } s"
  end

You can always adjust returned value to your needs.

Solution 9 - Ruby on-Rails

Be careful with the duration longer than one day.

(timing/3600).to_i.to_s.rjust(2,'0') + ":"+Time.at(timing).utc.strftime("%M:%S")

Solution 10 - Ruby on-Rails

An answer inspired from Lev Lukomsky's one taking advantage of ActiveSupport::Duration, and handling milliseconds (useful to benchmark code)

# duration in ms modulus number of ms in one second
milliseconds = duration.in_milliseconds % 1.second.in_milliseconds

# duration in seconds modulus number of seconds in one minute
seconds = (duration / 1.second) % (1.minute / 1.second)

# duration in minutes modulus number of minutes in one hour
minutes = (duration / 1.minute) % (1.hour / 1.minute)

# duration in hours modulus number of hours in one day
hours = (duration / 1.hour) % (1.day / 1.hour)

format("%02d:%02d:%02d:%03d", hours, minutes, seconds, milliseconds) #=> "12:05:00:001"

Of course you can extend this easily with days, months, years, etc using related ActiveSupport methods and repeating the same structure.

Keep in mind that for too long durations, this may be inaccurate since the duration of 1 month is not fixed in number of days, and I'm not sure how AS:Duration deals with that.

Solution 11 - Ruby on-Rails

Shout out to @joshuapinter who gave the best answer (in the form of a comment).

Use the drop-in replacement dotiw gem to gain more control over the accuracy of the output to suit different needs:

https://github.com/radar/distance_of_time_in_words

Sample view code:

%label
  Logoff after:
  - expire_in = distance_of_time_in_words(Time.now, Time.now + user.custom_timeout.minutes, :only => [:minutes, :hours, :days])
  = expire_in

Resulting in something like this:

Logoff after: 1 day, 13 hours, and 20 minutes

Solution 12 - Ruby on-Rails

Just to throw in my 2 cents:

Time.at(i).utc.strftime((i < 3600) ? '%-M minutes and %-S seconds' : '%-H hours, %-M minutes, and %-S seconds')

Built off of Xiao Bin's answer.

Solution 13 - Ruby on-Rails

Here a simple solution using divmod and map:

    hours = 3.5456
    value = (hours*60).divmod(60).map{ |a| "%02d"%[a.floor] }.join(":")
    => "03:32"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioncydoniaView Question on Stackoverflow
Solution 1 - Ruby on-RailsLev LukomskyView Answer on Stackoverflow
Solution 2 - Ruby on-RailsallanView Answer on Stackoverflow
Solution 3 - Ruby on-RailsIAmNaNView Answer on Stackoverflow
Solution 4 - Ruby on-RailsCristobal ViedmaView Answer on Stackoverflow
Solution 5 - Ruby on-RailsSheharyarView Answer on Stackoverflow
Solution 6 - Ruby on-RailsBecca Royal-GordonView Answer on Stackoverflow
Solution 7 - Ruby on-RailsSathishView Answer on Stackoverflow
Solution 8 - Ruby on-RailsIgor SpringerView Answer on Stackoverflow
Solution 9 - Ruby on-RailsXiao BinView Answer on Stackoverflow
Solution 10 - Ruby on-RailsCyril Duchon-DorisView Answer on Stackoverflow
Solution 11 - Ruby on-RailsJon KernView Answer on Stackoverflow
Solution 12 - Ruby on-RailsDanielView Answer on Stackoverflow
Solution 13 - Ruby on-RailsLuciano RibasView Answer on Stackoverflow