Как я могу начать со строки HH:MM:SS Ruby, например «00:00:08», и объединить ее с «-00:00:06», чтобы получить разницу, которая в этом случае будет «00:00:02» случай?
Все, что я пытаюсь найти, включает преобразование в DateTime, но действительно ли мне нужно потратить дни или годы, чтобы выяснить относительную разницу?
Наверное, я не понимаю, как это моделировать с помощью DateTime.
@kraftydevil: Time.strptime('00:00:08', '%H:%M:%s')-Time.strptime('00:00:06', '%H:%M:%s')? Возвращает 2.0, которое вы можете преобразовать обратно во время или просто выполнить простые математические вычисления, чтобы самостоятельно отформатировать его как строку.
@ShadowRanger %s — количество секунд с начала эпохи, оно переопределяет все остальное. используйте %S.
@Алекс: Ах, упс. Вот что я получаю за то, что не перепроверил документы. Спасибо за исправление.

Если вы хотите избежать DateTime, вы можете вручную проанализировать время и преобразовать его в секунды.
def to_seconds(str)
neg = str =~ /^\-/
str = str.sub(/^\-/, '')
(neg ? -1 : 1) * str
.split(':')
.map(&:to_i)
.reverse
.each_with_index
.sum { |x, i| x * 60 ** i }
end
Складывать или вычитать секунды легко. На этом этапе вам просто нужно отменить процесс, чтобы вернуться к отформатированной метке времени в часах/минутах/секундах.
Создайте класс для анализа вашей строки в секундах, определите любые необходимые операции, такие как + и -, и отформатируйте обратно в строковое представление из секунд. Наличие отрицательного знака в строке требует немного дополнительной руки:
class Tyme
def initialize str
@negative = str.start_with?("-")
str = str.sub(/-/, "")
@h, @m, @s = str.split(":").map(&:to_i)
@total = @h * 3600 + @m * 60 + @s
end
def total
@negative ? -@total : @total
end
def +(other)
format total + other.total
end
def -(other)
format total - other.total
end
private
def format total_seconds
minutes, seconds = total_seconds.abs.divmod(60)
hours, minutes = minutes.divmod(60)
fmt = "%02d:%02d:%02d" % [hours, minutes, seconds]
total_seconds.negative? ? "-#{fmt}" : fmt
end
end
>> Tyme.new("00:00:08") + Tyme.new("-00:00:02")
=> "00:00:06"
>> Tyme.new("00:00:08") - Tyme.new("00:00:02")
=> "00:00:06"
>> Tyme.new("00:00:08") - Tyme.new("-00:00:02")
=> "00:00:10"
@Стефан рефакторинг
class Duration
def self.parse str
negative = str.start_with?("-") ? -1 : 1
str = str.sub(/-/, "")
h, m, s = str.split(":").map(&:to_i)
total = negative * (h * 3600 + m * 60 + s)
new total
end
def initialize seconds
@seconds = seconds
end
attr_reader :seconds
def +(other)
Duration.new seconds + other.seconds
end
def -(other)
Duration.new seconds - other.seconds
end
def to_s
minutes, seconds = @seconds.abs.divmod(60)
hours, minutes = minutes.divmod(60)
fmt = "%02d:%02d:%02d" % [hours, minutes, seconds]
@seconds.negative? ? "-#{fmt}" : fmt
end
def inspect
"#<Duration #{to_s}>"
end
end
>> Duration.parse("00:00:08") + Duration.parse("-00:00:02")
=> #<Duration 00:00:06>
>> Duration.parse("00:00:08") - Duration.parse("00:00:02")
=> #<Duration 00:00:06>
>> Duration.parse("00:00:08") - Duration.parse("-00:00:02")
=> #<Duration 00:00:10>
Несколько предложений: сделайте так, чтобы initialize взял количество секунд и определил отдельный self.parse для анализа строк. Измените + и -, чтобы возвращать новые экземпляры Tyme вместо строк. Переименуйте format в to_s/inspect. (возможно, переименуйте Tyme в Duration)
Вот еще один вариант:
def time_to_seconds(time)
ord,hours,min,seconds = time.scan(/(-|\+)?(\d+):(\d{1,2}):(\d{1,2})/).flatten
result = [3600,60,1].zip([hours,min,seconds].map(&:to_i)).sum{|a| a.reduce(:*)}
result.send("#{ord || '+'}@")
end
def diff_times(t1,t2)
diff_seconds = time_to_seconds(t1) + time_to_seconds(t2)
ord = '-' if diff_seconds < 0
result = [3600,60,1].map {|e| r,diff_seconds = diff_seconds.abs.divmod(e); r }
sprintf "#{ord}%02d:%02d:%02d", *result
end
t1 = "00:00:08"
t2 = "-00:00:06"
diff_times(t1,t2)
#=> "00:00:02"
Сначала мы преобразуем каждое время в секунды, используя регулярное выражение для захвата каждой группы часов, минут и секунд, учитывая + или -.
Затем мы складываем их вместе, чтобы получить продолжительность в секундах.
Наконец, мы конвертируем их обратно в массив [hours, minutes,seconds] и используем Kernel#sprintf для форматирования вывода String
Чтобы сбалансировать читабельность, тестируемость и компактность, я предлагаю следующие три метода. Я не думаю, что сложность вычислений оправдывает создание класса.
Преобразование строк в секунды
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
Некоторые примеры:
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
Преобразование секунд в строки
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
Некоторые примеры:
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
Вычислите желаемый результат
def compute(str1, str2)
seconds_to_str(str_to_seconds(str1) + str_to_seconds(str2))
end
Некоторые примеры:
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
@engineersmnky, спасибо за то, что твой орлиный взгляд снова обратил внимание на мой ответ. Я сделал редактирование.
Вам это не нужно, но использование DateTime кажется самым простым способом.