Rails daylight savings issue while searching - ruby-on-rails

I am searching records in user model. The search attributes from_date, to_date will be used to searched records in user model based on created_at column.
User model : (id, name, created_at)
I have the following records in the database.
id, name, created_at
1 jd1 2013-09-04 18:01:57
2 jd2 2013-09-05 19:01:57
3 jd3 2013-09-05 23:01:57
When i am searching, between "2013-09-04".to_date(from_date) and "2013-09-05".to_date(to_date), only the first two records are being returned. The last one is not being returned. When i change the to_date to "2013-09-06" the last record is showing. This is the query i used.
date_range = from_date ... to_date + 1.day
scope :by_date, ->(date_range) {where(created_at: date_range)}
User.by_date(date_range)
What is wrong with the query? I think there is daylight time zone issue with it.

You need to cast your created_at fields as Date:
CAST(users.created_at AS DATE)
In the where clause:
where("CAST(users.created_at AS DATE) BETWEEN ? AND ?", date1, date2 )
In your case, with your scope:
scope :by_date, lambda{ |date_range| where("CAST(users.created_at AS DATE) BETWEEN ? AND ?", date_range.min, date_range.max ) }
Hope this helps!
Bonus: The short version of CAST, works with PostGre SQL:
scope :by_date, lambda{ |date_range| where("users.created_at::date BETWEEN ? AND ?", date_range.min, date_range.max ) }

Related

Selecting entries with a specific year and month from a Rails database

How can I select all database entries with a given date and month in a datetime? I tried this:
scope :with_year_and_month, ->(year, month) {
where("YEAR(created_at) = ? AND MONTH(created_at) = ?", month, year)
}
Now that would work perfectly fine in a MySQL database, but in an Sqlite database it fails because Sqlite hasn't implemented the MONTH() and YEAR() functions. The following works fine though in Sqlite:
scope :with_year_and_month, ->(year, month) {
where("strftime('%m', created_at) = ? AND strftime('%Y', created_at) = ?", "%02d" % month, year.to_s)
}
How would I do this in a database agnostic way?
I think you should let active record handle this, since it checks what database it's using and uses a driver for each database, the trick is to find a generic query to run, for me this is what I could come up with, might not be the best but I'm open for suggestions
scope :with_year_and_month, ->(year, month) {
where(created_at: Date.new(year,month,1)..Date.new(year,month,-1))
}
This will generate a between query
WHERE created_at BETWEEN '2015-02-01' AND '2015-02-28'
I think this should work with you
If created_at is DateTime then you should replace the Date with DateTime object so you don't miss the time between 2015-02-28 00:00:00 and 2015-02-28 23:59:59, but you'll need extra parameters for the time
scope :with_year_and_month, ->(year, month) {
where(created_at: DateTime.new(year,month,1)..DateTime.new(year,month,-1, -1, -1, -1))
}
of course you could create a small helper function to return those dates and shorten your method.
The result would be
Where created_at BETWEEN '2015-02-01 00:00:00' AND '2015-02-28 23:59:59'
Update:
So after a couple of comments, here's the final version
scope :with_year_and_month, ->(year, month) {
date = DateTime.new(year,month)
where(created_at: date...date.next_month)
}
The generated query would look like this
created_at >= '2015-02-01 00:00:00' AND created_at < '2015-03-01 00:00:00'

Querying active record objects from database based on created_at filters

I know that Ruby on rails stores all times in UTC (for created_at and updated_at fields) and when you fetch an active-record object from database, and ask RoR for it's date, it will convert it your configured (in environment.rb) Timezone and show you.
But my case is different. I am building a custom query. And I am adding a where clause manually to it. Where clause is such that: select * where created_at > [user entered date].
Now the problem that's arising is that the user entered date is in UTC - 7 and created_at is in UTC. So I can't really make it work. I could hardcode it like select * where created_at > [user-entered-date] 07:00:00 - but this created problem because of daylight savings, and also doesn't seem like a good solution.
This is not the only problem, the second problem is that when I print out the record.created_at, I am getting UTC date (perhaps because I build a custom query?), which also I don't want to manually (hardcode) convert to local time.
Here's my code for the query:
cond = EZ::Where::Condition.new
if !start_date.empty?
start_date = params[:filter][:start_date].to_date.to_s(:db)
cond.append "(registrations.created_at) >= '#{start_date} 07:00:00'" #Here!
end
if !end_date.empty?
end_date = params[:filter][:end_date].to_date
end_date = end_date + 1.day;
end_date = end_date.to_s(:db)
cond.append "(registrations.created_at) <= '#{end_date} 07:00:00'" #Here!
end
registrations = Registration.all(
:joins => [:event],
:select => 'registrations.id, registrations.first_name, registrations.last_name, registrations.company_name,
(registrations.created_at) AS reg_date, events.name AS evt_name, sum(fees) AS fees, code, events.id AS event_id',
:group => 'registrations.id',
:order => 'evt_name, events.id',
:conditions=> cond.to_sql
)
unless registrations.empty?
registrations.each_with_index do |registration, i|
sheet[ i, 3 ] = (DateTime.strptime(registration.reg_date, "%Y-%m-%d %H:%M:%S") - 7.hours).to_date #Here!
end
end
Try to use TimeWithZone and TimeZone
tz = ActiveSupport::TimeZone.new("Mountain Time (US & Canada)")
...
start_date = tz.local_to_utc(params[:filter][:start_date].to_time).to_s(:db)
...
sheet[ i, 3 ] = registration.reg_date.in_time_zone("Mountain Time (US & Canada)").to_date

Use active record to find a record by month and day, ignoring year and time

I have a model (Entries) with five years worth of records (one record per day). I need a method that, when passed a date object such as 2011-12-25 00:00:00, will show me ALL the records that have happened on 12/25 (querying against the :created_at column), regardless of the year or time that's passed.
RoR 3.0.9 / Ruby 1.9.2p290
You can use the MONTH and DAY values of mysql. Maybe something like:
Model.where("MONTH(created_at) = ? and DAY(created_at) = ?", somedate.month, somedate.day)
A general solution that should work with most SQL databases (include MySQL and PostgreSQL):
Entry.where('extract(month from created_at) = ? AND extract(day from created_at) = ?', d.month, d.day)
SQLite doesn't understand extract though so you'd have to use:
Entry.where("strftime('%m/%d', created_at) = ?", d.strftime('%m/%d'))
Assuming that you are using mysql:
User.where(["DAY(created_at) = ? AND MONTH(created_at) = ?", date.day, date.month])
Assume RDBMS is MySQL and you have form with combobox to select month and/or date_of_months, may be you could make a named_scope, for example :
scope :by_date_or_month, lambda { |date, month| {:conditions => ["DAYOFMONTH(created_at) = ? or MONTH(created_at) = ?", date, month]}}
Test from IRB :
Model.by_date_or_month(31,8)
Model.by_date_or_month(nil,8)

Get records where date.year == some_year in Rails

I have a date column in my database in a Rails 3.1 app, and I want to be able to get the records where the date's year matches a specific year.
I tried where(:date.year == year) but of course I got NoMethodError: undefined method 'year' for :date:Symbol. Is it possible to do this type of query?
You can use a scope to build something like:
scope :for_year, lambda {|date| where("date >= ? and date <= ?", "#{date.year}0101", "#{date.year}1231")}
In your Model:
scope :by_year, lambda { |year| where('extract(year from created_at) = ?', year) }
In your Controller:
#courses = Course.by_year(params[:year])
Jesse gave you, I think, the idea for the actual solution, but to explain why this failed - it's because it tried to evaluate ".year" as a method on the symbol you passed it: ":date".
The word :date is just a parameter to tell "where" which value it will later use to construct the SQL query to pass to the db.
It doesn't turn into the actual date of the record. But the ".year" will evaluate as you're passing it as a parameter, before anything has been done with the ":date" symbol.
Assuming your date format is: YYYY-MM-DD HH:MM:SS
Try this:
where(Date.strptime(:date, "%Y-%m-%d %H:%M:%S").year == year)
OR
where(["YEAR(?) = ?", :date, year])

Rails activerecord: query for record by a datetime field?

I'm trying to query for records in the TimeSlot table by the field start_date which has type datetime. Here's what I've tried so far that's failed:
TimeSlot.where(:start_date => DateTime.new(2010, 9, 1))
TimeSlot.where(:start_date => DateTime.new(2010, 9, 1).to_s)
TimeSlot.where(:start_date => "2010-09-08")
TimeSlot.where(:start_date => "2010-09-08 00:00:00")
I'd appreciate any help.
Your queries look good to me.
Are you sure that you have a matching row in the db?
To debug, look in your logs/development.log file.
Added:
Problem could be timezones. Your query is using your server's timezone. Your data could be stored in a different timezone.
I'm betting it is a timezone thing as well. Everything in the DB is automatically converted to UTC by rails. Queries 1 and 4 should work if there isn't an offset.
Answer from rubyonrails.org
Client.where("created_at >= :start_date AND created_at <= :end_date",
{:start_date => params[:start_date], :end_date => params[:end_date]})
or
Client.where("created_at IN (?)",
(params[:start_date].to_date)..(params[:end_date].to_date))
Here is what I have for query all the TimeSlot start at "2010-09-08", if your start_date is a date field.
TimeSlot.where("start_date >= ? AND start_date <= ?", "2010-09-08", "2010-09-08")
If you start_date is a datetime field.
TimeSlot.where("start_date >= ? AND start_date <= ?", "2010-09-08", "2010-09-09")
Because the datetime start at 00:00:00

Resources