Как сгенерировать удобочитаемый временной диапазон с помощью ruby ​​on rails

Я пытаюсь найти лучший способ создать следующий вывод

<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

person csanz    schedule 09.11.2010    source источник


Ответы (6)


Если вам нужно что-то более «точное», чем 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"

О, одно замечание по вашему коду:

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

это действительно плохой способ понять разницу. Используйте просто:

self.updated_at - self.created_at
person Mladen Jablanović    schedule 09.11.2010
comment
просто интересно, почему это плохо? (self.created_at..self.updated_at).count спасибо за чистый ответ! - person csanz; 09.11.2010
comment
@csanz: .count в этом случае перебирает массив каждой секунды между двумя временными метками и подсчитывает их. Как если бы вы вычислили результат выражения 100-50, фактически посчитав все числа от 50 до 100. - person Mladen Jablanović; 09.11.2010
comment
Я предпочитаю методы DateHelper. Если вы собираетесь конвертировать его в английский язык, то вы, вероятно, не хотите объединять дни и секунды. Это искусственная точность. - person Mark Thomas; 09.11.2010
comment
Верно, но во многом зависит от контекста. Например, мне нужно было что-то подобное для отображения продолжительности каждого элемента в таблице. Сначала я попробовал DateHelper, но позже заменил его собственным методом, который печатает что-то вроде 5:30:25, выровненное по правому краю. Табличные данные намного читабельнее, чем нечеткие выражения из DH. - person Mladen Jablanović; 10.11.2010
comment
Это именно моя точка зрения. Бывают случаи, когда нужно использовать формат данных, в котором важна точность, а бывают случаи, когда нужно использовать разговорный английский, где слишком много точности мешает. - person Mark Thomas; 10.11.2010
comment
Я немного изменил это и добавил модуль для гибкого использования: /a> В частности, каждый раздел (секунды, минуты, часы, дни) является необязательным и используется во множественном числе для возврата строки, например 1 day 10 hours vs 1 days 10 hours, которую возвращает приведенный выше код. - person JonathanSimmons; 24.09.2015
comment
Этот код отличный, но он не работает более 1000 дней (86_400_000 секунд). humanize(86_400_001) => "0 days 0 hours 0 minutes 1 seconds" неправильно. Эту ошибку можно исправить, обновив последний элемент массива до [Float::INFINITY, :days]. - person Powers; 26.10.2015
comment
DateHelper хорош, но если вы хотите что-то настраиваемое, то ответ выше — хороший образец. Итак, вы можете избежать искусственной точности, просто взяв первые два элемента результирующего массива. Вы можете решить, как обрабатывать значения за слишком много дней. - person Marlin Pierce; 19.06.2018

В 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"
    
person Doug R    schedule 09.11.2010
comment
Обратите внимание, что distance_of_time_in_words поддерживает i18n и имеет двойной интерфейс: его можно использовать непосредственно в продолжительности: distance_of_time_in_words(6270.873) #=> environ 2 heures (на французском языке) - person Cyril Duchon-Doris; 26.02.2019

chronic_duration преобразует числовое время в удобочитаемое и наоборот

person usr    schedule 19.12.2010

Если вы хотите показать значительную продолжительность в диапазоне от секунд до дней, альтернативой может быть (поскольку он не должен работать лучше всего):

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

Надеюсь, это поможет.

person rosenfeld    schedule 23.06.2016

Есть проблема с 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

В основном просто передайте свои данные за считанные секунды.

person Denis    schedule 25.09.2015

В Rails есть DateHelper для представлений. Если это не совсем то, что вы хотите, вам, возможно, придется написать свой собственный.

У @Mladen Jablanović есть ответ с хорошим примером кода. Однако, если вы не против продолжить настройку примера метода очеловечивания, это может быть хорошей отправной точкой.

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.

person Marlin Pierce    schedule 19.06.2018