как рассчитать относительное время в рубине?
Как рассчитать 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 (а неDateTime
instance) в качестве первого аргумента. Однако это не упростило бы метод, поскольку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"