26


16

Ruby on Railsを使用して人間が読める時間範囲を生成する方法

私は次の出力を生成する最良の方法を見つけようとしています

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

私はこのコードを始めました

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

これを行うより効率的な方法はありますか。 非常に単純なもののための多くの冗長コードのようです。

これの別の用途は次のとおりです。

 was posted 20 seconds ago
 was posted 2 hours ago

このコードは似ていますが、代わりにTime.nowを使用します。

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

6 Answer


58


http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words [distance_of_time_in_words]よりも「正確」なものが必要な場合は、次の行に沿って何かを書くことができます。

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"

ああ、あなたのコードに関する1つの発言:

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

違いを得るには本当に悪い方法です。 単純に使用します:

self.updated_at - self.created_at


26


`DateHelper`には、必要なものを提供する2つのメソッドがあります。

  1. * http://apidock.com/rails/ActionView/Helpers/DateHelper/time_ago_in_words [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. * http://apidock.com/rails/ActionView/Helpers/DateHelper/distance_of_time_in_words [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"


7


chronic_durationは数値の時間を読み取り可能に解析し、逆も同様です。


1


数秒から数日の範囲で重要な期間を表示したい場合は、代替方法があります(最高のパフォーマンスを発揮する必要はないため)。

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'

別の方法として、問題がない場合にのみ秒部分を削除することに興味があるかもしれません(別のアプローチも示しています)。

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

お役に立てば幸いです。


0


「distance_of_time_in_words」に問題がある

ヘルパーを追加するだけです:

 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

基本的に、数秒でデータを渡すだけです。


0


Railsにはビュー用の `DateHelper`があります。 それが正確にあなたが望むものではない場合、あなたはあなた自身を書かなければならないかもしれません。

@MladenJablanovićには、良いサンプルコードで回答があります。 ただし、サンプルのヒューマナイズメソッドを引き続きカスタマイズする必要がない場合は、これが出発点として適しています。

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

これにより、操作可能な値とユニット名の配列が得られます。

最初の要素がゼロ以外の場合、それは日数です。 週、月、年を表示するなど、複数の日を処理するコードを作成できます。 それ以外の場合は、先頭の「0」値を切り取り、次の2つを取ります。

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

コードはrubyの `while`ステートメントの使用さえ見つけます。