3

I'm trying to add hours and minutes that are stored in the database like this:

+----+---------+-------+
| id | user_id | time  |
+----+---------+-------+
|  1 |       4 | 03:15 |
|  2 |       4 | 02:22 |
+----+---------+-------+

The time field is a string. How can I add the hours and minutes expressed by the strings like 05:37?

I tried this

current_user.table.pluck(:time).sum(&:to_f)

but the output is only 5.

Stefan
  • 109,145
  • 14
  • 143
  • 218
user9769384
  • 70
  • 1
  • 18
  • 1
    Perhaps you should change the format of the saved data. If you used an int for minutes this would be much easier. This would also enable you to filter (e.g. everything less than 1 hour) or sum up values. – mbuechmann Sep 27 '18 at 06:12
  • `current_user.table` probably refers to the user table and not to the table where the times are saved. – mbuechmann Sep 27 '18 at 06:15
  • It doesn't make sense to add times without dates, so I'm assuming it's more like a duration? Like your measuring time for running laps or something? Also look at the database types Rails supports e.g. `Time`, see https://stackoverflow.com/questions/11889048/is-there-documentation-for-the-rails-column-types. – Sree Sep 27 '18 at 06:15
  • @mbuechmann the `table` is `times` table where user can saving their `times` – user9769384 Sep 27 '18 at 06:31
  • _"The time field is a string"_ – why? You should save data in an appropriate format. If this is elapsed time, you could for example store the number of minutes (which can easily be converted into HH:MM for displaying purposes). – Stefan Sep 27 '18 at 07:11
  • @Sree _"It doesn't make sense to add times without dates"_ – opening hours or alarm clocks are typical examples for times without dates. – Stefan Sep 27 '18 at 07:16

2 Answers2

10

If you read the times from your table into an array you obtain something like

arr = ["03:15", "02:22"]

You could then write

arr.sum do |s|
  h, m = s.split(':').map(&:to_i)
  60*h + m
end.divmod(60).join(':')
  #=> "5:37"

See Array#sum (introduced in MRI v2.4) and Integer#divmod. To support Ruby versions earlier than 2.4 use Enumerable#reduce in place of Array#sum.

The three steps are as follows.

mins = arr.sum do |s|
  h, m = s.split(':').map(&:to_i)
  60*h + m
end
  #=> 337
hm = mins.divmod(60)
  #=> [5, 37]
hm.join(':')
  #=> "5:37"
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • You can use `sum` instead of `reduce`. – Stefan Sep 27 '18 at 07:22
  • Thanks, @Stefan, much better. I did an edit. I can’t test now but it looks OK. Btw, I picked up a downvote. I wonder if it was because I named my variable for totals minutes `secs` (which you changed—thanks). Sleep awaits. – Cary Swoveland Sep 27 '18 at 08:43
  • Downvote for variable naming? I don't think so, but you never know. – Stefan Sep 27 '18 at 08:52
  • Hey @CarySwoveland Would you please suggest me how I subtract like I need to subtract `10:15` to subtract `05:05`, please advise how – user9769384 Sep 27 '18 at 10:10
  • 1
    @user9769384 it works the same way: (1) convert each value to the number of minutes, i.e. `10:15` → `615` and `05:05` → `305` (2) subtract the minutes `615 - 305` → `310` (3) convert the result to HH:MM `310.divmod(60)` → `[5, 10]` which is `05:10` – Stefan Sep 27 '18 at 11:34
6

Please check this sample of summing time in ruby:

require 'time'

t = Time.parse("3:15")
puts t.strftime("%H:%M")

t2 = Time.parse("02:22")
puts t2.strftime("%H:%M")
t3 = t.to_i + t2.to_i

puts Time.at(t3).utc.strftime("%H:%M")

This is fast sum of times below 24 hour span. For correct solution for every case please check @Cary code above.

Here is a small Ruby Gem made from @Cary code sample which extends Ruby Array class with method sum_strings in example like:

["12:23","23:30","1:2"].sum_strings(':') will result as "36:55"

Gem https://github.com/nezirz/sum_strings/

Sample project with using Gem: https://github.com/nezirz/use_sum_strings

Nezir
  • 6,727
  • 12
  • 54
  • 78
  • 4
    I suggest you use `strptime` rather than `parse`. My standard example for illustrating the problem with `parse` is `Time.parse("It was a tough week for Elizabeth May.") #=> 2018-05-01 00:00:00 -0700`. Otherwise, good answer. – Cary Swoveland Sep 27 '18 at 06:47
  • 3
    Hold the phone! Try changing the first statement to `t = Time.parse("23:15")`. You should get `"25:37"` as the total, but being more than 24 hours you have a problem. – Cary Swoveland Sep 27 '18 at 06:53
  • Or `Time.parse('27:15')` which results in an `ArgumentError` – the OP didn't say that the values don't exceed 24 hours. – Stefan Sep 27 '18 at 07:34
  • 1
    `Hold the phone` :) neat phrase – fl00r Sep 27 '18 at 08:39