Convert seconds to fractional hours (half hour being .50 and not 30) - ruby-on-rails

I have the following method to convert a duration in seconds to hours. For example, 2 hours and a half would result in 2.30 Also, 2 hours and fifteen minutes would result in 2.15. So it outputs number of hours and number of minutes.
I want to modify the method to display 2.5 or 2.25 instead of the above. I need to do it that way to make calculations. For example, if there is a $30/hr salary and employee worked 10 hours and a half, I need to multiply 30*10.5 and not 30*10.30.
def hour_quantity
unless self.duration.blank?
hours = (self.duration/60)/60
minutes = (self.duration/60) % 60
hours.to_s + '.' + minutes.to_s
end
end

Jon is correct, as usual. Just use floating point division:
def hour_quantity
duration / 3600.0
end
When displaying, you might want to round:
puts '%.2f' % hour_quantity
This will give you 2 decimal places, always (e.g. "2.00", "2.25", "2.50"). Alternatively:
puts '%g' % hour_quantity
puts '%g' % 2.0 #=> "2"
puts '%g' % 2.25 #=> "2.25"
puts '%g' % 2.4999999 #=> "2.5"

Since Rails 6.1.0 you can do:
1800.seconds.in_hours # => 0.5
15.minutes.in_hours # => 0.25
These are the methods that were newly introduced: in_seconds, in_minutes, in_hours, in_days, in_weeks, in_months, and in_years.

Related

Show duration (in minutes) to hours

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

How to just show minutes or hours or weeks in rails time ago?

I would like to get the following result based on created_at:
1-59M
1-24H
1-999+W
E.g, if a post is 5 minutes old it will say 5M. If it is 15 hours old it will say: 15H and lastly it will say 52W if it is 52 weeks old.
Bonus: how would I make it work with: https://github.com/basecamp/local_time
You just want it in weeks, hours or minutes? How about this (it would go in a helpers file)
def short_age_string(time)
diff = Time.now - time #value is seconds (float)
if diff >= 0
result = "1-"
else
result = "1+"
end
diff = diff.abs.to_i
if diff >= 604800 #seconds in a week
weeks = diff/604800
return "#{result}#{weeks}#{"+" if weeks >= 999}W"
elsif diff > 3600 #seconds in an hour
return "#{result}#{diff/3600}H"
else
return "#{diff/60}#{minutes}M"
end
end
I took the liberty of making it return "1+..." for times in the future.
I believe you'd use strftime to manage it with i18n
According to strftimer, you'd need to use %-dH, %-dM, %-dD, %-dW to get the format you desire:
#view
<%=l record.created_at, format: :small %>
#config/locales/en.yml
time:
small: %-dH
I've tried testing this & it will only bring back the initial number. More testing is needed, but should set you on the right track

grouping and displaying by dates

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

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

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"

How do I get the number of seconds between two DateTimes in Ruby on Rails

I've got code that does time tracking for employees. It creates a counter to show the employee how long they have been clocked in for.
This is the current code:
start_time = Time.parse(self.settings.first_clock_in)
total_seconds = Time.now - start_time
hours = (total_seconds/ 3600).to_i
minutes = ((total_seconds % 3600) / 60).to_i
seconds = ((total_seconds % 3600) % 60).to_i
This works fine. But because Time is limited to the range of 1970 - 2038 we are trying to replace all Time uses with DateTimes. I can't figure out how to get the number of seconds between two DateTimes. Subtracting them yields a Rational which I don't know how to interpret, whereas subtracting Times yields the difference in seconds.
NOTE: Since Ruby 1.9.2, the hard limit of Time is removed. However, Time is optimized for values between 1823-11-12 and 2116-02-20.
Subtracting two DateTimes returns the elapsed time in days, so you could just do:
elapsed_seconds = ((end_time - start_time) * 24 * 60 * 60).to_i
Or, more readably:
diff = datetime_1 - datetime_2
diff * 1.days # => difference in seconds; requires Ruby on Rails
Note, what you or some other searchers might really be looking for is this:
diff = datetime_1 - datetime_2
Date.day_fraction_to_time(diff) # => [h, m, s, frac_s]
You can convert them to floats with to_f, though this will incur the usual loss of precision associated with floats. If you're just casting to an integer for whole seconds it shouldn't be big enough to be a worry.
The results are in seconds:
>> end_time.to_f - start_time.to_f
=> 7.39954495429993
>> (end_time.to_f - start_time.to_f).to_i
=> 7
Otherwise, you could look at using to_formatted_s on the DateTime object and seeing if you can coax the output into something the Decimal class will accept, or just formatting it as plain Unix time as a string and calling to_i on that.
Others incorrectly rely on fractions or helper functions. It's much simpler than that. DateTime itself is integer underneath. Here's the Ruby way:
stop.to_i - start.to_i
Example:
start = Time.now
=> 2016-06-21 14:55:36 -0700
stop = start + 5.seconds
=> 2016-06-21 14:55:41 -0700
stop.to_i - start.to_i
=> 5
I am using ruby-2.1.4 and for me the following worked
Time.now - Time.new(2014,11,05,17,30,0)
gave me the time difference in seconds
reference: ruby doc
there's a method made for that:
Time.now.minus_with_coercion(10.seconds.ago)
equals 10.
Source: http://apidock.com/rails/Time/minus_with_coercion
Hope I helped.
Define a Ruby function like this,
def time_diff(start_time, end_time)
seconds_diff = (start_time - end_time).to_i.abs
days = seconds_diff / 86400
seconds_diff -= days * 86400
hours = seconds_diff / 3600
seconds_diff -= hours * 3600
minutes = seconds_diff / 60
seconds_diff -= minutes * 60
seconds = seconds_diff
"#{days} Days #{hours} Hrs #{minutes} Min #{seconds} Sec"
end
And Call this function,
time_diff(Time.now, Time.now-4.days-2.hours-1.minutes-53.seconds)

Resources