2

I'm using Ruby 2.4. I want to convert milliseconds to a readable hours, minutes, and seconds format. SO I have this method

  def time_as_str(time_in_ms)
    regex = /^(0*:?)*0*/
    Time.at(time_in_ms.to_f/1000).utc.strftime("%H:%M:%S.%1N").sub!(regex, '')
  end

The problem is, if my value in milliseconds is a day or greater, this function doesn't display the correct values. FOr instance, if I pass

2.4.0 :009 > TimeFormattingHelper.time_as_str(86400000)
 => ".0"

86400000 is a day in milliseconds (unless I've miscalculated). So I would expect the value to be "24:00:00". How do I correct the above to display the time formatted properly?

Dave
  • 15,639
  • 133
  • 442
  • 830
  • Possible duplicate of [How to generate a human readable time range using ruby on rails](http://stackoverflow.com/questions/4136248/how-to-generate-a-human-readable-time-range-using-ruby-on-rails) – Brad Werth Apr 17 '17 at 22:15
  • Your regex is messed up. Even with it gone, this doesn't do what you are expecting. `Time.at(86400000.to_f/1000) => 1970-01-01 18:00:00 -0600`. You're getting the hours from the date... `Time.at(86400000.to_f/1000).strftime("%H:%M:%S.%1N") => "18:00:00.0" ` https://ruby-doc.org/core-2.3.0/Time.html#method-c-at – Brad Werth Apr 17 '17 at 22:18
  • 1
    Also, when you are trying to troubleshoot something like this, it helps to break it down into steps, each one simple enough to easily determine success. Your last line has no fewer than 6 individual operations, each one contributing to the difficulty of determining exactly where all of this went wrong... – Brad Werth Apr 17 '17 at 22:23
  • Read Jon Skeet's "[Writing the perfect question](https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/)" – the Tin Man Apr 17 '17 at 22:53
  • 3
    `"24:00:00"` is an invalid time though isn't it? Your method is returning a `Time` object and so should only return valid times i.e between `"00:00:00"` and `"23:59:59"`. Unless you're trying to convert milliseconds to hours, minutes and seconds which is different to converting a valid time in milliseconds to in hh:mm:ss. – Sagar Pandya Apr 17 '17 at 22:55
  • @sagarpandya82, Perhaps I shouldn't have used the word "Time" but rather "duration". I'm not looking to get the time, but the duration as something that's human readable. – Dave Apr 17 '17 at 23:17
  • You should modify your question to make that clear. Put the information where you would have if you'd incorporated it originally. Don't use "edit" or "update" tags as we can see what changed and when if we need to. – the Tin Man Apr 17 '17 at 23:27
  • Possible duplicate of [Convert milliseconds to hours and mins in ruby](http://stackoverflow.com/questions/40485828/convert-milliseconds-to-hours-and-mins-in-ruby) – Sagar Pandya Apr 17 '17 at 23:34

2 Answers2

1
def time_as_str(ms)
  secs, ms    =   ms.divmod(1000)
  mins, secs  = secs.divmod(60)
  hours, mins = mins.divmod(60)
  s = "%d.%d.%d.%s" % [hours, mins, secs, ms.zero? ? "0" : ms.to_s.sub(/0*\z/,'')]
  if hours > 0
    s
  elsif mins > 0
    s[2..-1]
  else
    s[4..-1]   
  end      
end

time_as_str(86_400_000)    #=> "24.0.0.0"
time_as_str(0)             #=> "0.0"
time_as_str(499)           #=> "0.499"
time_as_str(60_280)        #=> "1.0.28"
time_as_str(360_000)       #=> "6.0.0"
time_as_str(1_000_000_200) #=> "277.46.40.2"

See Integer#divmod, an oft-overlooked method.

Suppose

ms = 2_045_670

then

secs, ms    =   ms.divmod(1000)
  #=> [2045, 670] (secs #=> 2045, ms #=> 670)
mins, secs  = secs.divmod(60)
  #=> [34, 5]
hours, mins = mins.divmod(60)
  #=> [0, 34]
ms = ms.zero? ? "0" : ms.to_s.sub(/0*\z/,'')
  #=> "67" (truncate)
s = "%d.%d.%d.%s" % [hours, mins, secs, ms]
  #=> "0.34.5.67"
if hours > 0   # false
  "0.34.5.67" 
elsif mins > 0 # true
  "34.5.67"
else           # not evaluated
  "5.67" 
end
  #=> "34.5.67"
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • Hi Cary, Thanks for this answer. I'm ahving the same issues that I was with Atul's answer, namely if I plug in 150400 into your function, I'm getting "0:02:30". I should be getting "2:30.4" to be nice and exact. Also is there any way to eliminate the leading zeroes? – Dave Jul 09 '17 at 16:53
  • Dave, that's an easy fix. Two questions. 1) What precision do you want for seconds (e.g., should 150412ms be "2.30.412"?. 2) Do you want to remove leading zeroes for minutes and seconds, as well as for hours? – Cary Swoveland Jul 09 '17 at 20:03
  • Hi, I'd like the precision to be whatever accurately represents my number of milliseconds. Since I'll never have a fractional number of milliseconds (it was always be a whole number), I'm guessing the precision will never be more than 3 places after the decimal point. I "think" I'm correct with that declaration. Yes, I would like to remove leading zeroes for both minutes and seconds, and hours. – Dave Jul 10 '17 at 15:00
  • I modified my answer. – Cary Swoveland Jul 10 '17 at 18:10
-1

Some basic info:

  • a second = 1000 milliseconds
  • a minute = 1000 × 60 milliseconds
  • an hour = 1000 × 60 × 60 milliseconds

Now, you can try below method:

def get_duration_hrs_and_mins(duration)
  duration = duration.to_i

  h = duration / (1000 * 60 * 60)
  m = duration / (1000 * 60) % 60
  s = duration / (1000.to_f) % 60

  h = zero_to_nil(h)
  m = zero_to_nil(m) if h.nil?

  arr = [h, m, s].map { |e| format_val(e) }

  arr.compact.join(':')
end

def format_val(e)
  case
  when e.nil?
    nil
  when e.zero?
    '00'
  when e.round(0) == e
    sprintf("%2d", e)
  else
    e.round(3)
  end
end

def zero_to_nil(val)
  val.zero? ? nil : val
end

Some output from rails console:

puts get_duration_hrs_and_mins(86400000)
# => 24:00:00
puts get_duration_hrs_and_mins(150400)
# => 2:30.4
puts get_duration_hrs_and_mins(150401)
# => 2:30.401
puts get_duration_hrs_and_mins(1500)
# => 1.5
puts get_duration_hrs_and_mins(864000)
# => 14:24
Radix
  • 2,527
  • 1
  • 19
  • 43
  • Thanks but there are a couple of issues with teh above. First, calling the function with 150400 is resulting in "00:02:30". It should be "2:30.4" (tenths of a second are getting chopped off in your function). Also your function is including leading zeroes and I'd like to exclude those. – Dave Jul 05 '17 at 14:44
  • @Dave So, you, even, want to exclude hour if it is `00`. And for seconds you want to print a one or two digit after decimal (`.`). Right? – Radix Jul 05 '17 at 18:02
  • Yes, definitely want to exclude leading zeroes. You can assume the "duration" is always going to be an integer, so I would like to include all decimal places taht accurately reflect what that value is. Thanks, – Dave Jul 05 '17 at 21:26
  • What is number_with_precision ? Is taht a built in Ruby function? I'm getting an error when I try and run this complaining about not knowing what "number_with_precision" is. – Dave Jul 07 '17 at 16:57
  • [`number_with_precision`](https://apidock.com/rails/ActionView/Helpers/NumberHelper/number_with_precision) is rails `NumberHelper`. I've included that helper (`ActionView::Helpers::NumberHelper`) in the answer. – Radix Jul 08 '17 at 05:50
  • Added a ruby way to achieve that. – Radix Jul 08 '17 at 06:30
  • Appreciate the updates. There are still a couple of strange things happening though. For instance, if I call the function with my example from earlier comments, 150400, it is returning "02:30.4". Then if I call it with 150401, it is still returning "02:30.4". Those results should be different, no? Also, the leading zeroes are still creeping in there. – Dave Jul 09 '17 at 16:50
  • @Dave I'm don't think it should be different. Seconds value of 150401 30.40100000000001, which gives 30.4 after rounding off. What's your expectation? – Radix Jul 09 '17 at 17:25
  • Also, there's no leading zero for hour and minutes, but is only for seconds. For example, 150 gives 0.15, no leading zero's for hour and min. Don't you want leading zero's for seconds too? If not then what should be the output if 0 is passed? – Radix Jul 09 '17 at 17:28
  • Milliseconds is seconds times 1,000, right? So 150401 is actually 150.401 seconds. So the numbers after the decimal would be ".401". Also could we make the answer "02:30.4" just be "2:30.4"? For 0, it is fine if the answer is "0". – Dave Jul 09 '17 at 17:50
  • @Dave Currently the method is returning two digits after decimal using `round(2)`, you could make it to `round(3)` to get the desired output. I've just updated the answer with changes including removing leading zero's & rounding to 3 decimal places.. – Radix Jul 09 '17 at 18:08