如何使用 ruby​​ on rails 生成人类可读的时间范围

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4136248/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 23:36:31  来源:igfitidea点击:

How to generate a human readable time range using ruby on rails

ruby-on-railsrubytime

提问by csanz

I'm trying to find the best way to generate the following output

我正在尝试找到生成以下输出的最佳方法

<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

I started this 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

Is there a more efficient way of doing this. It seems like a lot of redundant code for something super simple.

有没有更有效的方法来做到这一点。对于超级简单的事情来说,似乎有很多冗余代码。

Another use for this is:

另一个用途是:

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

The code for this is similar, but instead i use Time.now:

代码类似,但我使用 Time.now:

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

回答by Mladen Jablanovi?

If you need something more "precise" than distance_of_time_in_words, you can write something along these lines:

如果您需要比 更“精确”的东西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"

Oh, one remark on your code:

哦,对你的代码有一句话:

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

is reallybad way to get the difference. Use simply:

真正糟糕的方式来获得差异。简单地使用:

self.updated_at - self.created_at

回答by Doug R

There are two methods in DateHelperthat might give you what you want:

有两种方法DateHelper可能会给你你想要的:

  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"
    
  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"
    

回答by usr

chronic_durationparses numeric time to readable and vice versa

chronic_duration将数字时间解析为可读,反之亦然

回答by rosenfeld

If you want to show significant durations in the seconds to days range, an alternative would be (as it doesn't have to perform the best):

如果您想在几秒到几天的范围内显示重要的持续时间,另一种方法是(因为它不必表现最佳):

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'

Alternatively you may be only interested in stripping the seconds part when it doesn't matter (also demonstrating another approach):

或者,您可能只对在无关紧要的情况下剥离秒部分感兴趣(也演示了另一种方法):

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

Hope that helps.

希望有帮助。

回答by Marlin Pierce

Rails has a DateHelperfor views. If that is not exactly what you want, you may have to write your own.

Rails 有一个DateHelperfor 视图。如果这不是您想要的,您可能需要自己编写。

@Mladen Jablanovi? has an answer with good sample code. However, if you don't mind continuing to customize a sample humanize method, this might be a good starting point.

@姆拉登·贾布拉诺维?有一个很好的示例代码的答案。但是,如果您不介意继续自定义示例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

This gives you an array of values and unit names that you can manipulate.

这为您提供了一组可以操作的值和单位名称。

If the first element is non-zero, it is the number of days. You may want to write code to handle multiple days, like showing weeks, months, and years. Otherwise, trim off the leading 0values, and take the next two.

如果第一个元素不为零,则为天数。您可能希望编写代码来处理多天,例如显示周、月和年。否则,修剪前导0值,并取下两个。

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

The code even finds use for a ruby whilestatement.

该代码甚至可以用于 rubywhile语句。

回答by Denis

There is problem with distance_of_time_in_wordsif u ll pass there 1 hour 30 minit ll return about 2 hours

存在与问题distance_of_time_in_words如果u会通过有1小时30分钟,它会返回约2小时

Simply add in 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

Basically just pass your data in seconds.

基本上只需在几秒钟内传递您的数据。