Issues with rails, postgres, timezones and scopes - ruby-on-rails-3

We're seeing some funky stuff when trying to do scopes in Rails 3 in regards to timezones. Let's say we have an attribute called start_at of type date time. In application.rb we have config.time_zone = 'Eastern Time (US & Canada)' set based on our server location.
When someone sets start_at to something like '2014-02-22 at 7pm EST' in the db it's stored as '2014-02-23 01:00:00-5' so when I setup a named scope such as:
scope :tomorrow, where("start_at = ?", Date.tomorrow)
that record is wrongly returned. If I try something like this:
scope :tomorrow, where("start_at at time zone 'EST' = ?", Date.tomorrow)
it still doesn't work. How can I create scopes with db date time comparisons that account for the local timezone without having to do something ridiculous, such as:
scope :tomorrow, where("date(start_at - INTERVAL '5 hours') = ?", Date.tomorrow)

You have to use the Time.zone option to use the zone that you configured
scope :tomorrow, where("start_at = ?", Time.zone.today + 1)
I recommend this good article about Rails time zones

Related

Rails business hours query for active record

I have two models
OfficeTimeing < ActiveRecord::Base
belongs_to :office
end
Office < ActiveRecord::Base
has_many :office_timings
end
with two fields opening_time and closing_time these fields have string values like "09:00 AM" I want a query how can I find currently open offices I want something like this.
Office.joins(:office_timings).where("Datetime.now > office_timings.opening_time AND office_timings.closing_time > DateTime.now")
I don't know how to compare two times represented as a string without being able to parse them in rails first, however, have you considered storing your opening and closing times in seconds (counting from midnight) rather than strings? That way you would easily be able to write this query.
UPDATE: A few useful methods to achieve this.
To get the current seconds since midnight for a specific time:
seconds = specific_time.seconds_since_midnight
To convert the seconds since midnight into a Time object:
time_instance = Time.at(seconds)
To produce a string in the form of 09:00 AM
time_instance.strftime("%I:%M %p")
Details about Time class.
Details about strftime.
You need to inject Datetime.now into your query using ?:
Office.joins(:office_timings)
.where(
"? > office_timings.opening_time AND office_timings.closing_time > ?",
Datetime.now, Datetime.now
)
Each ? will be safely replaced by the arguments following the query string.
You could also do this directly in Postgres using now(), and make the condition a bit easier to read using BETWEEN:
.where("now() BETWEEN office_timings.opening_time AND office_timings.closing_time")

Rails 3.2.8 - How to handle Time zones?

I've participated the Rails Rumble this year and I found myself tangled by time zone issues.
I uploaded my app in linode and I live in EST time zone or -4 to UTC.
I have a model and it saves things by doing the following:
def self.processing_creation(user_id, home_id, chore_id)
registered_chore = RegisteredChore.where("DATE(created_at) = ?", Date.today).find_by_user_id_and_home_id_and_chore_id(user_id, home_id, chore_id)
unless registered_chore
registered_chore = RegisteredChore.create(user_id: user_id, home_id: home_id, chore_id: chore_id, created_at: Time.zone.now)
user = registered_chore.user
user.add_one_chore_point
end
registered_chore
end
RegisteredChore.where("DATE(created_at) = ?", Date.today).find_by_user_id_and_home_id_and_chore_id(user_id, home_id, chore_id)
returns false even if the data was just created.
I noticed that "create" saves in UTC but Date.today uses user's local time zone.
What's the best way to handle this?
Another example to illustrate the issue:
I want to register a chore at 11 PM in EST time.
The server is already in the next day (e.g: 12).
Rails saves the data in the next day date (12).
But user is still in day 11.
Technically, user with the current method, could save the entry again because the days are different from the db and user interface.
How to solve this?
Rails will, by default, save times to the database as UTC. If you're using DATE([Date.today]), then it's going to be looking up records on the 11th, not the 12th. To get the right date, you probably want to set Time.zone to the user's current timezone, and then do the query.
I built a gem called by_star to handle this kind of date searching, and I reckon you should use it. With it, your query would be this:
RegisteredChore.today.where(:user_id => user.id,
:home_id => home.id,
:chore_id => chore.id)
Rails 3 by default saves all the time in database relative in GMT+00:00 time zone. So you will have to set an offset while computing time depending on your timezone. Else, you can change default Activerecord time zone by adding following to application.rb
config.time_zone = 'Your time zone' (Example: 'Eastern Time (US & Canada)')
config.active_record.default_timezone = 'Your time zone' (Example: 'Eastern Time (US & Canada)')

How to update_attributes w/ UNIX Timestamp and set MySQL DATETIME automatically?

I just assumed that some rails magic would automatically convert an incoming post w/ a unix time 1345069440000 to the appropriate datetime on the backend. However, I have a model Event with a datetime called "start_at" and:
e = Event.new()
e.start_at = 1345069440000
e.save
It seems to send the 1345069440000 straight through and then mysql nulls it. Same with a ruby time
e = Event.new()
e.start_at = 1345069440
e.save
if I set it to some arbitrary strings, it does a better job of inferring:
e.start_at = '1/1344/12'
e.save
sets the date to '1334-12-01 00:00:00 UTC +00:00". So, it's making an attempt.
Clearly I can override the setter in my class, but I was hoping to change this behavior much higher up so that all controllers would support unix times for any datetime being passed up.
Rails 3.2, Ruby 1.9.2
Looks like this code from active_record/attribute_methods/time_zone_conversion.rb is attempting to do the conversion:
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
One option (albeit a little heavy-handed) would be to monkey-patch Fixnum to add a .to_time method:
def to_time
Time.at(self)
end

Store time interval in PostgreSQL from Rails

I need to store time inverval in PosgreSQL. Data will be put from Ruby on Rails.
Say, PizzaHut accepts orders from 9:00 to 18:00.
I've created following migration:
class AddOrderTimeAndDeliveryTimeToMerchants < ActiveRecord::Migration
def self.up
add_column :merchants, :order_time, :interval
end
I've read the documentation, and have following code now:
Merchant.create( :delivery_time => "9:00 18:00" )
When i execute it, i get following error message:
PGError: ERROR: invalid input syntax for type interval: "9:00 18:00"
How to do it correctly ?
I don't think an interval is really what you want for that. An interval represents a timespan without any specific end points; for example, you add an interval to an existing time to get another time. You would be better off with two distinct times:
add_column :merchants, :order_from, :time, :null => false
add_column :merchants, :order_to, :time, :null => false
Then, if for some reason you need to know how many hours they're open for delivery, you can construct an interval by subtracting :order_from from :order_to.
If you really must use an interval, then you'll need to construct a value something like this:
:delivery_time => "interval '11 hour'"
Note how this illustrates that an interval is not a specific time range from A to B, it is just a time range of some certain length (with no specified end points).
It's likely that you want a time without timezone here, since if Dominoes in NY opens at 9:00 local time, and Dominoes in California also opens at 9:00 local time, then a time with a timezone would not work properly.
What you likely want is one of two things. either two times a start and an end time, or a start time and an interval. I would suggest two times, but the choice is yours. Note that you can an interval from two times by subtracting one from the other.
select '09:00:00'::time - '05:00:00'::time;
?column?
----------
04:00:00

date comparisons in Rails

I'm having trouble with a date comparison in a named scope. I'm trying to determine if an event is current based on its start and end date. Here's the named scope I'm using which kind of works, though not for events that have the same start and end date.
named_scope :date_current, :conditions => ["Date(start_date) <= ? AND Date(end_date) >= ?", Time.now, Time.now]
This returns the following record, though it should return two records, not one...
>> Event.date_current
=> [#<Event id: 2161, start_date: "2010-02-15 00:00:00", end_date: "2010-02-21 00:00:00", ...]
What it's not returning is this as well
>> Event.find(:last)
=> #<Event id: 2671, start_date: "2010-02-16 00:00:00", end_date: "2010-02-16 00:00:00", ...>
The server time seems to be in UTC and I presume that the entries are being stored in the DB in UTC. Any ideas as to what I'm doing wrong or what to try?
Thanks!
Easiest way to debug is to look in your rails log to see exactly what SQL statement is being generated by Rails.
Then post the sql if still having problems.
In the meantime, my guess is that something is not set to UTC. Where something is the set {operating system, rails environment, dbms}
Added: Also, why are you comparing "Date" (in the dbms) with "Time" values (from your statement)? Better to have the type classes match explicitly. I use the standard of a new day has time component 00:00:00. That way you can compare with the db without needing the date function in your SQL.
I struggled with the same issue. The records are stored in UTC, but the utc value is not inserted into the query. I solved it, like you, by converting it to utc manually, like this time.utc.
def find_production_since(start_time)
find(:all, :conditions => ["time > ?", start_time.utc])
end
This might help you.
named_scope :date_current, :conditions => ["Date(start_date) <= Date(NOW()) AND Date(end_date) >= Date(NOW())"]
Rather than using Time.now (and then laboriously converting to a date as per your second comment) you could just use Date.today, which will insert '2010-04-20' into your SQL, and should compare against Date(start_date) without worrying about times.