¿Puede Ruby imprimir la diferencia de tiempo (duración) fácilmente?

¿Ruby puede hacer algo como esto?

irb(main):001:0> start = Time.now => Thu Nov 05 01:02:54 -0800 2009 irb(main):002:0> Time.now - start => 25.239 irb(main):003:0> (Time.now - start).duration => "25 seconds" 

(el método de duración no existe ahora) … y de manera similar, informa

 23 minutes and 35 seconds 1 hour and 33 minutes 2 days and 3 hours 

(bien informe la duración total, hasta cuántos segundos, o informe hasta 2 números y unidades (si se informa el día y la hora, entonces no es necesario indicar cuántos minutos))

Aquí hay una forma rápida y sencilla de implementar esto. Establezca medidas predefinidas por segundos, minutos, horas y días. Luego, dependiendo del tamaño del número, da salida a la cadena apropiada con esas unidades. Extenderemos Numeric para que pueda invocar el método en cualquier clase numérica ( Fixnum , Bignum o en su caso Float ).

 class Numeric def duration secs = self.to_int mins = secs / 60 hours = mins / 60 days = hours / 24 if days > 0 "#{days} days and #{hours % 24} hours" elsif hours > 0 "#{hours} hours and #{mins % 60} minutes" elsif mins > 0 "#{mins} minutes and #{secs % 60} seconds" elsif secs >= 0 "#{secs} seconds" end end end 

Hay una gem disponible https://rubygems.org/gems/time_diff

Lo que da la diferencia en un hash

Prueba una gem de Ruby para esa https://rubygems.org/gems/time_difference – joya de diferencia de tiempo para Ruby Documentation en https://github.com/tmlee/time_difference

 start_time = Time.new(2013,1) end_time = Time.new(2014,1) TimeDifference.between(start_time, end_time).in_years => 1.0 

Eche un vistazo al método Rails DateHelper.distance_of_time_in_words . Te dará un excelente lugar para comenzar. A pesar de estar cargado con números mágicos, el enfoque debería funcionar para usted.

La diferencia de tiempo es una bonita cadena impresa:

  class Numeric def duration rest, secs = self.divmod( 60 ) # self is the time difference t2 - t1 rest, mins = rest.divmod( 60 ) days, hours = rest.divmod( 24 ) # the above can be factored out as: # days, hours, mins, secs = self.duration_as_arr # # this is not so great, because it could include zero values: # self.duration_as_arr.zip ['Days','Hours','Minutes','Seconds']).flatten.join ' ' result = [] result < < "#{days} Days" if days > 0 result < < "#{hours} Hours" if hours > 0 result < < "#{mins} Minutes" if mins > 0 result < < "#{secs} Seconds" if secs > 0 return result.join(' ') end end 

Diferencia de tiempo como una matriz:

  class Numeric def duration_as_arr rest, secs = self.divmod( 60 ) rest, mins = rest.divmod( 60 ) days, hours = rest.divmod( 24 ) [days, hours, mins, secs] end end 

Ejemplo:

  x = 1209801.079257 x.duration => "14 Days 3 Minutes 21.079257000004873 Seconds" x.duration_as_arr => [14, 0, 3, 21.079257000004873] 

Sobre la base de la respuesta de Michael Richard , aquí hay un reemplazo para el bloque if que tiene la pluralización inglesa correcta, y no dirá cosas como “14 días y 0 horas”:

 if days > 0 hour_remainder = hours % 24 if hour_remainder > 0 hour_str = hour_remainder == 1 ? 'hour' : 'hours' "#{days} days and #{hour_remainder} #{hour_str}" elsif days == 1 "#{days} day" else "#{days} days" end elsif hours > 0 min_remainder = mins % 60 if min_remainder > 0 min_str = min_remainder == 1 ? 'minute' : 'minutes' "#{hours} hours and #{min_remainder} #{min_str}" elsif hours == 1 "#{hours} hour" else "#{hours} hours" end elsif mins > 0 sec_remainder = secs % 60 if sec_remainder > 0 sec_str = sec_remainder == 1 ? 'second' : 'seconds' "#{mins} minutes and #{sec_remainder} #{sec_str}" elsif minutes == 1 "#{mins} minute" else "#{mins} minutes" end elsif secs == 1 "#{secs} second" elsif secs >= 0 "#{secs} seconds" end 

No pude soportar poner una solución genérica aquí, aunque: ¿tiene un año, 365 días?

Adicional pongo un abs al convertir self.to_int

 class Numeric def duration steps=[60, 60, 24, 365,0] names=[:seconds, :minutes, :hours, :days, :years] results=[] stepper = self.to_int.abs steps.each { |div| if stepper>0 if div>0 results< 0 ? results[e-1] : 0 et=results[e] || 0 et.to_s+" "+names[e].to_s + (mt>0 ? " "+mt.to_s+" "+names[e-1].to_s : '') end end 

y con traducción

 class Numeric def i18n_duration steps=[60, 60, 24, 365,0] names=[:seconds, :minutes, :hours, :days, :years] results=[] stepper = self.to_int.abs steps.each { |div| if stepper>0 if div>0 results< 0 ? results[e-1] : 0 et=results[e] || 0 I18n.t("datetime.distance_in_words.x_#{names[e]}", count: et) + (mt>0 ? " "+I18n.t("datetime.distance_in_words.x_#{names[e-1]}", count: mt):'') end end 

Como alternativa, puedes hacer esto:

 start = DateTime.now.strftime('%Q').to_i / 1000.0 #=> 1409163050.088 sleep 3.5 #=> 3 now_ms = DateTime.now.strftime('%Q').to_i / 1000.0 #=> 1409163053.606 '%.3f' % (now_ms - start) #=> "3.518" 

He dado una implementación alternativa para esto cuando se usa en los registros de script aquí [copiado desde allí]:

Cómo generar un rango de tiempo legible para humanos usando Ruby on Rails

Si desea mostrar duraciones significativas en el rango de segundos a días, una alternativa sería (ya que no tiene que realizar el mejor):

 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(dhms)).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' 

Alternativamente, puede que solo le interese quitar la parte de segundos cuando no importe (lo que también demuestra otro enfoque):

 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(dhms)[-parts.size..-1]) pairs.pop if pairs.size > 2 # do not report seconds when irrelevant pairs.flatten.join end 

Espero que ayude.

Vine con esta solución

 def formate_duration(started, ended) differences = ["secs", "mins", "hours", "days"].reduce([[nil, [(ended - started).round, nil]]]) do |acc, unit| mod = unit == "hours" ? 24 : 60 # will return [ over, unit_value ] # over is than used for calculation in next step # accumulator is in format [["unit" [ over, value]]] and we are interesting in latest over in each step # => This is why main diff is in this place in accumulator entry = acc[acc.size-1][1][0].divmod(mod) acc < < [ unit, entry ] end # this will do string conversion and reverse in one step str = differences.drop(1).reduce("") do |acc, current| if (current[1][1] > 0) "#{current[1][1]} #{current[0]} #{acc}" else acc end end str.empty? ? "now" : str end 

Tenga en cuenta que esto no funcionará para las diferencias superiores a 60 días (en ese caso, deberá agregar la condición)

 time_difference = current_time - old_time def seconds_fraction_to_time(time_difference) days = hours = mins = 0 mins = (seconds / 60).to_i seconds = (seconds % 60 ).to_i hours = (mins / 60).to_i mins = (mins % 60).to_i days = (hours / 24).to_i hours = (hours % 24).to_i return [days,hours,mins,seconds] end 

entonces puedes imprimirlo como quieras,

es decir

 if(days > 0) return "#{days} Days #{hours} Hours" else return "#{hours} Hours #{mins} Minutes" end