ruby-on-rails - 解答 - ruby on rails5




ruby on railsを使って人間が読める時間範囲を生成する方法 (4)

私は次の出力を生成する最良の方法を見つけることを試みています

<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

私はこのコードを開始した

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

これを行うより効率的な方法がありますか? これは、何かスーパーのための冗長なコードのような単純です。

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

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

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

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

Railsには、 DateHelper用のDateHelperがあります。 それがあなたの望むものではない場合は、あなた自身で書く必要があります。

@MladenJablanovićは良いサンプルコードで答えてくれました。 しかし、サンプルのhumanizeメソッドをカスタマイズし続けても構わない場合は、これが良い出発点かもしれません。

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ステートメントの使用法が見つかります。


distance_of_time_in_wordsよりも「正確な」ものが必要な場合は、次の行に何かを書くことができます:

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"

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

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

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

self.updated_at - self.created_at

DateHelperには、あなたが望むものを与える方法が2つあります:

  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"
    

distance_of_time_in_words問題があります。そこに1時間30分を渡すと約2時間戻ります

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

 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

基本的には秒単位でデータを渡すだけです。







time