Ruby/Rails - How to convert seconds to time? - ruby-on-rails

I need to perform the following conversion:
0 -> 12.00AM
1800 -> 12.30AM
3600 -> 01.00AM
...
82800 -> 11.00PM
84600 -> 11.30PM
I came up with this:
(0..84600).step(1800){|n| puts "#{n.to_s} #{Time.at(n).strftime("%I:%M%p")}"}
which gives me the wrong time, because Time.at(n) expects n to be number of seconds from epoch:
0 -> 07:00PM
1800 -> 07:30PM
3600 -> 08:00PM
...
82800 -> 06:00PM
84600 -> 06:30PM
What would be the most optimal, time zone independent solution for this transformation?

The simplest one-liner simply ignores the date:
Time.at(82800).utc.strftime("%I:%M%p")
#-> "11:00PM"

Not sure if this is better than
(Time.local(1,1,1) + 82800).strftime("%I:%M%p")
def hour_minutes(seconds)
Time.at(seconds).utc.strftime("%I:%M%p")
end
irb(main):022:0> [0, 1800, 3600, 82800, 84600].each { |s| puts "#{s} -> #{hour_minutes(s)}"}
0 -> 12:00AM
1800 -> 12:30AM
3600 -> 01:00AM
82800 -> 11:00PM
84600 -> 11:30PM
Stephan

Two offers:
The elaborate DIY solution:
def toClock(secs)
h = secs / 3600; # hours
m = secs % 3600 / 60; # minutes
if h < 12 # before noon
ampm = "AM"
if h = 0
h = 12
end
else # (after) noon
ampm = "PM"
if h > 12
h -= 12
end
end
ampm = h <= 12 ? "AM" : "PM";
return "#{h}:#{m}#{ampm}"
end
the Time solution:
def toClock(secs)
t = Time.gm(2000,1,1) + secs # date doesn't matter but has to be valid
return "#{t.strftime("%I:%M%p")} # copy of your desired format
end
HTH

In other solutions, the hour-counter would be reset to 00 when crossing 24-hour day boundaries. Also beware that Time.at rounds down, so it will give the wrong result if the input has any fractional seconds (f.ex. when t=479.9 then Time.at(t).utc.strftime("%H:%M:%S") will give 00:07:59 and not 00:08:00` which is the correct one).
If you want a way to convert any number of seconds (even high counts larger than 24-hour day spans) into an ever increasing HH:MM:SS counter, and handle potential fractional seconds, then try this:
# Will take as input a time in seconds (which is typically a result after subtracting two Time objects),
# and return the result in HH:MM:SS, even if it exceeds a 24 hour period.
def formatted_duration(total_seconds)
total_seconds = total_seconds.round # to avoid fractional seconds potentially compounding and messing up seconds, minutes and hours
hours = total_seconds / (60*60)
minutes = (total_seconds / 60) % 60 # the modulo operator (%) gives the remainder when leftside is divided by rightside. Ex: 121 % 60 = 1
seconds = total_seconds % 60
[hours, minutes, seconds].map do |t|
# Right justify and pad with 0 until length is 2.
# So if the duration of any of the time components is 0, then it will display as 00
t.round.to_s.rjust(2,'0')
end.join(':')
end
Modified from #springerigor's and suggestion in the discussion at https://gist.github.com/shunchu/3175001

Related

math operations with hours,minutes and seconds

I work with decimal times in Lua and make arithmetical operations on them.
For example 124500+5=124505 (12:45:05)
What formula can avoid 60 digits problem?
124459+5=124504 (not 124464)
How can I resolve it?
You are mixing formation with calculation. The best way is to transform your time "string" in a real number:
12:45:05 -> 12 * 60 * 60 + 45 * 60 + 05 = 45905
The function could look like this:
function time_to_number(t)
return (math.floor(t / 10000) * 60 * 60) + ((math.floor(t / 100) % 100) * 60) + (t % 100)
-- you can also use % 10000 if the hours are limited to two digits
end
Now you can calculate on the seconds.
To format the value back you can use this function
function time_split(t)
local hour = math.floor(t / 3600)
local min = math.floor((t % 3600) / 60)
local sec = (t % 3600) % 60
return hour, min, sec
end
I have used many brackets for readability, which are not all required.

Ruby Program time conversion

The task is to Write a method that will take in a number of minutes, and returns a string that formats the number into hours:minutes.
here's what I have so far:
def time_conversion(minutes)
minutes = (minutes / 60) % 60
hours = minutes / (60 * 60)
format(" %02d:%02d ", hours, minutes)
return format
end
it's not working out for me
Try this
def time_conversion(time)
minutes = time % 60
hours = time / 60
minutes = (minutes < 10)? "0" + minutes.to_s : minutes.to_s
return hours.to_s + ":" + minutes
end
Using division in Ruby returns a whole number, lowered to the previous number. Using modulus returns the remainder after division.
Ruby's Numeric#divmod is exactly what you want here. It returns both the quotient and remainder of a division operation, so e.g. 66.divmod(60) returns [ 1, 6 ]. Combined with sprintf (or String#%, it makes for an extremely simple solution:
def time_conversion(minutes)
"%02d:%02d" % minutes.divmod(60)
end
puts time_conversion(192)
# => 03:12
Well try
h = minutes/60
M = minutes%60

change the TimeStamp return into mm:sec not hour

I have this:
<(Time.now).to_i>
it returns a integer thing
and want to convert that integer value into (mins and seconds)
only as per my requirement
Try this
Time.now.strftime("%M:%S")
Hope you are trying to find this
You can get the minutes and seconds by this
delta = Time.now.to_i
%w[ weeks days hours minutes seconds].collect do |step|
seconds = 1.send(step)
(delta / seconds).to_i.tap do
delta %= seconds
end
end
It will return an array with having 5 elements . You can get the mins and secs from
arr[3] and arr[4]
Also you can take the whatever the time in integer format as delta .
If you want to show only mins and secs when hr = 0
#hr = timedef[0]
#mn = timedef[1]
#sec = timedef[2]
time_remains = ''
unless self.timedef.blank?
if #hr > 0
time_remains = time_remains + "#{#hr} #{'hour'.pluralize(#hr)} "
elsif #mn > 0
time_remains = time_remains + "#{#mn} #{'minute'.pluralize(#mn)} and #{#sec} #{'second'.pluralize(#sec)} "
else
time_remains = time_remains + "#{#sec} #{'second'.pluralize(#sec)} "
end
end
use strftime -- see this page http://www.dzone.com/snippets/date-time-format-ruby it has the formats (though its similar to c/c++ and Java)
Also, you will likely want to do some research into timezone handling on the time class. there is a function: in_time_zone that can convert the timezone for you. So typically you store the times or the times fetched are in UTC then you can dynamically change the timezone before you strftime.

Ruby average value of array of time values (fixnums)

Looking to get the average duration, where duration is in the form of 1.day, 3.months, 2.weeks format..
# provided array
a = [1.day, 3.days, 1.week, 4.days]
# desired output
a.average = "3 days"
Any way I have tried results in a number of seconds being the output.. for instance:
a.inject(:+) = "15 days"
a.inject(:+) / a.size = 324000
I've looked at the Linguistics gem, but it only outputs the value as a number (three hundred and twenty four thousand)
def average_days(a)
seconds = a.inject(:+) / a.size
minutes = seconds / 60
days = (minutes / 1440).round
"#{days} days"
end
> a = [1.day, 3.days, 1.week, 4.days]
> (a.inject(0.0) {|sum, n| sum + n} / a.size) / (60 * 60 * 24)
=> 3.75
If you insist. Round and/or truncate however you want.
((a.inject(0.0) {|sum, n| sum + n} / a.size) / (60 * 60 * 24)).days

How to generate a human readable time range using ruby on rails

I'm trying to find the best way to generate the following output
<name> job took 30 seconds
<name> job took 1 minute and 20 seconds
<name> job took 30 minutes and 1 second
<name> job took 3 hours and 2 minutes
I started this code
def time_range_details
time = (self.created_at..self.updated_at).count
sync_time = case time
when 0..60 then "#{time} secs"
else "#{time/60} minunte(s) and #{time-min*60} seconds"
end
end
Is there a more efficient way of doing this. It seems like a lot of redundant code for something super simple.
Another use for this is:
<title> was posted 20 seconds ago
<title> was posted 2 hours ago
The code for this is similar, but instead i use Time.now:
def time_since_posted
time = (self.created_at..Time.now).count
...
...
end
If you need something more "precise" than distance_of_time_in_words, you can write something along these lines:
def humanize(secs)
[[60, :seconds], [60, :minutes], [24, :hours], [Float::INFINITY, :days]].map{ |count, name|
if secs > 0
secs, n = secs.divmod(count)
"#{n.to_i} #{name}" unless n.to_i==0
end
}.compact.reverse.join(' ')
end
p humanize 1234
#=>"20 minutes 34 seconds"
p humanize 12345
#=>"3 hours 25 minutes 45 seconds"
p humanize 123456
#=>"1 days 10 hours 17 minutes 36 seconds"
p humanize(Time.now - Time.local(2010,11,5))
#=>"4 days 18 hours 24 minutes 7 seconds"
Oh, one remark on your code:
(self.created_at..self.updated_at).count
is really bad way to get the difference. Use simply:
self.updated_at - self.created_at
There are two methods in DateHelper that might give you what you want:
time_ago_in_words
time_ago_in_words( 1234.seconds.from_now ) #=> "21 minutes"
time_ago_in_words( 12345.seconds.ago ) #=> "about 3 hours"
distance_of_time_in_words
distance_of_time_in_words( Time.now, 1234.seconds.from_now ) #=> "21 minutes"
distance_of_time_in_words( Time.now, 12345.seconds.ago ) #=> "about 3 hours"
chronic_duration parses numeric time to readable and vice versa
If you want to show significant durations in the seconds to days range, an alternative would be (as it doesn't have to perform the best):
def human_duration(secs, significant_only = true)
n = secs.round
parts = [60, 60, 24, 0].map{|d| next n if d.zero?; n, r = n.divmod d; r}.
reverse.zip(%w(d h m s)).drop_while{|n, u| n.zero? }
if significant_only
parts = parts[0..1] # no rounding, sorry
parts << '0' if parts.empty?
end
parts.flatten.join
end
start = Time.now
# perform job
puts "Elapsed time: #{human_duration(Time.now - start)}"
human_duration(0.3) == '0'
human_duration(0.5) == '1s'
human_duration(60) == '1m0s'
human_duration(4200) == '1h10m'
human_duration(3600*24) == '1d0h'
human_duration(3600*24 + 3*60*60) == '1d3h'
human_duration(3600*24 + 3*60*60 + 59*60) == '1d3h' # simple code, doesn't round
human_duration(3600*24 + 3*60*60 + 59*60, false) == '1d3h59m0s'
Alternatively you may be only interested in stripping the seconds part when it doesn't matter (also demonstrating another approach):
def human_duration(duration_in_seconds)
n = duration_in_seconds.round
parts = []
[60, 60, 24].each{|d| n, r = n.divmod d; parts << r; break if n.zero?}
parts << n unless n.zero?
pairs = parts.reverse.zip(%w(d h m s)[-parts.size..-1])
pairs.pop if pairs.size > 2 # do not report seconds when irrelevant
pairs.flatten.join
end
Hope that helps.
There is problem with distance_of_time_in_words if u ll pass there 1 hour 30 min it ll return about 2 hours
Simply add in helper:
PERIODS = {
'day' => 86400,
'hour' => 3600,
'minute' => 60
}
def formatted_time(total)
return 'now' if total.zero?
PERIODS.map do |name, span|
next if span > total
amount, total = total.divmod(span)
pluralize(amount, name)
end.compact.to_sentence
end
Basically just pass your data in seconds.
Rails has a DateHelper for views. If that is not exactly what you want, you may have to write your own.
#Mladen Jablanović has an answer with good sample code. However, if you don't mind continuing to customize a sample humanize method, this might be a good starting point.
def humanized_array_secs(sec)
[[60, 'minutes '], [60, 'hours '], [24, 'days ']].inject([[sec, 'seconds']]) do |ary, (count, next_name)|
div, prev_name = ary.pop
quot, remain = div.divmod(count)
ary.push([remain, prev_name])
ary.push([quot, next_name])
ary
end.reverse
end
This gives you an array of values and unit names that you can manipulate.
If the first element is non-zero, it is the number of days. You may want to write code to handle multiple days, like showing weeks, months, and years. Otherwise, trim off the leading 0 values, and take the next two.
def humanized_secs(sec)
return 'now' if 1 > sec
humanized_array = humanized_array_secs(sec.to_i)
days = humanized_array[-1][0]
case
when 366 <= days
"#{days / 365} years"
when 31 <= days
"#{days / 31} months"
when 7 <= days
"#{days / 7} weeks"
else
while humanized_array.any? && (0 == humanized_array[-1][0])
humanized_array.pop
end
humanized_array.reverse[0..1].flatten.join
end
end
The code even finds use for a ruby while statement.

Resources