2010-11-09 21 views
23

Estoy tratando de encontrar la mejor manera de generar la siguiente salidaCómo generar un rango de tiempo legible por humanos usando Ruby on Rails

<name> job took 30 seconds 
<name> job took 1 minute and 20 seconds 
<name> job took 30 minutes and 1 second 
<name> job took 3 hours and 2 minutes 

empecé este código

def time_range_details 
    time = (self.created_at..self.updated_at).count 
    sync_time = case time 
    when 0..60 then "#{time} secs"  
    else "#{time/60} minunte(s) and #{time-min*60} seconds" 
    end 
end 

¿Existe una forma más eficiente de hacer esto. Parece mucho código redundante para algo súper simple.

Otro uso para esto es:

<title> was posted 20 seconds ago 
<title> was posted 2 hours ago 

El código para esto es similar, pero en lugar de utilizar i Time.now:

def time_since_posted 
    time = (self.created_at..Time.now).count 
    ... 
    ... 
end 

Respuesta

50

Si necesita algo más "preciso" que distance_of_time_in_words, puede escribir algo en este sentido:

def humanize secs 
    [[60, :seconds], [60, :minutes], [24, :hours], [1000, :days]].map{ |count, name| 
    if secs > 0 
     secs, n = secs.divmod(count) 
     "#{n.to_i} #{name}" 
    end 
    }.compact.reverse.join(' ') 
end 

p humanize 1234 
#=>"20 minutes 34 seconds" 
p humanize 12345 
#=>"3 hours 25 minutes 45 seconds" 
p humanize 123456 
#=>"1 days 10 hours 17 minutes 36 seconds" 
p humanize(Time.now - Time.local(2010,11,5)) 
#=>"4 days 18 hours 24 minutes 7 seconds" 

Oh , un comentario en tu c oda:

(self.created_at..self.updated_at).count 

es realmente mala manera de conseguir la diferencia. Utilice simplemente:

self.updated_at - self.created_at 
+0

solo curiosidad, ¿por qué es malo? (self.created_at..self.updated_at) .count ¡gracias por la respuesta limpia! – csanz

+2

@csanz: '.count' en ese caso itera a través de una matriz de cada segundo entre las dos marcas de tiempo y las cuenta. Como si calcularas el resultado de la expresión '100-50' contando todos los números entre 50 y 100. –

+1

Prefiero los métodos DateHelper. Si te vas a molestar en convertirlo al inglés, entonces probablemente no quieras combinar días y segundos. Es una precisión artificial. –

23

Hay dos métodos en DateHelper que puedan dar lo que desee:

  1. time_ago_in_words

    time_ago_in_words(1234.seconds.from_now) #=> "21 minutes" 
    
    time_ago_in_words(12345.seconds.ago)  #=> "about 3 hours" 
    
  2. distance_of_time_in_words

    distance_of_time_in_words(Time.now, 1234.seconds.from_now) #=> "21 minutes" 
    
    distance_of_time_in_words(Time.now, 12345.seconds.ago)  #=> "about 3 hours" 
    
0

hay un problema con distance_of_time_in_words si u ll acontecido 1 hora y 30 min que regresaré aproximadamente 2 horas

Simplemente agregue ayudante:

PERIODS = { 
    'day' => 86400, 
    'hour' => 3600, 
    'minute' => 60 
    } 


def formatted_time(total) 
    return 'now' if total.zero? 

    PERIODS.map do |name, span| 
    next if span > total 
    amount, total = total.divmod(span) 
    pluralize(amount, name) 
    end.compact.to_sentence 
end 

Basicamente solo pase sus datos en segundos.

1

Si desea mostrar duraciones significativas en los segundos a la gama días, una alternativa sería (ya que no tiene que realizar la 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(d h m s)).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' 

alternativa, puede haber sólo está interesado en la destrucción la parte segundos cuando no importa (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(d h m s)[-parts.size..-1]) 
    pairs.pop if pairs.size > 2 # do not report seconds when irrelevant 
    pairs.flatten.join 
end 

Espero que ayude.