I've got 2 Time Without Time Zone columns in my table (start_time and end_time). When an event occurs, I need to see which of these columns has a start_time < Time.now < end_time. The problem seems to be that Rails is not saving the Time in UTC and is instead saving the time exactly as I enter it in my field. My local timezone is UTC -5, so if I put in an end_time of 09:00:00, I would expect that to get saved to the database as 14:00:00, but it's getting saved as 09:00:00, so when I later run my start_time < Time.now < end_time query, I'm getting the wrong results (I'm off by those 5 hours).
Here's an example (the model is called TimeParameter):
1.9.3-p392 :001 > TimeParameter.create(start_time: "00:30:00", end_time: "09:00:00")
(7.0ms) SELECT * FROM geometry_columns WHERE f_table_name='time_parameters'
(0.2ms) BEGIN
SQL (9.8ms) INSERT INTO "time_parameters" ("created_at", "day_of_week", "end_time", "start_time", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Tue, 23 Jul 2013 14:29:06 UTC +00:00], ["day_of_week", nil], ["end_time", 2000-01-01 09:00:00 UTC], ["start_time", 2000-01-01 00:30:00 UTC], ["updated_at", Tue, 23 Jul 2013 14:29:06 UTC +00:00]]
(308.9ms) COMMIT
=> #<TimeParameter id: 24, start_time: "2000-01-01 00:30:00", end_time: "2000-01-01 09:00:00", day_of_week: nil, created_at: "2013-07-23 14:29:06", updated_at: "2013-07-23 14:29:06">
Local time is ~09:30, UTC is ~14:30
Notice that the updated_at and created_at reflect the 14:30 UTC time but my start_time and end_time are not converted to UTC before being saved.
Ruby version: 1.9.3-p392
Rails version: 3.2.13
Related
I have a Rails 7 model that uses Postgres' virtual column feature:
create_table :time_entries do |t|
# ...
t.virtual :duration, type: :interval, as: %(("to" - "from")::interval), stored: true, null: false
# ...
end
The problem is, that after I create a record via Rails create(...) these virtual column is nil:
[16] pry(main)> TimeEntry.create(from: Time.zone.now, to: 1.day.from_now)
TRANSACTION (0.3ms) BEGIN
TimeEntry Create (0.5ms) INSERT INTO "time_entries" ("from", "to", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING "id"
[["from", "2022-11-18 06:45:11.419000"], ["to", "2022-11-19 06:45:11.420000"], ["created_at", "2022-11-18 06:45:11.420862"], ["updated_at", "2022-11-18 06:45:11.420862"]]
TRANSACTION (0.9ms) COMMIT
=> #<TimeEntry:0x0000ffff86f1ae10
id: 13,
from: Fri, 18 Nov 2022 06:45:11.419000000 UTC +00:00,
to: Sat, 19 Nov 2022 06:45:11.420000000 UTC +00:00,
duration: nil,
created_at: Fri, 18 Nov 2022 06:45:11.420862000 UTC +00:00,
updated_at: Fri, 18 Nov 2022 06:45:11.420862000 UTC +00:00>
When you reload the model, duration is set.
I found out, that this is due to Rails only returning the id column using RETURNING "id" at the end of the INSERT INTO statement. When you execute the query in Postgres directly you can return the generated duration column directly after the insert:
app_development=# INSERT INTO "time_entries" ("from", "to", "created_at", "updated_at") VALUES ( '2022-11-18 06:34:46.889000', '2022-11-18 06:34:56.889000', '2022-11-18 06:34:46.889000', '2022-11-18 06:34:46.889000') RETURNING "duration", "id";
duration | id
----------+----
00:00:10 | 11
(1 row)
Is it possible to customize the RETURNING "id" part of the SQL query in my model, so that the instance of TimeEntry already have the duration set after I create it?
EDIT:
I found the code segment inside the Postgres Adapter and tried to monkey patch it like this:
require "active_record/connection_adapters/postgresql/database_statements"
module PostgresReturningPatch
def sql_for_insert(...)
sql, *args = super
if sql.include?(TimeEntry.table_name) && sql.ends_with?(%(RETURNING "id"))
returning_virtual_columns = TimeEntry::columns.select(&:virtual?).map do |column|
quote_column_name(column.name)
end.join(", ")
sql += ", #{returning_virtual_columns}"
end
binding.pry
[sql, *args]
end
end
ActiveRecord::ConnectionAdapters::PostgreSQL::DatabaseStatements.module_eval do
prepend PostgresReturningPatch
end
Unfortunately, it's still nil when Rails returns the instance of my model, despite that the SQL ends now with RETURNING "id", "date", "duration".
I have a rails 5 server that is configured to be "Eastern Time (US & Canada)"
The server works great mostly, it stores times in the database in UTC.
For example, if I am in a console and do
Time.zone.now
=> Fri, 27 Oct 2017 15:07:04 EDT -04:00
Which is correct (we have not fall back on time yet)
However if I pull down an active record object:
> s = Schedule.first
Schedule Load (6.2ms) SELECT "schedules".* FROM "schedules" ORDER BY "schedules"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Schedule id: 1, product_id: 1, day: "Monday", start_time: "2000-01-01 22:00:00", end_time: "2000-01-01 23:00:00", size: 25, description: "Grades 3-5", created_at: "2017-08-23 14:16:09", updated_at: "2017-08-23 14:16:09", is_full: false>
2.4.0 :004 > s.start_time
=> Sat, 01 Jan 2000 17:00:00 EST -05:00
As you can see it convert is to Easter Standard time, which is not correct. Is it possible rails is confused or am I doing something wrong?
(using ruby 2.4)
This was user error, the objects date was in EST so Active Record was doing its job, fix was to set it to today and it would be in the right zone.
I want to convert the utc time to 'utc+1' or 'utc+something' time zone and convert back that to utc time zone.
Its something like that what i want to do. I am asking user to choose there UTC time zone ex: utc+4 .and i am getting current UTC time using Time.now.utc .Now i wwant to convert this utc time to 'utc+4'.
And after displaying that time to 'utc+4' i want to convert back that time equivalent utc time zone.
How this can be done?
If you are working with Ruby On Rails and you want to change time zone per request and reset it back after finishing the request. You can use Time.use_zone to set the time zone for the user (document: http://api.rubyonrails.org/classes/Time.html#method-i-use_zone)
The following is what I have tested in Rails 4.1.
First, It is recommended to set the sane default for config.time_zone (in config/application.rb), I set to "Mumbai" (UTC+5.30) for instance. (to list time zones, you can use command bundle exec rake time:zones:all)
module MyApp
class Application < Rails::Application
config.time_zone = 'Mumbai'
end
end
In your project, run rails g model User name:string time_zone:string
And bundle exec rake db:migrate
Then, create some test users via rails console, run rails c
Loading development environment (Rails 4.1.4)
irb(main):001:0> first_user = User.create!(name: 'zdk', time_zone: 'Bangkok')
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "users" ("created_at", "name", "time_zone", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2014-07-31 09:21:13.750710"], ["name", "zdk"], ["time_zone", "Bangkok"], ["updated_at", "2014-07-31 09:21:13.750710"]]
(0.6ms) commit transaction
=> #<User id: 1, name: "zdk", time_zone: "Bangkok", created_at: "2014-07-31 09:21:13", updated_at: "2014-07-31 09:21:13">
irb(main):002:0> second_user = User.create!(name: 'joe', time_zone: 'London')
(0.1ms) begin transaction
SQL (0.8ms) INSERT INTO "users" ("created_at", "name", "time_zone", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2014-07-31 09:21:31.299606"], ["name", "joe"], ["time_zone", "London"], ["updated_at", "2014-07-31 09:21:31.299606"]]
(1.9ms) commit transaction
=> #<User id: 2, name: "joe", time_zone: "London", created_at: "2014-07-31 09:21:31", updated_at: "2014-07-31 09:21:31">
irb(main):003:0>
Try to query what we just created, you can see that it uses time zone that you have set in Application config (config.time_zone).
The output:
irb(main):003:0> first_user.created_at
=> Thu, 31 Jul 2014 14:51:13 IST +05:30
irb(main):005:0> second_user.created_at
=> Thu, 31 Jul 2014 14:51:31 IST +05:30
And how to handle per request basis time zone using Time.zone.
Go to your ApplicationController (app/controllers/application_controller.rb file).
Create a method that set time zone called by around_filter ( More details: http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails ). I also create hello action will be routed from root url. Like so:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
around_filter :set_time_zone
def set_time_zone
if true #if user loggin ?
#user = User.first #Change to User.last to see the result.
Time.use_zone(#user.time_zone) { yield }
else
yield
end
end
def hello
render plain: "Hello, user: #{#user.name}. Created: #{#user.created_at}"
end
end
Routing application uri to your controller method by editing your config routes (config/routes.rb) to have following this
Rails.application.routes.draw do
root 'application#hello'
end
If you set everything correctly. You should have the output in this format
for the first user: Hello, user: zdk. Created: <Date> <Time> +0700
for the second user: Hello, user: joe. Created: <Date> <time> +0100
In summary, the flow is something like:
+------------+
| APP |
+------------+
+ Use the Time.zone value instead if it's set
| ^
WRITE |
v | READ
+
Convert to UTC Convert from UTC to config.time_zone
+ ^
| |
WRITE | READ
| |
v +
+--------------------------------+
| |
| DB |
| |
| UTC |
+--------------------------------+
Hope this helps.
You can use ActiveSupport::TimeWithZone
Example :
#Define a TimeZone
Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
#Define a utc time
t = Time.utc(2007, 2, 10, 20, 30, 45) # => '2007-02-10 20:30:45 UTC
#same time with gmt -5
t.in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -5:00
I have two models join by a middle model
class Integration < ActiveRecord::Base
has_many :integration_records
has_many :records, through: :integration_records
end
class IntegrationRecord < ActiveRecord::Base
belongs_to :integration
belongs_to :record
end
class Record < ActiveRecord::Base
has_many :integration_records
has_many :integrations, through: :integration_records
end
i = Integration.create(whatever)
i.records.create(whatever)
=> (0.1ms) BEGIN
SQL (8.7ms) INSERT INTO "records" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", Sat, 03 May 2014 00:31:02 UTC +00:00], ["updated_at", Sat, 03 May 2014 00:31:02 UTC +00:00]]
SQL (0.6ms) INSERT INTO "records" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", Sat, 03 May 2014 00:31:06 UTC +00:00], ["updated_at", Sat, 03 May 2014 00:31:06 UTC +00:00]]
(0.4ms) COMMIT
i.records
=> [one record]
Record.all.count
=> 2
i.integration_records
=> #<IntegrationRecord id: nil, integration_id: 1, record_id: 2 >
Notice id is nil
IntegrationRecord.all
=> #<ActiveRecord::Relation []>
IntegrationRecord.create
=> #<IntegrationRecord id: nil, integration_id: nil, record_id: nil>
Notice id is nil
Record.create.integrations.create
=> (0.1ms) BEGIN
SQL (8.7ms) INSERT INTO "integrations" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", Sat, 03 May 2014 00:31:02 UTC +00:00], ["updated_at", Sat, 03 May 2014 00:31:02 UTC +00:00]]
SQL (0.6ms) INSERT INTO "records" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", Sat, 03 May 2014 00:31:06 UTC +00:00], ["updated_at", Sat, 03 May 2014 00:31:06 UTC +00:00]]
(0.4ms) COMMIT
Not sure why this is happening, in the case of i.records.create(whatever) it should output:
SQL (0.6ms) INSERT INTO "records" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id" [["created_at", Sat, 03 May 2014 00:31:06 UTC +00:00], ["updated_at", Sat, 03 May 2014 00:31:06 UTC +00:00]]
(0.4ms) COMMIT
INSERT INTO "integration_records" ("created_at", "data", "integration_id", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Sat, 03 May 2014 15:57:05 UTC +00:00], ["data", {}], ["integration_id", 5], ["updated_at", Sat, 03 May 2014 15:57:05 UTC +00:00]]
I should note that for some reason, when I create a new app in rails 4.0.4, I still had this problem. But when I change the names of the models and specifically Record model, it works perfectly fine. So when I changed it to Recordrow no problem at all.
As per your current association setup, all you need to do is, just follow these simple instructions step by step
Create an Integration record
## this will create an "integration" record in "integrations" table
integration = Integration.create(whatever)
Create an associated Record record
## this will create an associated "record" entry in "records" table
## PLUS this will also created an associated record in "integration_records" table
integration.records.create(whatever)
View the associated records and associated integration_records
## Display all the "records" entries associated to this particular integration
integration.records
## Display all the "integration_records" entries associated to this particular integration
integration.integration_records
UPDATE
i.records.create(whatever) was creating 2 records, found out that the issue was with the name records of the table. Once changed everything works fine. It looks like records is reserved word.
Also, OP found this link which states records is reserved
I am trying to generate some seed material.
seed_array.each do |seed|
Task.create(date: Date.new(2012,06,seed[1]), start_t: Time.new(2012,6,2,seed[2],seed[3]), end_t: Time.new(2012,6,2,seed[2] + 2,seed[3]), title: "#{seed[0]}")
end
Ultimately I will put random hours, minutes, seconds. The problem that I am facing is that instead of creating a time with the 2012-06-02 date it creates a time with a different date: 2000-01-01.
I tested Time.new(2012,6,2,2,20,45) in rails console and it works as expected. When I am trying to seed my database however some voodoo magic happens and I don't get the date I want.
Any inputs are appreciated. Thank you!
Update1:
(0.0ms) begin transaction SQL
(0.5ms) INSERT INTO "tasks" ("created_at", "date", "description",
"end_t", "group_id", "start_t", "title", "updated_at") VALUES (?, ?,
?, ?, ?, ?, ?, ?) [["created_at", Tue, 03 Jul 2012 02:15:34 UTC
+00:00], ["date", Thu, 07 Jun 2012], ["description", nil],
["end_t", 2012-06-02 10:02:00 -0400], ["group_id", nil],
["start_t", 2012-06-02 08:02:00 -0400], ["title", "99"],
["updated_at", Tue, 03 Jul 2012 02:15:34 UTC +00:00]]
(2.3ms) commit transaction
This is a small sample of the log.
Update 2
Task id: 101, date: "2012-06-26", start_t: "2000-01-01 08:45:00", end_t: "2000-01-01 10:45:00", title: "1", description: nil, group_id: nil, created_at: "2012-07-03 02:15:33", updated_at: "2012-07-03 02:15:33"
This is what shows up in rails console.
Sounds like you used the :time type for your start_t and end_t. In that context time refers to a pure time of day data type, with no date, because that's what the SQL time type is.
Ruby's Time type covers both date and time, so your database column needs to be :datetime. In fact ruby has no pure time of day class so activerecord uses a regular Time with a bogus date instead.
I have tested you code like this:
arr = [1,2,3]
arr.each do |seed|
Timetest.create(time: Time.new(2012,6,2,2,20,45), title: "#{seed}")
end
And its working as expected. But i got this sql on my console and its a bit different than yours in your start_t and end_t
SQL (4.1ms) INSERT INTO "timetests" ("created_at", "time", "title", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Tue, 03 Jul 2012 02:11:35 UTC +00:00], ["time", Sat, 02 Jun 2012 07:20:45 UTC +00:00], ["title", "1"], ["updated_at", Tue, 03 Jul 2012 02:11:35 UTC +00:00]]
Update 1
Have you tried to use DateTime.new instead of Time.new?
Task.create(date: DateTime.new(2012,06,seed[1]), start_t: DateTime.new(2012,6,2,seed[2],seed[3]), end_t: DateTime.new(2012,6,2,seed[2] + 2,seed[3]), title: "#{seed[0]}")