How do I convert this string to milliseconds? - ruby-on-rails

I’m using Rails 4.2.7. I want to calculate the number of milliseconds given a duration, which could include hours, minutes, and seconds. So I wrote these two functions:
def convert_to_hrs(string)
if !string.nil?
string.strip!
case string.count(':')
when 0
'00:00:' + string.rjust(2, '0')
when 1
'00:' + string
else
string
end
else
"00:00:00"
end
end
def duration_in_milliseconds(input)
input = convert_to_hrs(input)
if input.match(/\d+:\d\d:\d\d\.?\d*/)
h, m, s = input.split(':').map(&:to_i)
(h.hours + m.minutes + s.seconds) * 1000
else
0
end
end
Unfortunately, when I call duration_in_milliseconds(input) with a number like 8:49, the result is zero. The result should be interpreted as 8 minutes and 49 seconds, which in milliseconds would be 529000. How do I adjust the above to account for this?

I'll just leave this one here:
def convert_to_ms(string)
string.split(':').map(&:to_i).inject(0) { |a, b| a * 60 + b } * 1000
end
convert_to_ms('8:49')
#=> 529000

Just another implementation.. Cause it's fun :)
def duration_in_milliseconds(string)
string.split(':')
.map(&:to_i)
.reverse
.zip([1,60,3600])
.map{ |segment, multiplier| segment*multiplier }
.inject(:+) * 1000
end

Related

Need to convert to integer and then sum of the Plucked data ruby 2.6

I need SUM of the plucked timings.
I did
#dailystatus_infos.task_times.pluck(:total_min)
I got following
["00:00:00", "1:52:00", "00:00:00", "00:02:28", "1:54:00"]
output. [Hour:Minute:Second] format
Now I need to convert those Minutes to integer and sum of it.
I need SUM of the plucked timings
I'd start by writing a helper method to convert the hh:mm:ss string to seconds. A regular expression would work:
def to_seconds(string)
string.match(/(?<hours>\d+):(?<minutes>\d+):(?<seconds>\d+)/) do |m|
m[:hours].to_i * 3600 + m[:minutes].to_i * 60 + m[:seconds].to_i
end
end
to_seconds('00:00:12') #=> 12
to_seconds('00:01:00') #=> 60
to_seconds('00:01:12') #=> 72
Now you can sum the seconds via:
total_mins = ["00:00:00", "1:52:00", "00:00:00", "00:02:28", "1:54:00"]
total_mins.sum { |str| to_seconds(str) }
#=> 13708
And, if necessary, convert that back to h:mm:ss via divmod:
seconds = 13708
hours, seconds = seconds.divmod(3600)
minutes, seconds = seconds.divmod(60)
format('%d:%02d:%02d', hours, minutes, seconds)
#=> "3:48:28"
We first determine the total seconds:
arr = ["00:00:00", "1:52:00", "00:00:00", "00:02:28", "1:54:00"]
s = arr.sum do |str|
str.split(':').reduce(0) { |t, s| t * 60 + s.to_i }
end
#=> 13708
and then manipulate s as desired. The number of minutes, for example, equals
s.fdiv(60)
#=> 228.46666666666667
which might be rounded or truncated.
Something like this
total_minutes = #dailystatus_infos.task_times.pluck(:total_min)
total_minutes.map { |t| (::Time.parse(t).seconds_since_midnight / 60).to_i }.sum
arr = ["00:00:00", "1:52:00", "00:00:00", "00:02:28", "1:54:00"]
arr.map { |t| ::Time.parse(t).seconds_since_midnight / 60 }.sum.to_i
=> 228
You get the minute by splitting on :. Then map and sum.
ary = ["00:00:00", "1:52:00", "00:00:00", "00:02:28", "1:54:00"]
ary.map{ |s| s.split(':')[1].to_i }.sum #=> 108 (0 + 52 + 0 + 2 + 54)

Any option to post-process returned value in long conditional, other than setting variables for each statement?

def some_method(x)
if x == 1
date = Date.today
elsif x == 5
date = Date.today + 2
else
date = Date.today - 2
end
date + 20
end
For visual clarity, is it possible somehow to omit date = for each statement and catch whatever the returned value is from the conditional and add 20 to it?
(The code is for example purpose, my own code has 10 if-statements.)
def some_method(x)
date = if x == 1
Date.today
elsif x == 5
Date.today + 2
else
Date.today - 2
end
date + 20
end
If you have 10 if statements it is probably better to refactor code using case-when like this:
def some_method(x)
date = case x
when 1; Date.today
when 5; Date.today + 2
else; Date.today - 2
end
date + 20
end

Printing 0 to 50 inclusive?

Why is this code not printing 0 to 50 inclusive?
i = 0
until i <= 50 do
print i
i += 1
end
Either use
until i > 50 do
# ...
end
or
while i <= 50 do
# ...
end
Here's a more "Ruby like" example:
(0..50).each do |i|
puts i
end
Ugh.
i = 0
until i <= 50 do
print i
i += 1
end
That would generate 51 iterations because you're starting at 0 and trying to run until 50 is reached, except that until is "notting" your condition. If you want to loop perhaps while would be a better test:
i = 0
while i <= 50 do
print i
i += 1
end
>> 01234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950nil
But, even with while there are still 51 values being output:
i = 0
output = []
while i <= 50 do
output << i
i += 1
end
output.size # => 51
If you want to loop 50 times, why not use:
50.times do |i|
puts i
end
Or:
50.times { |i| puts i }
Change until to while. until is basically the same thing as while, but the conditional is inverted.
Another iterative method to use would be upto:
0.upto(50) do |i|
puts i
end
I really love this method for quick number iterations. It's super idiomatic (it does what it says) and it's inclusive of both start and end values so you don't have to calculate/account for an exclusive end val.
until stops executing when the condition it has is true. Because it is true from the beginning, nothing happens.
Just swap the comparison operators.
i = 0
until i > 50 do
print i
i += 1
end
You could also do
i = 0
while i <= 50 do
print i
i += 1
end
Use Idiomatic Ruby
Other answers have addressed why your original code doesn't work, and have pointed out the logic error in your conditional. However, it's worth noting that a more idiomatic way to do what you're doing would avoid the conditional altogether. For example:
(1..50).each { |i| pp i }
This works. ;)
i = 1
while i < 51 do
print i
i += 1
end
the first thing you have the comparison sing backwards
you want to do something like:
i = 0
until i >= 50 do
print i
i += 1
end
you can take a look at http://ruby-doc.org/core-2.1.2/doc/syntax/control_expressions_rdoc.html#label-until+Loop for more info

finding which timerange includes given time

I have time ranges, for example
normal time : 03:15 - 17:00
discounttime : 17:00 - 23:10
night time : 23:10 - 03:15
And I have to find to which range current time belongs_to.
Before I had such code:
when (3*60+15)..(17*60+00)
puts "normal_time"
when (17*60+00)..(23*60+10)
puts "disc_time"
else
puts "night_time"
end
But this code is not working if I have
normal time : 07:15 - 19:00
discounttime : 19:00 - 01:10
night time : 01:10 - 07:15
Which code should I use to make both configs working?
Should I use 2 days duration?
Dirty Solution
I know that the code is not optimized, but it works. I'll refactor it
time = Time.now
a = [:normal_time, :disc_time, :night_time]
b = a + [a[0]]
pp = Priceplan.last # has times sets for :normal_time, :disc_time, :night_time
check = time.hour*60+time.min
a.each_with_index do |e, i|
starts_at = pp.send(e).hour * 60 + pp.send(e).min
ends_at = pp.send(b[i+1]).hour * 60 + pp.send(b[i+1]).min
if starts_at < ends_at
if (starts_at..ends_at).include?(check)
p "ok!!!!!!!! at #{e}"
break
end
else
if (starts_at..(24*60)).include?(check)
p "ok!!!!!!!! at #{e} (divided first)"
break
elsif (0..ends_at).include?(check)
p "ok!!!!!!!! at #{e} (divided second)"
break
end
end
end
Ranges always run from low numbers to higher numbers. When you have a range like: 19:00-01:10 you're creating an invalid range. To fix it just divide up the ranges so that you have 19:00-23:59 and then a new range 00:00-01:10 to cover the rest of the timespan.
You can try using today midnight as a base point
def today_midnight
Time.now.utc.midnight
end
normal time : where(:belongs_to => (today_midnight + 7.hours + 15.minutes) .. (today_midnight + 19.hours))
discount time : where(:belongs_to => (today_midnight + 1.hours + 10.minutes) .. (today_midnight + 19.hours))
night time: where(:belongs_to => (today_midnight + 1.hour + 10.minutes) .. (today_midnight + 7.hours + 15.minutes))
Though long, I believe writing in such a manner makes the code a lot more readable. i.e. a year after, if you'll be able to understand whats going on by quickly looking at the code.

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