I have a problem with timezones and a postgresql db (Rails 3.0.4, PostgreSQL 9.0). I'm using a custom scope, in which I append some conditions, do joins etc.pp.
The problem is, that Rails don't convert the times to my local timezone.
Here is the code of the scope:
scope :with_activities_within_range_in_week, lambda{ |latMin, lngMin, latMax, lngMax, date|
select("date_trunc('day', activities.starting_at) as date,
count(activities.id) as value
") \
.within(latMin, lngMin, latMax, lngMax) \
.joins(:activities) \
.merge(Activity.in_week(date)) \
.group("date") \
.order("date")
}
The within method checks for ranges, the Activity.in_week scope returns this:
where("activities.starting_at >= ? AND activities.starting_at < ?", start, stop)
And in the select statement, I want to trunc the starting_at field to day.
I'm getting the following output for the date field:
2011-04-07 18:48:32
2011-04-02 14:07:20
2011-04-02 14:06:49
Unfortunately it doesn't include the timezone. When I try to access my model through the "normal" Rails functions, it works:
puts Activity.first.starting_at
-> 2011-04-15 06:47:55 +0200
What I'm doing wrong? Hope someone can help!
thx,
tux
Your database is storing your timestamps in UTC (as it should). ActiveRecord is making timezone adjustments when it knows that it has a timestamp; so, when you say this:
puts Activity.first.starting_at
AR knows that starting_at is a timestamp so it instantiates the timestamp as an ActiveSupport::TimeWithZone instance and that class applies the timezone adjustment. But, when you say this:
select("date_trunc('day', activities.starting_at) as date ...
AR isn't going to parse the SQL to figure out that date_trunc will return a timestamp, AR doesn't even know what date_trunc means. AR will just see a string coming out of the database and it will hand it to you without interpretation. You are free to feed that string to ActiveSupport::TimeWithZone (or your favorite time handling class) yourself: there's nothing wrong with telling AR things that it does not and cannot know on its own.
Rails is clever but it isn't magic.
Related
I do have a postgres DB which contains a timestamp. This timestamp follow the ISO standard and looks like:
2017-08-28 20:14:45.684926+00
I have used ActiveRecord to access the DB from Ruby/Sinatra environment but the ts returned is ISO8601.
Is there a way to force the formatting to ISO and not ISO8601 ?
I would like to avoid parsing the data once received.
Right now, I am using the command:
class ApiResponse < ActiveRecord::Base
class << self
def send_query(user, params)
limit = params.include?('limit') ? params['limit'] : 50
where_params = {
userid: user,
allowed_intent: true
}
ApiResponse.select([:id, :ts, :userid, :intent, :response])
.where(where_params)
.order(ts: :desc)
.limit(limit).to_a
end
end
The ApiResponse class is used to access the DB through ActiveRecord and it works fine. but the format is not correct. it shows :
2017-08-29T05:58:44.488Z
instead of something like
2017-08-28 20:14:45.684926+00
This format ISO is the one in the db
Any idea how to get the timestamp ts correctly formated as I expect inside the ActiveRecord call?
I think there are some misunderstandings here:
Postgres uses ISO 8601 for output (the so called ISO, they are not different standards) https://www.postgresql.org/docs/9.6/static/datatype-datetime.html
ActiveRecord, I assume, returns an instance of DateTime class or something similar, and not a string in that format.
What you see is only a format used for displaying; you can use strftime to display the data in another format.
Our app has a lease_booking model with a field of lease_date which is a datatime (we use sqlite for development and mysql for production). The app needs to find all the lease_booking on a given date. The code is like:
LeaseBooking.where("lease_date == ?", '2012-1-5')
The problem is that we can not find any lease_booking on 2012/1/5 because the lease_date is a date+time format. How can the app compare the date part only for a datatime field in rails 3.1?
thanks.
I'm afraid you'd have to do :
LeaseBooking.where("date(lease_date) = ?", '2012-1-5')
(with only one = sign)
Maybe there is a better answer, because call the "date" function is not really pretty for ActiveRecord! Like Jason Lewis says in the comments, it is DB-specific.
Alternately, try this:
scope :booking_date, lamda {|d| where('lease_date > ?', Date.parse(d).to_time).where('lease_date < ?', (Date.parse(d) +1).to_time)}
And just call
LeaseBooking.booking_date('2012-01-05')
The where-chaining is ugly, but whether or not you can use multiple ? placeholders, or named variables, to do it in one statement, is highly DB-dependent. ARel is pretty good at turning even ugly chained queries into efficient SQL, though.
I'm in the U.S., and we usually format dates as "month/day/year". I'm trying to make sure that my Rails app, using Ruby 1.9, assumes this format everywhere, and works the way it did under Ruby 1.8.
I know that lots of people have this issue, so I'd like to create a definitive guide here.
Specifically:
'04/01/2011' is April 1, 2011, not Jan 4, 2011.
'4/1/2011' is also April 1, 2011 - the leading zeros should not be necessary.
How can I do this?
Here's what I have so far.
Controlling Date#to_s behavior
I have this line in application.rb:
# Format our dates like "12/25/2011'
Date::DATE_FORMATS[:default] = '%m/%d/%Y'
This ensures that if I do the following:
d = Date.new(2011,4,1)
d.to_s
... I get "04/01/2011", not "2011-04-01".
Controlling String#to_date behavior
ActiveSupport's String#to_date method currently looks like this (source):
def to_date
return nil if self.blank?
::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday))
end
(In case you don't follow that, the second line creates a new date, passing in year, month and day, in that order. The way it gets the year, month and day values is by using Date._parse, which parses a string and somehow decides what those values are, then returns a hash. .values_at pulls the values out of that hash in the order Date.new wants them.)
Since I know that I will normally pass in strings like "04/01/2011" or "4/1/2011", I can fix this by monkeypatching it like this:
class String
# Keep a pointer to ActiveSupport's String#to_date
alias_method :old_to_date, :to_date
# Redefine it as follows
def to_date
return nil if self.blank?
begin
# Start by assuming the values are in this order, separated by /
month, day, year = self.split('/').map(&:to_i)
::Date.new(year, month, day)
rescue
# If this fails - like for "April 4, 2011" - fall back to original behavior
begin
old_to_date
rescue NoMethodError => e
# Stupid, unhelpful error from the bowels of Ruby date-parsing code
if e.message == "undefined method `<' for nil:NilClass"
raise InvalidDateError.new("#{self} is not a valid date")
else
raise e
end
end
end
end
end
class InvalidDateError < StandardError; end;
This solution makes my tests pass, but is it crazy? Am I just missing a configuration option somewhere, or is there some other, easier solution?
Are there any other date-parsing cases I'm not covering?
Gem: ruby-american_date
This gem was created since I asked this question. I'm now using it and have been pleased.
https://github.com/jeremyevans/ruby-american_date
Date.strptime is probably what you're looking for in ruby 1.9.
You're probably stuck monkeypatching it onto string.to_date for now, but strptime is the best solution for parsing dates from strings in ruby 1.9.
Also, the formats are symmetric with strftime as far as I know.
you can use rails-i18n gem or just copy the en-US.yml and set your default locale "en-US" in config/application.rb
For parsing US-style dates, you could use:
Date.strptime(date_string, '%m/%d/%Y')
In console:
> Date.strptime('04/01/2011', '%m/%d/%Y')
=> Fri, 01 Apr 2011
> Date.strptime('4/1/2011', '%m/%d/%Y')
=> Fri, 01 Apr 2011
Use REE? :D
Seriously though. If this is a small app you have complete control over or you are standardizing on that date format, monkey patching for a project is totally reasonable. You just need to make sure all your inputs come in with the correct format, be it via API or website.
Instead of using to_s for Date instances, get in the habit of using strftime. It takes a format string that gives you complete control over the date format.
Edit:
strptime gives you full control over the parsing by specifying a format string as well. You can use the same format string in both methods.
Another option is Chronic - http://chronic.rubyforge.org/
You just need to set the endian preference to force only MM/DD/YYYY date format:
Chronic::DEFAULT_OPTIONS[ :endian_precedence ] = [ :middle ]
However the default for Chronic is the out-of-order US date format anyway!
I have Rails3 application with model user and field expires_at created like this:
t.column :expires_at, :timestamp
In my database (postgresql) it has type:
timestamp without timezone
The problem is when I call:
#user.expires_at = Time.now
#user.save
it is saved into database with UTC timezone (my local time is UTC + 1:00, Warsaw) but I don't want that. I just want to have time with my local timezone saved into the database (2011-03-30 01:29:01.766709, not 2011-03-29 23:29:01.766709)
Can I achieve this using rails3?
For saving time in local timezone to database this has to be set in application.rb
config.active_record.default_timezone = :local
If you only want to use local times on certain columns, rather than as a global setting, then the Rails documentation tells us this:
# If your attributes are time zone aware and you desire to skip time zone conversion to the current Time#zone when reading certain attributes then you can do following:
class Topic < ActiveRecord::Base
self.skip_time_zone_conversion_for_attributes = [:written_on]
end
(This also skips time zone conversion on writing, not just reading). And you can pass in an array of symbols for multiple attributes.
I am not sure which versions of Rails this was introduced in, though.
I need to update a field (called updated_at). The field in MySQL is of type datetime, and the class is ActiveSupport::TimeWithZone. But the dates are strings like "10/17/2008". I used "10/17/2008".to_date (And I intend .to_time and .to_datetime), and even if in console the ActiveRecord class save successfully, the field in the database still is the current date.
OK.. let's take them one at the time.
First, it is not recommended to set a field name updated_at, since this is a "magic" field that is automatically populated by Rails.
If you want to disable this functionality, you may:
class Foo < ActiveRecord::Base
self.record_timestamps = false
end
in your class, but this will also disable created_at fields.
The best option is to add a new field (e.g. my_updated_at) as date in the database, and then Rails will automatically handle conversions, meaning that the next snippet will work:
Foo.new({:my_updated_at => "10/17/2008"})
Second, the answer on how to parse a string to ActiveSupport::TimeWithZone is:
ActiveSupport::TimeZone['UTC'].parse("10/17/2008")
but I don't think this will help you (of course, change UTC with your current date/time).
Simply
date_as_string = "2008-10-17"
ActiveSupport::TimeZone['UTC'].parse(date_as_string)
# => Fri, 17 Oct 2008 00:00:00 UTC +00:00
And just to confirm that it worked..
ActiveSupport::TimeZone['UTC'].parse(date_as_string).class
# => ActiveSupport::TimeWithZone