J'essaie de trouver le meilleur moyen de générer la sortie suivante
<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
J'ai commencé ce code
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
Y at-il un moyen plus efficace de le faire? Il semble que beaucoup de code redondant pour quelque chose de super simple.
Une autre utilisation pour ceci est:
<title> was posted 20 seconds ago
<title> was posted 2 hours ago
Le code utilisé est similaire, mais j'utilise plutôt Time.now:
def time_since_posted
time = (self.created_at..Time.now).count
...
...
end
Si vous avez besoin de quelque chose de plus "précis" que distance_of_time_in_words
, vous pouvez écrire quelque chose dans le sens suivant:
def humanize secs
[[60, :seconds], [60, :minutes], [24, :hours], [Float::INFINITY, :days]].map{ |count, name|
if secs > 0
secs, n = secs.divmod(count)
"#{n.to_i} #{name}" unless n.to_i==0
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, une remarque sur votre code:
(self.created_at..self.updated_at).count
est vraiment mauvaise façon de faire la différence. Utilisez simplement:
self.updated_at - self.created_at
Il existe deux méthodes dans DateHelper
qui peuvent vous donner ce que vous voulez:
time_ago_in_words( 1234.seconds.from_now ) #=> "21 minutes"
time_ago_in_words( 12345.seconds.ago ) #=> "about 3 hours"
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"
chronic_duration analyse le temps numérique en lisible et vice versa
Si vous souhaitez afficher des durées significatives comprises entre quelques secondes et quelques jours, une alternative serait (car elle ne doit pas forcément donner les meilleurs résultats):
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'
Sinon, vous ne voudrez peut-être que supprimer la partie secondes lorsque ce n'est pas grave (démontrant également une autre approche):
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
J'espère que cela pourra aider.
Rails a une DateHelper
pour les vues. Si ce n'est pas exactement ce que vous voulez, vous devrez peut-être écrire le vôtre.
@Mladen Jablanović a une réponse avec un bon exemple de code. Toutefois, si vous souhaitez continuer à personnaliser un exemple de méthode humanize, cela pourrait être un bon point de départ.
def humanized_array_secs(sec)
[[60, 'minutes '], [60, 'hours '], [24, 'days ']].inject([[sec, 'seconds']]) do |ary, (count, next_name)|
div, prev_name = ary.pop
quot, remain = div.divmod(count)
ary.Push([remain, prev_name])
ary.Push([quot, next_name])
ary
end.reverse
end
Cela vous donne un tableau de valeurs et de noms d'unités que vous pouvez manipuler.
Si le premier élément est différent de zéro, c'est le nombre de jours. Vous voudrez peut-être écrire du code pour gérer plusieurs jours, par exemple afficher des semaines, des mois et des années. Sinon, coupez les valeurs 0
en tête et prenez les deux suivantes.
def humanized_secs(sec)
return 'now' if 1 > sec
humanized_array = humanized_array_secs(sec.to_i)
days = humanized_array[-1][0]
case
when 366 <= days
"#{days / 365} years"
when 31 <= days
"#{days / 31} months"
when 7 <= days
"#{days / 7} weeks"
else
while humanized_array.any? && (0 == humanized_array[-1][0])
humanized_array.pop
end
humanized_array.reverse[0..1].flatten.join
end
end
Le code trouve même une utilisation pour une instruction Ruby while
.
Il y a un problème avec distance_of_time_in_words
si vous allez y passer 1 heure 30 min ça va revenir environ 2 heures
Ajoutez simplement dans helper:
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
En gros, il suffit de transmettre vos données en quelques secondes.