как рассчитать относительное время в рубине?

Как рассчитать relative_time для секунд, минут, дней, месяцев и года в рубине?

Given a DateTime object, implement relative time
d = DateTime.now
d.relative_time # few seconds ago

Possible outputs
# few seconds ago
# few minutes ago - till 3 minutes
# x minutes ago (here x starts from 3 till 59)
# x hours ago (x starts from 1 - 24) and so on for the below outputs
# x days ago
# x weeks ago
# x months ago
# x years ago

как это реализовать в рубине? пожалуйста помоги

4 ответа

Использовать дату GNU

В ядре и стандартной библиотеке Ruby нет удобных методов для того типа вывода, который вам нужен, поэтому вам придется реализовать свою собственную логику. Без расширений ActiveSupport вам лучше обратиться к утилите даты GNU (а не BSD). Например:

now       = Time.now
last_week = %x(date -d "#{now} - 1 week")

Если вы передадите флаг формата дате GNU, например +"%s", команда date предоставит дату в секундах с начала эпохи. Это позволяет вам создавать операторы case для любых единиц времени, которые вы хотите сравнить, например, более 360 секунд назад или менее 86400.

Пожалуйста, ознакомьтесь с форматами ввода даты, определенными GNU coreutils, чтобы получить более полное представление о том, как использовать различные параметры строки даты.

Использование расширений Rails

Если вы хотите установить и вам требуется пара расширений Rails в своем коде, вы можете использовать ActionView::Helpers::DateHelper#distance_of_time_in_words. Например:

require "active_support/core_ext/integer/time"
require "active_support/core_ext/numeric/time"
require "action_view"

include ActionView::Helpers::DateHelper

2.weeks.ago
#=> 2020-07-12 09:34:20.178526 -0400

distance_of_time_in_words Date.current, 1.month.ago
#=> "30 days"

sprintf "%s ago", distance_of_time_in_words(Time.now, 1.hour.ago)
#=> "about 1 hour ago"

Некоторая комбинация различных методов #ago в сочетании с расчетами даты / времени и вызовами помощника по расстоянию будет делать то, что вы хотите, но вам придется реализовать некоторую собственную логику, если вы хотите преобразовать результаты по-другому (например, указав что вы хотите выводить данные о больших расстояниях в виде недель или месяцев, а не лет).

Другие драгоценные камни

Конечно, есть и другие драгоценные камни Ruby, которые могут помочь. В Ruby Toolbox есть несколько полезных инструментов для определения разницы во времени, но ни один из них не поддерживается в хорошем состоянии. Будьте осторожны с покупателем, и ваш пробег может отличаться.

Смотрите также

Прежде всего, DateTime предназначен для исторических дат, тогда как Timeиспользуется для текущих дат, поэтому вам, вероятно, понадобится последнее. (см. Когда следует использовать DateTime, а когда - время?)

Учитывая момент времени 45 минут назад: (45 × 60 = 2700)

t = Time.now - 2700

Вы можете узнать разницу в секундах с текущим временем через Time#-:

Time.now - t
#=> 2701.360482

# wait 10 seconds

Time.now - t
#=> 2711.148882

Эту разницу можно использовать в caseвыражение вместе с диапазонами для генерации соответствующей строки продолжительности:

diff = Time.now - t

case diff
when 0...60       then "few seconds ago"
when 60...180     then "few minutes ago"
when 180...3600   then "#{diff.div(60)} minutes ago"
when 3600...7200  then "1 hour ago"
when 7200...86400 then "#{diff.div(3600)} hours ago"
# ...
end

Возможно, вам придется скорректировать числа, но это должно помочь вам.

Я построю метод relative_time у которого есть два аргумента:

  • date_timeэкземпляр DateTime, указывающий время в прошлом; а также
  • time_unit, один из :SECONDS, :MINUTES, :HOURS, :DAYS, :WEEKS, :MONTHS или :YEARS, указав единицу времени, для которой разница во времени между текущим временем и date_time должно быть выражено.

Код

require 'date'

TIME_UNIT_TO_SECS = { SECONDS:1, MINUTES:60, HOURS:3600, DAYS:24*3600,
                      WEEKS: 7*24*3600 }
TIME_UNIT_LBLS    = { SECONDS:"seconds", MINUTES:"minutes", HOURS:"hours",
                      DAYS:"days", WEEKS: "weeks", MONTHS:"months",
                      YEARS: "years" }

def relative_time(date_time, time_unit)
  now = DateTime.now
    raise ArgumentError, "'date_time' cannot be in the future" if
      date_time > now

  v = case time_unit
      when :SECONDS, :MINUTES, :HOURS, :DAYS, :WEEKS
        (now.to_time.to_i-date_time.to_time.to_i)/
          TIME_UNIT_TO_SECS[time_unit]
      when :MONTHS
        0.step.find { |n| (date_time >> n) > now } -1
      when :YEARS
        0.step.find { |n| (date_time >> 12*n) > now } -1
      else
        raise ArgumentError, "Invalid value for 'time_unit'"
      end
  puts "#{v} #{TIME_UNIT_LBLS[time_unit]} ago"
end

Примеры

date_time = DateTime.parse("2020-5-20")

relative_time(date_time, :SECONDS)
5870901 seconds ago

relative_time(date_time, :MINUTES)
97848 minutes ago

relative_time(date_time, :HOURS)
1630 hours ago

relative_time(date_time, :DAYS)
67 days ago

relative_time(date_time, :WEEKS)
9 weeks ago

relative_time(date_time, :MONTHS)
2 months ago

relative_time(date_time, :YEARS)
0 years ago

Объяснение

Если time_unit равно :SECONDS, :MINUTES, :HOURS, :DAYS или :WEEKS Я просто вычисляю количество секунд, прошедших между date_timeи текущее время, и разделите это на количество секунд в данной единице времени. Например, еслиtime_unit равно :DAYS прошедшее время в секундах делится на 24*3600, потому что в день столько секунд.

Если time_unit равно :MONTHS, Я использую метод Date# >> (который наследуетсяDateTime), чтобы определить количество месяцев, прошедших с date_time до тех пор, пока не будет достигнуто время, которое позже текущего времени, затем вычтите 1.

Расчет аналогичен, если time_unit равно :YEARS: определить количество лет, прошедших с date_time до тех пор, пока не будет достигнуто время, которое позже текущего времени, затем вычтите 1.

Можно потребовать, чтобы пользователь ввел экземпляр Time (а неDateTimeinstance) в качестве первого аргумента. Однако это не упростило бы метод, посколькуTime экземпляр необходимо преобразовать в DateTime пример, когда time_unit равно :MONTH или :YEAR, чтобы использовать метод Date#>>.

Следующая функция использует возможности Date#<< для представления прошлых дат относительно сегодняшнего дня.

      def natural_past_date(date)
  date_today = Date.today
  date_then = Date.parse(date)

  if (date_today << 12) >= date_then
    dy = (1..100).detect { (date_today << (_1.next * 12)) < date_then }
    "#{dy} year#{dy > 1 ? 's' : ''} ago"
  elsif (date_today << 1) >= date_then
    dm = (1..12).detect { (date_today << _1.next) < date_then }
    "#{dm} month#{dm > 1 ? 's' : ''} ago"
  elsif (dw = (dd = (date_today - date_then).to_i)/7) > 0
    "#{dw} week#{dw > 1 ? 's' : ''} ago"
  else
    if (2...7) === dd
      "#{dd} days ago"
    elsif (1...2) === dd
      'yesterday'
    else
      'today'
    end
  end
end

Некоторые примеры использования функции:

      Date.today
=> #<Date: 2022-03-16 ...

natural_past_date '2020/01/09'
=> "2 years ago"

natural_past_date '2021/09/09'
=> "6 months ago"

natural_past_date '2022/03/08'
=> "1 week ago"

natural_past_date '2022/03/14'
=> "2 days ago"

natural_past_date '2022/03/15'
=> "yesterday"

natural_past_date '2022/03/16'
=> "today"
Другие вопросы по тегам