Search code examples
ruby

Ruby add/subtract seconds, minutes, hours only


How can I start with a HH:MM:SS Ruby string like "00:00:08" and combine it with "-00:00:06" to get the difference, which would be "00:00:02" in this case?

Anything I try to look up involves converting to a DateTime, but do I really need to involve days or years to find out the relative difference?


Solution

  • To balance readability, testability and compactness I propose the following three methods. I don't think the complexity of the calculation justifies the creation of a class.

    Convert strings to seconds

    def str_to_seconds(str)
      tot = str.match(/(\d{2,}):(\d{2}):(\d{2})(?!\d)/)
               .captures
               .zip([3600, 60, 1])
               .sum { |s,d| s.to_i * d }
      str[0] == '-' ? -tot : tot
    end
    

    Some examples:

    puts "      str        str_to_seconds(str)"
    puts "------------------------------------"
    ["00:00:07", "00:00:37", "00:02:07", "00:58:02", "02:00:07",
     "06:01:02", "50:00:07", "130:00:07", "-130:00:07", "00:00:011"].each do |str|
      print "%11s" % str
      puts "   %15d" % str_to_seconds(str)
    end
    
          str        str_to_seconds(str)
    ------------------------------------
       00:00:07                 7
       00:00:37                37
       00:02:07               127
       00:58:02              3482
       02:00:07              7207
       06:01:02             21662
       50:00:07            180007
      130:00:07            468007
     -130:00:07           -468007
       00:00:011
      NoMethodError: undefined method `captures' for nil:NilClass
    

    Convert seconds to strings

    def seconds_to_str(secs)
      abs_secs = secs.abs
      hrs, abs_secs = abs_secs.divmod(3600)
      mins, abs_secs = abs_secs.divmod(60)
      str = "%02d:%02d:%02d" % [hrs, mins, abs_secs]
      secs < 0 ? "-#{str}" : str
    end
    

    Some examples:

    puts "      secs     seconds_to_str(secs)"
    puts "-----------------------------------"
    [7, 37, 127, 3482, -7207, 21662, 180007, 468007, -468007].each do |secs|
      puts "%11d   %14s" % [secs, seconds_to_str(secs)]
    end
    
          secs     seconds_to_str(secs)
    -----------------------------------
              7         00:00:07
             37         00:00:37
            127         00:02:07
           3482         00:58:02
          -7207        -02:00:07
          21662         06:01:02
         180007         50:00:07
         468007        130:00:07
        -468007       -130:00:07
    

    Compute desired result

    def compute(str1, str2)
      seconds_to_str(str_to_seconds(str1) + str_to_seconds(str2))
    end
    

    Some examples:

    puts "    str1        str2     compute(str1, sz)"
    puts "------------------------------------------"
    [[ "00:00:45", "00:00:17"], [" 00:00:45", "-00:00:17"],
     ["-00:00:45", "00:00:17"], ["-00:00:45", "-00:00:17"],
     [" 16:12:05", "13:13:06"], [ "16:12:05", "-13:13:06"],
     ["-16:12:05", "13:13:06"], ["-16:12:05", "-13:13:06"],].each do |str1, str2|
      puts "%10s  %10s      %10s" % [str1, str2, compute(str1, str2)]
    end
    
        str1        str2     compute(str1, sz)
    ------------------------------------------
      00:00:45    00:00:17        00:01:02
      00:00:45   -00:00:17        00:00:28
     -00:00:45    00:00:17       -00:00:28
     -00:00:45   -00:00:17       -00:01:02
      16:12:05    13:13:06        29:25:11
      16:12:05   -13:13:06        02:58:59
     -16:12:05    13:13:06       -02:58:59
     -16:12:05   -13:13:06       -29:25:11