I what to check a time with a case statement. How to do?
Use ranges:
case time
when (Time.now - 60)..(Time.now) then puts 'within the last minute'
when (Time.now - 3600)..(Time.now) then puts 'within the last hour'
end
Ranges work with all sorts of values. You can use Dates too:
case date
when (Date.today - 1)..(Date.today) then puts 'less than a day ago'
when (Date.today - 30)..(Date.today) then puts 'less than a month ago'
end
Update: Ruby 1.9 broke Time ranges, so that example works only in Ruby 1.8.7. The Date example works in both versions though. In 1.9 you can use this code to match a Time:
case time.to_i
when ((Time.now - 60).to_i)..(Time.now.to_i) then puts 'within the last minute'
when ((Time.now - 3600).to_i)..(Time.now.to_i) then puts 'within the last hour'
end
Just use the version that doesn't have a defined variable at the top...
t = event.time # arbitrary example.
case
when t <= Time.now
# Event is in the past.
else
# Event is in the future.
end
Related
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
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
ActiveSupport Core Extensions
DateAndTime::Calculations
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.
I will construct a method relative_time that has two arguments:
date_time an instance of DateTime that specifies a time in the past; and
time_unit, one of :SECONDS, :MINUTES, :HOURS, :DAYS, :WEEKS, :MONTHS or :YEARS, specifying the unit of time for which the difference in time between the current time and date_time is 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#>>.
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"
I've been attempting to output either Morning or Afternoon depending on the time of the day. I turned the time into a string, and tried to compare against it. Here's what I have:
t = Time.now
# => 2016-05-11 07:18:10 -0500
if t.to_s >= '12:00'
'Good afternoon'
else
'Good morning'
end
# => "Good afternoon"
It defaults to "Good afternoon". Why is this? Is it because Ruby sets up time in a 24 hour clock? Or is it something within the coding?
You don't need any string manipulation:
t = Time.now
# => 2016-05-11 20:26:11 +0800
t.hour
# => 20
Just compare its hour (an integer) with 12.
You are comparing strings - which won't give you the results you expect.
Time.now.to_s outputs a string like: "2016-05-11 13:27:43 +0100". When you compare it to "12:00" that is a comparison of the letters in the string, and not the time they represent.
Try this instead:
t = Time.now
if t.strftime('%P') == 'pm'
'Good afternoon'
else
'Good morning'
end
Documentation for strftime: http://ruby-doc.org/core-2.3.1/Time.html#method-i-strftime
require 'time'
t = Time.new
puts "Good %s" % [(t.to_i/43199).even? ? "morning" : "afternoon"]
Good morning
t += 43199
puts "Good %s" % [(t.to_i/43199).even? ? "morning" : "afternoon"]
Good afternoon
Note: 43199 = 12*60*60/2 - 1
Just sayin'.
I have a method that is basically a loop and it calls itself at the end each time. What is the best way for the method to not call itself when the date reaches a certain point? Each iteration through adds 1 day and basically processes stats for that day. It looks like the below:
def loop(start_day)
date = start_day
#do a bunch of stuff
date = date +1.day
if date > Time.now
puts "loop should be over"
end
loop(date)
end
Each iteration through adds 1 day
That's not true for the code you've posted. In your code you add 1 day to the start date once and then you keep processing the same date over and over again because you recurse on the old date (start_date), not the incremented date (date).
What is the best way for the method to not call itself when the date reaches a certain point?
Just put the recursive call inside an if or, in this case, inside of the else of the if that you already have.
Since you set date to start_date immediately, it seems there's no point in having both. Here's the more canonical form of doing recursion:
def loop(date)
return if date > Time.now
#do a bunch of stuff
loop(date + 1.day)
end
Update: If it's not obvious to you that recursion isn't necessary here, in real life, it would make more sense to do something like this:
def process_from(date)
while date <= Time.now
# Process date
date += 1.day
end
end
What about this?
def loop(start_day)
return "loop should be over" if start_day >= Time.now
#...
loop(modified_date)
end
or...
def loop(start_day)
date = start_day.dup
time = Time.now
date += 1.day while date <= time
'loop should be over'
end
It seems like you want to iterate over all days from starting date to today. Then maybe this is even more simple:
def map_date(date)
(date.to_date..Date.today).map do |d|
d.strftime("Today is %A")
end
end
You must have base case (stop case) for recursive function;
example:
def loop(date)
if date == certain_point
return [something_for_stop]
end
loop(date - 1)
end
If I've got a time object:
t = Time.now
and I want to know if that time is AM or PM, right now the only way I can figure to do this is:
t.strftime("%p") == "PM"
Now, that %p is getting interpolated from something, right? Is there another way to get to it?
I ask because I'm doing some time formatting where I want to display a time range like:
"9:00 AM - 5:00 PM"
"4:00 - 5:30 PM"
"10:15 - 11:45 AM"
Right now I have to do this checking the string value of strftime, but I'd prefer to write something like:
if start_time.am? && end_time.pm? || start_time.pm? && end_time.am?
...instead of the much more verbose strftime string comparisons I'm doing now.
Based on http://www.ruby-doc.org/core-1.9.3/Time.html, I do not believe there is any other way. You could monkey-patch Time to save you some tedious strftime, however:
class Time
def meridian
self.strftime('%p')
end
def am?
self.meridian == 'AM'
end
def pm?
self.meridian == 'PM'
end
end
There isn't anything as nice as time.am? but you can use time.hour < 12 instead.
class Time
def am?
self.hour.in? (0..12)
end
def pm?
self.hour.in? (13..24)
end
end
Is there a way to iterate over a Time range in Ruby, and set the delta?
Here is an idea of what I would like to do:
for hour in (start_time..end_time, hour)
hour #=> Time object set to hour
end
You can iterate over the Time objects, but it returns every second between the two. What I really need is a way to set the offset or delta (such as minute, hour, etc.)
Is this built in to Ruby, or is there a decent plugin available?
Prior to 1.9, you could use Range#step:
(start_time..end_time).step(3600) do |hour|
# ...
end
However, this strategy is quite slow since it would call Time#succ 3600 times. Instead,
as pointed out by dolzenko in his answer, a more efficient solution is to use a simple loop:
hour = start_time
while hour < end_time
# ...
hour += 3600
end
If you're using Rails you can replace 3600 with 1.hour, which is significantly more readable.
If your start_time and end_time are actually instances of Time class then the solution with using the Range#step would be extremely inefficient since it would iterate over every second in this range with Time#succ. If you convert your times to integers the simple addition will be used but this way you will end up with something like:
(start_time.to_i..end_time.to_i).step(3600) do |hour|
hour = Time.at(hour)
# ...
end
But this also can be done with simpler and more efficient (i.e. without all the type conversions) loop:
hour = start_time
begin
# ...
end while (hour += 3600) < end_time
Range#step method is very slow in this case. Use begin..end while, as dolzenko posted here.
You can define a new method:
def time_iterate(start_time, end_time, step, &block)
begin
yield(start_time)
end while (start_time += step) <= end_time
end
then,
start_time = Time.parse("2010/1/1")
end_time = Time.parse("2010/1/31")
time_iterate(start_time, end_time, 1.hour) do |t|
puts t
end
if in rails.