如何使用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
如果你需要比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"
哦,你的代码有一句话:
(self.created_at..self.updated_at).count
是一个非常糟糕的方式来获得差异。 使用简单:
self.updated_at - self.created_at
DateHelper
中有两种方法可以为您提供所需的方法:
-
time_ago_in_words
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
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将数字时间解析为可读,反之亦然
如果您想在几秒到几天的范围内显示重要的持续时间,则可以选择(因为它不必执行最佳):
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(dhms)).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(dhms)[-parts.size..-1]) pairs.pop if pairs.size > 2 # do not report seconds when irrelevant pairs.flatten.join end
希望有所帮助。
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
基本上只需几秒钟即可传递数据。
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
值,然后取下两个。
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
语句。