Calculate how many days since creation - ruby-on-rails

Let's say a record has a created_at timestamp of Fri, 16 Feb 2018 18:40:53 UTC +00:00 and I want to find out the amount of days that have passed from the current time. How could I achieve this?
Here is what I've tried:
(Fri, 16 Feb 2018 18:40:53 UTC +00:00 - Time.now).to_i
But that does not work.

Almost, but Ruby doesn't understand your way of writing a datetime.
require 'date'
(DateTime.now - DateTime.new(2018, 2, 16, 18, 40, 53, 0)).to_i

In rails
If you get the timestamp for the model User from the database (not a timestamp string to parse):
seconds_from_creation = Date.today.to_time - user.created_at.to_time
Or directly convert to days (to_i rounds to integer, check if is suitable for you or customise):
((Date.today.to_time - user.created_at.to_time)/(24*60*60)).to_i
In a view you could use the following, which returns a string :
time_ago_in_words user.created_at
# => 10 days (in my case)

You can use
require 'time'
created_at = 'Fri, 16 Feb 2018 18:40:53 UTC +00:00'
(Time.now - Time.parse(created_at)).to_i / 86400

Related

Given a timestamp, how to determine how many weeks ago?

I'm looking to return the number of weeks since a user was created in my app..
My model is User.rb (id, created_at)
Given user.created_at:
2.4.0 :008 > user.created_at
=> Mon, 14 Aug 2017 15:51:23 UTC +00:00
How can I do something like: user.created_at.weeks_ago where it returns some integer which represents the number of weeks since the user was created?
If you just want to build a human-friendly string, I believe time_ago_in_words is what you are after.
https://apidock.com/rails/ActionView/Helpers/DateHelper/time_ago_in_words
Otherwise, I would resort to something like the following:
(Time.new - user.created_at) / 1.week
If you want a more elegant solution, Subtract dates in Ruby and get the difference in minutes mentions the Time Difference gem for Ruby.
e.g.
TimeDifference.between(user.created_at.to_time, Time.now).in_weeks
I would use:
Time.current
=> Wed, 20 Sep 2017 03:56:15 UTC +00:00
Then
((Time.current - person.created_at)/604800).to_i
the subtraction gives you the number of seconds then divide it by 604800 which is the number of seconds in a week.

Ruby group_by date every 7 days

How can I sort a collection of records by week start and end date? After looking at the strf docs the closest I could get was to group by the week number
weeks = my_data.group_by{|x| x.created_at.strftime('%U %Y') }
{"34 2015"=>
[#<DoctorRecommendation:0x007fd663196ea0
created_at: Tue, 25 Aug 2015 22:29:44 UTC +00:00,
patient_profile_id: 104,],
"33 2015"=>
[#<DoctorRecommendation:0x007fd663194ce0
created_at: Thu, 20 Aug 2015 13:41:37 UTC +00:00,
patient_profile_id: 21,,
#<DoctorRecommendation:0x007fd66319f500
created_at: Tue, 18 Aug 2015 02:47:30 UTC +00:00,
patient_profile_id: 61,
#<DoctorRecommendation:0x007fd66319db38
created_at: Tue, 18 Aug 2015 02:47:29 UTC +00:00,
patient_profile_id: 85,
]
}
I'd rather have the keys be 8/10/2015 - 8/16/2015 and 8/17/2015 - 8/23/2015
There are a couple of issues with this approach. First is that I haven't been able to convert it back to a proper date (tried Date.parse("34 2015")) but received an ArgumentError: invalid date. The second issue with this approach is that certain weeks would be omitted if there were no records.
You can use Rails' date extension to get the beginning and ending of the week (configurable), and use
that to create your range:
weeks = my_data.group_by do |x|
date = x.created_at
left = date.beginning_of_week.strftime('%-m/%-d/%Y')
right = date.end_of_week.strftime('%-m/%-d/%Y')
"#{left} - #{right}"
end
Here's code that does what you want, but with a lot of date parsing. Someone better at Ruby could probably optimize this.
my_data.group_by do |x|
date = Date.parse(x.created_at.strftime("%F"))
monday = date - date.strftime("%u").to_i
sunday = monday + 7
"#{monday.strftime("%F")} - #{sunday.strftime("%F")}"
end
My other suggestion is to query the minimum created_at date and today, generate all of the weeks in between with ruby very quickly, and then do queries. This is slower if you're getting all the data, but much faster if you only need a few of the weeks.

Daylight Saving Time start and end dates in Ruby/Rails

I'm working on a Rails app where I need to find the Daylight Saving Time start and end dates given a specific offset or timezone.
I basically save in my database the timezone offset received from a user's browser( "+3", "-5") and I want to modify it when it changes because of daylight saving time.
I know Time instance variables have the dst? and isdst methods which return true or false if the date stored in them is in the daylight saving time or not.
> Time.new.isdst
=> true
But using this to find the Daylight Saving Time beginning and end dates would take too many resources and I also have to do it for each timezone offset I have.
I would like to know a better way of doing this.
Ok, building on what you've said and #dhouty's answer:
You want to be able to feed in an offset and get a set of dates for knowing if there is a DST offset or not. I would recommend ending up with a range made of two DateTime objects, as that is easily used for many purposes in Rails...
require 'tzinfo'
def make_dst_range(offset)
if dst_end = ActiveSupport::TimeZone[offset].tzinfo.current_period.local_end
dst_start = ActiveSupport::TimeZone[offset].tzinfo.current_period.local_start
dst_range = dst_start..dst_end
else
dst_range = nil
end
end
Now you have a method that can do more than just take an offset thanks to the sugar that comes with ActiveSupport. You can do things like:
make_dst_range(-8)
#=> Sun, 08 Mar 2015 03:00:00 +0000..Sun, 01 Nov 2015 02:00:00 +0000
make_dst_range('America/Detroit')
#=> Sun, 08 Mar 2015 03:00:00 +0000..Sun, 01 Nov 2015 02:00:00 +0000
make_dst_range('America/Phoenix')
#=> nil #returns nil because Phoenix does not observe DST
my_range = make_dst_range(-8)
#=> Sun, 08 Mar 2015 03:00:00 +0000..Sun, 01 Nov 2015 02:00:00 +0000
Today happens to be August 29th so:
my_range.cover?(Date.today)
#=> true
my_range.cover?(Date.today + 70)
#=> false
my_range.first
#=> Sun, 08 Mar 2015 03:00:00 +0000
#note that this is a DateTime object. If you want to print it use:
my_range.first.to_s
#=> "2015-03-08T03:00:00+00:00"
my_range.last.to_s
#=> "2015-11-01T02:00:00+00:00"
ActiveSupport gives you all sorts of goodies for display:
my_range.first.to_formatted_s(:short)
#=> "08 Mar 03:00"
my_range.first.to_formatted_s(:long)
#=> "March 08, 2015 03:00"
my_range.first.strftime('%B %d %Y')
#=> "March 08 2015"
As you can see it's completely doable with just the offset, but as I said, offset doesn't tell you everything, so you might want to grab their actual time zone and store that as a string since the method will happily accept that string and still give you the date range. Even if you are just getting the time offset between your zone and theirs, you can easily figure correct that to the UTC offset:
my_offset = -8
their_offset = -3
utc_offset = my_offset + their_offset
What you are probably looking for is TZInfo::TimezonePeriod. Specifically, the methods local_start/utc_start and local_end/utc_end.
Given a timezone offset, you can get a TZInfo::TimezonePeriod object with
ActiveSupport::TimeZone[-8].tzinfo.current_period
Or if you have a timezone name, you can also get a TZInfo::TimezonePeriod object with
ActiveSupport::TimeZone['America/Los_Angeles'].tzinfo.current_period

rails' utc_to_local and daylight savings time

> e = Event.first
> e.registration_start_utc #registration_start_utc is a datetime column
=> Sat, 23 Oct 2010 06:38:00 UTC +00:00
> e.registration_start_utc.utc?
=> true
> ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(e.registration_start_utc)
=> Sat, 23 Oct 2010 02:38:00 UTC +00:00
2 questions about this:
1) Why is that last output showing "UTC" -- the hour got converted (6 => 2) but it still says UTC. Why not EST/EDT?
2) What happens after daylight savings time switches over and the offset for New York moves from -4 to -5? The value in the DB doesn't change so my only conclusion is that my app will start showing "1:38" everywhere instead of the correct 2:38?
I'm mostly concerned with #2 here. #1 is more of a curiosity.
Thanks!
2) utc_to_local uses the date to determine which offset is correct, so the output will always be the same for a given date.
You can test for that like this:
t = Time.utc(2011,3, 14, 12)
# => 2011-03-14 12:00:00 UTC
t2 = Time.utc(2011,3, 11, 12)
# => 2011-03-11 12:00:00 UTC
ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(t)
# => 2011-03-14 08:00:00 UTC
ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(t2)
# => 2011-03-14 07:00:00 UTC
1) It doesn't seem right to me either. My guess is that they are interested only in the actual value of the hour, minutes, etc... and disregard the timezone.
In any case, you might be better off using:
e.registration_start_utc.in_time_zone("Eastern Time (US & Canada)")
See also this question I just asked...

Why does this rails query behave differently depending on timezone?

I have a rails time-based query which has some odd timezone sensitive behaviour, even though as far as I know I'm using UTC. In a nutshell, these queries give different answers:
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours).gmtime]).length
=> 279
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours)]).length
=> 280
Where the DB actually does contain one model created in the last hour, and the total number of models is 280. So only the first query is correct.
However, in environment.rb I have:
config.time_zone = 'UTC'
The system time zone (as reported by 'date') is BST (which is GMT+1) - so somehow this winds up getting treated as UTC and breaking queries.
This is causing me all sorts of problems as I need to parameterise the query passing in different times to an action (which are then converted using Time.parse()), and even though I send in UTC times, this 'off by one hour' DST issue crops a lot. Even using '.gmtime()' doesn't always seem to fix it.
Obviously the difference is caused somehow by an implicit conversion somewhere resulting in BST being incorrectly treated as UTC, but why? Doesn't rails store the timestamps in UTC? Isn't the Time class timezone aware? I am using Rails 2.2.2
So what is going on here - and what is the safe way to program around it?
edit, some additional info to show what the DB and Time class are doing:
>> Model.find(:last).created_at
=> Tue, 11 Aug 2009 20:31:07 UTC +00:00
>> Time.now
=> Tue Aug 11 22:00:18 +0100 2009
>> Time.now.gmtime
=> Tue Aug 11 21:00:22 UTC 2009
The Time class isn't directly aware of your configured timezone. Rails 2.1 added a bunch of timezone support, but Time will still act upon your local timezone. This is why Time.now returns a BST time.
What you likely want is to interact with Time.zone. You can call methods on this like you would the Time class itself but it will return it in the specified time zone.
Time.zone.now # => Tue, 11 Aug 2009 21:31:45 UTC +00:00
Time.zone.parse("2:30 PM Aug 23, 2009") # => Sun, 23 Aug 2009 14:30:00 UTC +00:00
Another thing you have to be careful with is if you ever do queries on the database where you are comparing times, but sure to use the UTC time (even if you have a different time zone specified) because Rails always stores UTC in the database.
Item.all(:conditions => ["published_at <= ?", Time.now.utc])
Also, instead of Time.now-1.hour do 1.hour.ago. It is easier to read and Rails will automatically use the configured timezone.
The TimeZone you need to set is UK, this will automatically handle BST
Time.zone = 'UK'
Time.zone.now
=> Sun, 17 Oct 2010 02:09:54 BST +01:00
start_date_format = DateTime.strptime(#start_date, date_format)
start_date_format_with_hour =
DateTime.strptime((start_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
end_date_format = DateTime.strptime(#end_date, date_format)
end_date_format_with_hour = DateTime.strptime((end_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
#filters_date = "invoices.created_at >= ? AND invoices.created_at < ?", start_date_format_with_hour, end_date_format_with_hour

Resources