"Ago" date/time functions in Ruby/Rails - ruby-on-rails

I was wondering if there's a way in Rails to calculate time stamp like - half a minute ago, 2 minute ago, 1 day ago etc. Something like twitter real time date stamp.
I want to know if Ruby/Rails has a built-in function for such date-time conversion?

You can use:
10.minutes.ago
2.days.since
Or in your views you have the helpers:
distance_of_time_in_words(from_time, to_time)
time_ago_in_words(from_time)
Check the API for details and more options.

You can use available methods to get the time in past or future using ago, since alias for from_now and many available methods
Time.current
#=> Tue, 20 Sep 2016 15:03:30 UTC +00:00
2.minutes.ago
#=> Tue, 20 Sep 2016 15:01:30 UTC +00:00
2.minutes.since
#=> Tue, 20 Sep 2016 15:05:30 UTC +00:00
1.month.ago
#=> Sat, 20 Aug 2016 15:03:30 UTC +00:00
1.year.since
#=> Wed, 20 Sep 2017 15:03:30 UTC +00:00
Check all the available methods in Time class

distance_of_time_in_words:
from_time = Time.now
distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds
time_ago_in_words:
time_ago_in_words(3.minutes.from_now) # => 3 minutes
time_ago_in_words(3.minutes.ago) # => 3 minutes
time_ago_in_words(Time.now - 15.hours) # => about 15 hours

Related

Get the difference in days between two dates Rails "Date"

What is the proper way to subtract or add dates in Rails?
I tried the intuitive way but got Rational:
irb(main):089:0> Date.today.increase_by("3 days")
=> Sun, 19 May 2019
irb(main):090:0> Date.today
=> Thu, 16 May 2019
irb(main):091:0> Date.today.increase_by("3 days") - Date.today
=> (3/1)
Disclaimer: Please note that I am new to Ruby and Rails as well. 2 months of experience so far :)
You can use
Date.today # Thu, 16 May 2019
Date.today + 3 # Thu, 19 May 2019
Date.today - 3 # Thu, 13 May 2019
For Difference
Date.today + 3 # Thu, 19 May 2019
Date.today - 3 # Thu, 13 May 2019
(d1 - d2).to_i # 6 (Days)

Converting time zone of Rails ActiveRecord timestamps

I extended the Time class in my Rails projects so I can easily get the time in NYC:
/lib/extensions.rb .
class Time
# Get NYC time:
def nyc
self.in_time_zone('Eastern Time (US & Canada)')
end
end
Testing it out, looks good:
time_a = Time.now.utc.nyc
=> Sun, 21 Apr 2019 18:42:12 EDT -04:00
The problem is when I pull timestamps from the DB:
time_b = object.created_at.in_time_zone('Eastern Time (US & Canada)')
=> Sun, 21 Apr 2019 17:22:04 EDT -04:00
time_c = object.created_at.nyc
=> Sun, 21 Apr 2019 17:22:04 UTC +00:00
Super confused. Converting the timestamp to EDT works when I use in_time_zone in the console, but not when I use the extension? Even though my extension method works on Time objects I create in console? What's happening here?
(Note: Time instances in Rails are in fact instances of ActiveSupport::TimeWithZone. "TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable." - ActiveSupportTimeWithZone)
you would need to patch ActiveSupport::TimeWithZone instead of Time, e.g.
class ActiveSupport::TimeWithZone
def nyc
in_time_zone('Eastern Time (US & Canada)')
end
end
Time.zone.now.nyc # => Mon, 22 Apr 2019 06:44:41 EDT -04:00
User.last.created_at.nyc # => Sun, 21 Apr 2019 13:34:45 EDT -04:00
https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html
(edit: I previously said "DateTime" instead of "ActiveSupport::TimeWithZone")

One year and one week time scope

I'm trying to notify my customers that their subscription is about to expire. This is how I look for those users to notify them. They will be billed on the date they subscribed + 1.year:
User.where(subscribed_at: 53.weeks.ago.beginning_of_day..53.weeks.ago.beginning_of_day)
My question is will this create issue with leap years? or is there a better way to do this?
Rails provides Time#advance for "precise Time calculations":
Time.now.advance(years: -1, weeks: -1)
#=> 2013-10-08 17:54:36 +0200
Time#all_day returns a whole day's range:
Time.now.advance(years: -1, weeks: -1).all_day
#=> 2013-10-08 00:00:00 +0200..2013-10-08 23:59:59 +0200
I think you should use 1.year.ago since 52.weeks.ago is not equal to a full year (52*7 = 364 days).
The usage of 1.year.ago would be better because it actually changes the year field of the DateTime, nothing else:
1.9.3p489 :005 > 2.year.ago
# => Mon, 15 Oct 2012 11:51:44 EDT -04:00
1.9.3p489 :006 > 5.year.ago
# => Thu, 15 Oct 2009 11:51:47 EDT -04:00
1.9.3p489 :007 > 999.year.ago
# => Sun, 15 Oct 1015 11:51:50 LMT -04:56 # For some reason the TimeZone changed!
In your case, I would use the following logic: NOPE NOPE, I would use #Stefan's answer!
range = (1.year.ago-1.week).beginning_of_day..(1.year.ago-1.week).end_of_day
User.where(subscribed_at: range)

Finding days between 2 days in Ruby on Rails

I am facing some problem in finding the days between 2 dates.
The scenario is as follow :
time = Time.new
enddate_timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
startdate = #logInfo.updated_at #here updated_at is the column in the db .
What is the best way to find the days ?
Post.where(["date(created_at) BETWEEN ? AND ?", Date.yesterday, Date.tomorrow]
More details: http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where
There are several possible solutions. A possibility is to create a Range with the dates, then convert the range into an array
# set the boundaries
today = Time.current
past = 5.days.ago
Note that both boundaries are time instances. We should cast them into dates. I used time(s) because your column is a time.
range = past.to_date..today.to_date
# => Sun, 29 Dec 2013..Fri, 03 Jan 2014
Use to_a to expand the range getting all the days
range.to_a
# => [Sun, 29 Dec 2013, Mon, 30 Dec 2013, Tue, 31 Dec 2013, Wed, 01 Jan 2014, Thu, 02 Jan 2014, Fri, 03 Jan 2014]
range.count
# => 6
You can also enumerate them
range.each { |day| puts day.day }
29
30
31
1
2
3
now = Time.now
future = Time.now + 100 days
while now < future
now = now + 1.day
puts now
end
This will give you the dates, not the days count.
(startdate.beginning_of_day..enddate_timestamp.to_time.beginning_of_day).step(1.day) do |day|
puts day
end
P.S: Performance wise it's not good.

Rails Time class has a TimeZone Bug. Can this be confirmed?

Here goes :
Time.zone.now => "Eastern Time (US & Canada)"
Time.zone.now => Wed, 15 Aug 2012 06:05:37 EDT -04:00
Time.zone.now + 39.years => Tue, 15 Aug 2051 06:06:03 EST -05:00
And so you have it, the end of our fabled Eastern Daylight Time has been prophesied by Ruby on Rails to end in the year 2051.
Also works for any other TimeZone changing area.
Time.zone
=> "Pacific Time (US & Canada)"
1.9.2p180 :003 > Time.zone.now
=> Wed, 15 Aug 2012 03:08:57 PDT -07:00
1.9.2p180 :004 > Time.zone.now + 39.years
=> Tue, 15 Aug 2051 03:08:57 PST -08:00
This exists in Rails 3.0 and in Rails 3.2.6
Yes, it looks like a bug. It's not Rails, however, it's the Ruby Time class. It has problems with times after 2038.
For example, with Ruby 1.8.7:
> Time.local(2037,8,16,9,30,15)
=> Sun Aug 16 09:30:15 -0400 2037
>
> Time.local(2038,8,16,9,30,15)
=> Mon Aug 16 09:30:15 -0500 2038
JRuby 1.6.7.2 - for instance - does not have this problem:
> Time.local(2038,8,16,9,30,15)
=> Mon Aug 16 09:30:15 -0400 2038
Note that, on MRI Ruby on 64-bit systems, the ActiveSupport time extension which supports the addition of durations ultimately calls Time.local or Time.utc via this method in active_support/core_ext/time/calculations.rb:
# Returns a new Time if requested year can be accommodated by Ruby's Time class
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
# otherwise returns a DateTime
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
rescue
offset = utc_or_local.to_sym == :local ? ::DateTime.local_offset : 0
::DateTime.civil(year, month, day, hour, min, sec, offset)
end
I guess the issue is that for years >= 2038, they were expecting an overflow exception and for DateTime to be used instead. On 64-bit systems, this doesn't happen.
UPDATE: This analysis is incorrect for Ruby 1.9.2+. Time.local works as expected, but the original problem still occurs.

Resources