Ruby on Rails noob named_scope with datetime - ruby-on-rails

I am a .net developer that was handed a ruby on rails application because I took one class 5 years ago. I am really a novice at understanding this application but I think I have found my problem now I need help to figure out how to fix it.
I have a schedule that shows audits and the time slots those audits are scheduled in.
in the schedules_controller.rb
#audits = Audit.after(Date.yesterday).before(Date.tomorrow).not_canceled.find_all_by_user_id(params[:id])
in the audit.rb model I have 2 named scopes
named_scope :after, lambda {|date| {:conditions => "date_time > '#{date} 23:59:00.000'"}}
named_scope: before, lambda {|date| {:conditions => "date_time < '#{date} 00:00:00.000'"}}
The datetimes in the database are stored in utc time so, when all the audits for today are shown the ones for later in the day are missing. For example (when in EST) if I have an audit for 7:30pm today, it is stored in the database with tomorrows date because of the 5 hour difference. Any suggestions on how to correct this issue? The application is in rails 2.3.5. Thanks!

You can pass the time object into the scope instead of date so that it will be more accurate. You have to be conscious about time zones while doing so:
Time.zone = "Eastern Time (US & Canada)"
start_time = Time.zone.parse("#{Date.today} 23:59:00")
end_time = Time.zone.parse("#{Date.yesterday} 00:00:00")
#audits = Audit.after(start_time)).before(end_time).not_canceled.find_all_by_user_id(params[:id])
and the scopes should be changed like this
named_scope :after, lambda {|time| {:conditions => ["date_time > ?", time]}}
named_scope :before, lambda {|time| {:conditions => ["date_time < ?", time]}}
The key here is the code Time.zone.parse which will parse the time assuming the string given is in the time zone specified by the line above. If we use Time.parse directly instead it will assumethe server machine clock's timezone as the timezone.
Here, Date.today is also risky as it gives date in server machine's timezone. There are two helper methods added by Rails to the time object as beginning_of_day and end_of_day which will give the correct time. So, using them, we can calculate
start_time = Time.zone.now.end_of_day - 1.day
end_time = Time.zone.now.beginning_of_day + 1.day
If in the config/environment.rb, you have already defined,
config.time_zone = 'Eastern Time (US & Canada)'
or the required time zone, you dont have to do the first line in the code (Time.zone = "Eastern Time (US & Canada)"). For getting the time zones in US, you can use rake time:zones:us. (For all timezones, it is rake time:zones:all)

Related

Really odd issues with Time when it used within a scope

There is a page in my Rails-app where tours should be displayed so that the start_date field is equal to tomorrow's date (in GMT+00)
I use default timezone in application.rb
# Set Time.zone default to the specified zone and make Active Record ...
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
However there in is a problem. Instead of the page displaying tour dates starting from tomorrow, I see dates from today and even yesterday.
I put to the page the following information:
Time.zone (GMT+00:00) UTC
Date.today_local_zone 2013-11-20
Date.today 2013-11-20
Time.now 2013-11-20 00:48:21 +0000
Code in my controller:
puts "... #{ Tour.upcoming.order('start_date ASC').to_sql }"
#tours = Tour.upcoming.order('start_date ASC')
And the scope in Tour model
class Tour < ActiveRecord::Base
attr_accessible :start_date, :title
scope :upcoming, where('tours.start_date > ?', Date.today_local_zone)
end
Briefly about my today_local_zone method:
class Date
def self.today_local_zone
Time.zone.now.to_date
end
end
Here is a line from my logs (the date the query is different from the date in the logs)
2013-11-20T00:48:21.178380+00:00 app[web.1]: ... SELECT "tours".* FROM "tours" WHERE (tours.start_date > '2013-11-19') ORDER BY start_date ASC
After heroku restart or deploy dates besome correct
In heroku console heroku run rails c all dates are correct too
I decided to start another application from scratch and to deploy in on heroku. And result remained the same.
I ping this page every 5 minutes with pingdom. Date become correct after hour or two or even 23 after midnight. i.e. value of lag is different each day.
UPD:
I tried to log value of Time.zone.now.to_s.to_date. It's value is correct.
Please also look at my gist with:
Gemfile
Screenshot of webpage, including all values and text of generated query
I missed this on first review. The problem is in your scope statement:
scope :upcoming, where('tours.start_date > ?', Date.today_local_zone)
This definition is wrong. It loads Date.today_local_zone when the class is loaded. You need to wrap it in a Proc. So it should be:
scope :upcoming, lambda { where('tours.start_date > ?', Date.today_local_zone) }
which ensures that Date.today_local_zone is executed each time the scope is used.

Supporting timezones in a Rails application

I added a calendar to my rails application. We have events with a start date and an end date (both includes hours and minutes). This ones are fields in the events table, actually their format is datetime. When you select the start date, end date, title and description (among others) for an specific event the the parameters hash is something like this:
parameters = {"event" => {"title" => "a title", "description" => "a description", "start_date" => "2012-09-23 23:45", "end_date" => "2012-09-24 15:32"}}
In the events controller i have something like this:
Event.create(:title => params[:event][:title],
:description => params[:event][:description],
:start_date => params[:event][:start_date].present? ? DateTime.strptime(params[:event][:start_date], "%Y-%m-%d %H:%M") : nil,
:end_date => params[:event][:end_date].present? ? DateTime.strptime(params[:event][:end_date], "%Y-%m-%d %H:%M") : nil,
...
)
Well, when this event is stored into the database (mysql) the value for start date is "2012-09-23 20:45" while the value for end date is "2012-09-24 12:32". I understand that Rails is automatically dealing with timezones. My problem is that when I want to fetch all the events for a specific time (for example, all events that occurs today) I do:
query = "((start_date between ? AND ?) OR (end_date between ? AND ?))"
query << "title NOT LIKE ? AND ..."
self.events.where(query,
Time.now.beginning_of_day, Time.now.end_of_day,
Time.now.beginning_of_day, Time.now.end_of_day,
some_restriction_here, ...)
I get the wrong events! (it is obvious why, the date and time is different in the database from the inputs). There's and obvious solution also, bring all the events into memory and the date and time will be as the original again but this is expensive in terms of resources. By the other hand this don't solve the problem of timezones, if someone in china crate an event the value in the database will be relative to the time offset of the server localization. I could have a field somewhere where the user can set a time zone and work with this storing a specific date for such user and working through this, what is your suggestion about the best way to deal with this?
Rails stores all dates in database in the UTC format. So one way is to always do operations in UTC and in the view convert the displayed dates to current time zone. The other way is to change Rails timezone on the beginning of every action. All parsed times and dates will then be in your custom time zone.
def action_name
Time.zone = "Prague"
# ... logic
end

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

How to query for all records created today (midnight UTC..Now)

I'm trying to create a created_at query range that will capture all records created from midnight UTC (db default) to now. In the past I've been able to query for time ranges with:
created_at => (24.hours.ago..Time.now)
But adjusting the above for the new use case does not work:
created_at => (Date.today..Time.now)
Any suggestions on how I can update the created_at range to be all records today / not in the last 24 hours?
Thanks
created_at => (DateTime.now.at_beginning_of_day.utc..Time.now.utc)
I think, you can try for below line as well.
ModelName.all :condition => ["DATE(created_at) = DATE(?)", Time.now]
OR In Rails 3
Model.where "DATE(created_at) = DATE(?)", Time.now
Cheers!
I think time zone should be considered.
where(:created_at => Time.zone.now.beginning_of_day.utc..Time.zone.now.end_of_day.utc)
And Rails will change time to utc time automatically, so the following is also ok.
where(:created_at => Time.now.beginning_of_day..Time.now.end_of_day)
Since Rails5.1, A new method Date#all_day was introduced, which returns a range of a day.
Instead of:
where(created_at: Date.today.beginning_of_day..Date.today.end_of_day)
An elegant way is:
where(created_at: Date.today.all_day)
Date.today.beginning_of_day.utc..Date.today.end_of_day.utc

Getting objects updated in last 10 seconds from ActiveRecord's find

I'd like to get the objects that are updated in the last 10 seconds from the ActiveRecord's find.
I tried doing this
#chats = Chat.find(:all, :conditions => ["updated_at > ?", 10.seconds.ago] )
as well as
#chats = Chat.find(:all, :conditions => ["updated_at > ?", Time.now-10.seconds] )
and even
#chats = Chat.find(:all, :conditions => {:updated_at => 10.seconds.ago..0.seconds.ago}]
But I still can't get it to work :(
EDIT: I am updating the column from another application to keep it alive and I'm looking at all the rows I kept alive in the last 10 seconds, to dispose the dead chats.
Are you sure that you are querying on the right column? I am thinking that you might need to look at the created_at timestamp instead. I don't know your application, but I am surprised you are updating existing chats, instead of just inserting new ones.
So... if my assumption is correct, this might be what you are looking for:
#chats = Chat.find(:all, :conditions => ["created_at > ?", 10.seconds.ago] )
If not... please provide a little more info. The query should work as you have it, and I am guessing the updated_at may not be getting updated.
The problem was in UTC-default of Rails. I changed it to
config.time_zone = 'Pacific Time (US & Canada)'
and now it works alright :)

Resources