'how to calculate relative time in ruby?
How to calculate relative_time for seconds,minutes, days, months and year in ruby?
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
how to implement this in ruby? please help
Solution 1:[1]
Use GNU Date
In Ruby's core and standard library, there are no convenience methods for the type of output you want, so you'd have to implement your own logic. Without ActiveSupport extensions, you'd be better off calling out to the GNU (not BSD) date utility. For example:
now = Time.now
last_week = %x(date -d "#{now} - 1 week")
If you pass a format flag to GNU date such as +"%s", the date command will provide the date as seconds since epoch. This allows you to create case statements for whatever units of time you want to compare, such whether it's more than 360 seconds ago or less than 86,400.
Please see the date input formats defined by GNU coreutils for a more comprehensive look at how to use the various date string options.
Using Rails Extensions
If you're willing to install and require a couple of Rails extensions in your code, you can use ActionView::Helpers::DateHelper#distance_of_time_in_words. For example:
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"
Some combination of the various #ago methods coupled with date/time calculations, and calls to the distance helper will do what you want, but you'll have to implement some of your own logic if you want to cast the results differently (e.g. specifying that you want the output of large distances as weeks or months instead of in years).
Other Gems
There are certainly other Ruby gems that could help. The Ruby Toolbox shows a number of gems for determining time differences, but none seem to be well-maintained. Caveat emptor, and your mileage may vary.
See Also
Solution 2:[2]
First of all, DateTime is intended for historical dates, whereas Time is used for current dates, so you probably want the latter. (see When should you use DateTime and when should you use Time?)
Given a time instance 45 minutes ago: (45 × 60 = 2,700)
t = Time.now - 2700
You can get the difference in seconds to the current time via Time#-:
Time.now - t
#=> 2701.360482
# wait 10 seconds
Time.now - t
#=> 2711.148882
This difference can be used in a case expression along with ranges to generate the corresponding duration string:
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
You might have to adjust the numbers, but that should get you going.
Solution 3:[3]
I will construct a method relative_time that has two arguments:
date_timean instance of DateTime that specifies a time in the past; andtime_unit, one of:SECONDS,:MINUTES,:HOURS,:DAYS,:WEEKS,:MONTHSor:YEARS, specifying the unit of time for which the difference in time between the current time anddate_timeis to be expressed.
Code
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
Examples
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
Explanation
If time_unit equals :SECONDS, :MINUTES, :HOURS, :DAYS or :WEEKS I simply compute the number of seconds elapsed between date_time and the current time, and divide that by the number of seconds per the given unit of time. For example, if time_unit equals :DAYS the elapsed time in seconds is divided by 24*3600, as there are that many seconds per day.
If time_unit equals :MONTHS, I use the method Date#>> (which is inherited by DateTime) to determine the number of months that elapse from date_time until a time is reached that is after the current time, then subtract 1.
The calculation is similar if time_unit equals :YEARS: determine the number of years that elapse from date_time until a time is reached that is after the current time, then subtract 1.
One could require the user to enter a Time instance (rather than a DateTime instance) as the first argument. That would not simplify the method, however, as the Time instance would have to be converted to a DateTime instance when time_unit equals :MONTH or :YEAR, to use the method Date#>>.
Solution 4:[4]
The following function uses the power of Date#<< to represent past dates relative to today.
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
Some sample usages of the function:
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"
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Stefan |
| Solution 3 | |
| Solution 4 | Judas Iscariot |
