I want to display the duration of a movie. Right now the movie.duration is shown in minutes (integer)
%b Duration: #{ #movie.duration } # 134 mins
Does rails have a time helper to show this is in a more human-readable way? Something like this:
Duration: 2h 23min
distance_of_time_in_words might help you.
You can use it like this:
distance_of_time_in_words(0, 143.minutes)
# => "about 2 hours"
To use an integer / float you'd need to convert to seconds manually:
distance_of_time_in_words(0, 143 * 60)
You could also calculate it like this:
"#{#movie.duration/60}h #{#movie.duration % 60}min"
The division will give you the hours, while the modulo will give you the minutes.
Finally, for the format specified in your question, there's a Gist you can use for the code here.
Try Following
def formatted_duration(total_minute)
hours = total_minute / 60
minutes = (total_minute) % 60
"#{ hours }h #{ minutes }min"
end
Looking at "How to recover an integer from an ActiveSupport::Duration object", I can see it should be possible, but for:
2.hours.parts
I get:
[[:seconds, 7200]]
How can this be done better so that I have:
[[:hours, 2]]
That may not be possible.
If you look at implementation of core-extensions in Active Support, time.rb, parts is set to :seconds for Duration instances constructed using Numeric#hours or Numeric#minutes or Numeric#seconds.
p 1.hour # 3600 seconds
p 2.minutes # 120 seconds
p 30.seconds # 30 seconds
Hence, you will always see something like below for all duration which are in hours, minutes or seconds, if you inspect the Duration#parts attribute
[[:seconds, 7200]]
In my Ruby on Rails application I have a constant like this:
TIME = 3.days
Is there a way to output this value in hours?
# => 72
Thanks for any help.
Ruby or Rails has no direct method for this but if you google you can find a lot of way to do this.you can try followings:
3.days/1.hour
also take a look at Is there any method in Rails to convert minutes as integer to days, months, etc
> 3.days / 3600
=> 72
Not very intuitive, I know.
TIME = 3.days
=> 259200
TIME/3600
=> 72
We have to covert it in hours (60 * 60 = 3600) :P
in_hours (Rails 6.1+)
Rails 6.1 introduces new ActiveSupport::Duration conversion methods like in_seconds, in_minutes, in_hours, in_days, in_weeks, in_months, and in_years.
As a result, now, your problem can be solved as simple as:
3.days.in_hours
# => 72.0
Here is a link to the corresponding PR.
I have a rails application and I want to display messages (Message) to the user based on the time the messages have arrived
every message (Message) has a property called created_at
for example
-- 20 minutes ago
-----message1
-----message2
-- 1 hour ago
-----message3
-----message4
-----message5
-- 2 days ago
-----message6
and so on and so forth.
how can I do this using rails?
I am using rails 3 b.t.w
You basically have two options. The first is to use the built-in Rails helper time_ago_in_words:
# in your views
<%= time_ago_in_words(message.created_at) %>
You can read more about this helper in the Rails documentation.
The downside with this is that it only works in views, and it might not be the entirely correct format. If that's the case, you can always define your own helper in an initializer. Here's a method I coded up for an old app, which you should be able to modify to suit your requirements:
class ActiveSupport::TimeWithZone
def how_long_ago
seconds = (Time.now - self)
# Keep adding days, weeks, months, years if necessary--same principle should apply
if seconds > 3600
(seconds / 3600).to_i.to_s + "h " + (seconds % 3600 / 60).to_i.to_s + "m"
elsif seconds > 60
(seconds / 60).to_i.to_s + "m " + (seconds % 60).to_i.to_s + "s"
else
seconds.to_i.to_s + "s"
end
end
end
Message.first.created_at.how_long_ago # => 3m 52s
To group the data based on this, you can use the group_by method on the messages array.
You can set default scope in your Message model:
default_scope order('messages.created_at DESC')
I found an answer on RailsCasts which is perfect for my needs
http://railscasts.com/episodes/29-group-by-month
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
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"
See: http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html
distance_of_time_in_words(3600)
=> "about 1 hour"
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.
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.
I use this to show time durations in my Rails Project:
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
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
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
ActiveSupport::Duration.build + inspect gives you valid results
>> ActiveSupport::Duration.build(125557).inspect
=> "1 day, 10 hours, 52 minutes, and 37 seconds"
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.
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")
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.
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
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.
Here a simple solution using divmod and map:
hours = 3.5456
value = (hours*60).divmod(60).map{ |a| "%02d"%[a.floor] }.join(":")
=> "03:32"