web-dev-qa-db-ger.com

So erstellen Sie einen für Menschen lesbaren Zeitbereich mit Ruby on rail

Ich versuche den besten Weg zu finden, um die folgende Ausgabe zu erzeugen

<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

Ich habe diesen Code gestartet

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

Gibt es einen effizienteren Weg, dies zu tun? Es scheint viel redundanter Code für etwas sehr einfaches zu sein. 

Eine andere Verwendung dafür ist:

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

Der Code dafür ist ähnlich, aber stattdessen verwende ich Time.now:

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

Wenn Sie etwas genaueres als distance_of_time_in_words benötigen, können Sie Folgendes schreiben:

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, eine Bemerkung zu Ihrem Code:

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

ist wirklich schlechter Weg, um den Unterschied zu ermitteln. Verwenden Sie einfach:

self.updated_at - self.created_at
58

Es gibt zwei Methoden in DateHelper, die Ihnen das geben können, was Sie wollen:

  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"
    
26
Doug R

chronic_duration parst numerische Zeit in lesbar und umgekehrt

7
usr

Wenn Sie signifikante Dauern im Sekunden- bis Tagebereich anzeigen möchten, wäre dies eine Alternative (da sie nicht die beste Leistung erbringen muss):

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'

Alternativ können Sie nur daran interessiert sein, den Sekundenabschnitt zu entfernen, wenn es keine Rolle spielt (auch einen anderen Ansatz zeigt):

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

Hoffentlich hilft das.

1
rosenfeld

Rails hat eine DateHelper für Ansichten. Wenn das nicht genau das ist, was Sie möchten, müssen Sie möglicherweise Ihr eigenes schreiben.

@Mladen Jablanović hat eine Antwort mit gutem Beispielcode. Wenn Sie nichts dagegen haben, eine Humanize-Beispielmethode weiter anzupassen, kann dies ein guter Ausgangspunkt sein.

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

Dadurch erhalten Sie eine Reihe von Werten und Gerätenamen, die Sie bearbeiten können.

Wenn das erste Element nicht Null ist, handelt es sich um die Anzahl der Tage. Möglicherweise möchten Sie Code schreiben, um mehrere Tage zu behandeln, z. B. Wochen, Monate und Jahre. Trennen Sie andernfalls die führenden 0-Werte und nehmen Sie die nächsten beiden Werte.

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

Der Code findet sogar Verwendung für eine while-Anweisung von Ruby.

0
Marlin Pierce

Es gibt ein Problem mit distance_of_time_in_words, wenn Sie dort vorbeikommen 1 Stunde 30 Minuten es kehrt zurück ca. 2 Stunden

Einfach Helfer hinzufügen:

 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

Übergeben Sie Ihre Daten grundsätzlich in Sekunden.

0
Denis