How to set timezone for rails and sqlite? - ruby-on-rails

The test app:
$ rails new ra1
$ cd ra1
$ ./bin/rails g model m1 dt1:datetime
$ ./bin/rake db:migrate
Then I add config.time_zone = 'Europe/Kiev' into config/application.rb, run console:
irb(main):001:0> M1.create dt1: Time.now
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "m1s" ("dt1", "created_at", "updated_at") VALUES (?, ?, ?) [["dt1", "2015-03-30 11:11:43.346991"], ["created_at", "2015-03-30 11:11:43.360987"], ["updated_at", "2015-03-30 11:11:43.360987"]]
(33.0ms) commit transaction
=> #<M1 id: 3, dt1: "2015-03-30 11:11:43", created_at: "2015-03-30 11:11:43", updated_at: "2015-03-30 11:11:43">
irb(main):002:0> Time.now
=> 2015-03-30 14:12:27 +0300
irb(main):003:0> Rails.configuration.time_zone
=> "Europe/Kiev"
What am I doing wrong?

Values in the database are always stored in UTC, no matter the time_zone.
The time zone configuration affects only the Ruby environment. The data is fetched from the database and the dates are converted into the selected timezone. The same applies to new time instances, as you noticed using Time.now.
The main reasons the time is normalized in the database is that it allows to easily convert the same value to multiple timezones, or change the timezone after the initial phase of the project without the need to reconvert all the dates. It's a good practice to use UTC in the database.

Related

How to store epoch timestamp in Rails?

I have a table that needs to store epoch timestamp.
class CreateKlines < ActiveRecord::Migration[5.1]
def change
create_table :klines do |t|
t.string :symbol
t.timestamp :open_time
t.timestamp :close_time
t.timestamps
end
end
However, when I stored it, it becomes nil
(*the open_time and the close_time)
EX:
Kline.create(open_time: Time.now.to_i)
(0.3ms) BEGIN
SQL (1.5ms) INSERT INTO `klines` (`created_at`, `updated_at`) VALUES ('2020-05-14 01:45:22', '2020-05-14 01:45:22')
(3.8ms) COMMIT
You can notice that the value of open_time is gone, and the result is
#<Kline:0x00007f9f68c87788
id: 600,
symbol: nil,
open_time: nil,
close_time: nil,
created_at: Thu, 14 May 2020 01:45:22 UTC +00:00,
updated_at: Thu, 14 May 2020 01:45:22 UTC +00:00>
Env:
Rails 5.1.4,
Ruby 2.6.5,
MySQL 5.7
Rails has two "special" timestamps, created_at and updated_at that Rails automatically stores and updates it properly (t.timestamps will generate those columns). Otherwise, it's not special meaning you need to set it manually.
So for example,
def open
update(open_time: Time.current)
end
a method like this can do what you want, and you can add anything to this method so that it does other things besides updating a column.
I realize my question wasn't related to Rails. I shouldn't use timestamp to store epoch time.
What is the data type for unix_timestamp (MySQL)?
Instead, I should use int(11) to store it.
Back to my question. If I want to store timestamp, I should use the time object in Rails.
EX:
Kline.create(open_time: Time.now)
(1.0ms) BEGIN
SQL (1.4ms) INSERT INTO `klines` (`open_time`, `created_at`, `updated_at`) VALUES ('2020-05-14 01:49:15', '2020-05-14 01:49:15', '2020-05-14 01:49:15')
(1.2ms) COMMIT

Rails 5 not saving dates correctly

I'm having a weird issue where Rails is not saving a date correctly. I've tried formatting the date a number of ways, including as an actual date object as well as various string formats, but it's wanting to swap the month and day regardless. When this results in an invalid date (ie 28/08/2019 as mm/dd/yyyy), it fails to include the parameter all together when saving.
If the date is valid when swapped (ie 2019-09-03 ==> 2019-03-09), it will save the incorrect (swapped) date.
params[:shift_date] = Date.strptime(shift_params[:shift_date], '%m/%d/%Y').to_date
#shift.save(params)
Results in...
<ActionController::Parameters {"shift_date"=>Tue, 03 Sep 2019, "start_time"=>"00:00:00", "end_time"=>"00:00:00"} permitted: true>
(0.1ms) BEGIN
↳ app/controllers/api/shifts_controller.rb:25
Shift Create (0.4ms) INSERT INTO "shifts" ("shift_date", "start_time", "end_time") VALUES ($1, $2, $3) RETURNING "id" [["shift_date", "2019-03-09"], ["start_time", "00:00:00"], ["end_time", "00:00:00"]]
With a date like '08/29/2019', again correctly interpreted into a Date object, when saved it will be omitted since 2019-29-08 is not a valid date. I've also tried converting to a string 2019-08-29, 2019-08-29 00:00:00, etc etc, and it always get its wrong no matter what I do. I'm very surprised passing a correctly set and valid Date object that Rails would change it at all...
Note this only happens in the controller. Via the console everything works as expected...
2.6.3 :076 > s.shift_date = Date.strptime('08/28/2019', '%m/%d/%Y')
=> Wed, 28 Aug 2019
2.6.3 :077 > s.save
(0.2ms) BEGIN
Shift Update (0.4ms) UPDATE "shifts" SET "shift_date" = $1, "updated_at" = $2 WHERE "shifts"."id" = $3 [["shift_date", "2019-08-28"], ["updated_at", "2019-08-28 20:48:31.944877"], ["id", 54]]
(0.3ms) COMMIT
=> true

Rails is saving one time, database returns another?

I am saving a time into database and I get a very different value back from the database.
2.1.0 :047 > Schedule.create(last_reminded_at: Time.now)
(0.9ms) BEGIN
SQL (1.1ms) INSERT INTO "schedules" ("last_reminded_at")
VALUES ($1) RETURNING "id" [["last_reminded_at", "2014-12-13 22:14:16.022267"]]
(8.3ms) COMMIT
=> #<Schedule id: 8, ... last_reminded_at: "2014-12-13 23:14:16">
Schedule is created with correct time, but when I get it back from the db, the time is always 1st Jan 2000.
2.1.0 :048 > Schedule.last
Schedule Load (0.9ms) SELECT "schedules".* FROM "schedules" ORDER BY id DESC LIMIT 1
=> #<Schedule id: 8, ... last_reminded_at: "2000-01-01 22:14:16">
Looking at the query, I suspect the insert statement is somehow incorrect?
Maybe I need to configure ActiveRecord timezone somehow?
This is the only time related config I have, in config/application.rb:
Time.zone = 'UTC'
The schema is:
create_table "schedules", force: true do |t|
....
t.time "last_reminded_at"
end
I am running Rails 4.1.8, Postgresql 9.2.6 and Ruby 2.1.2p95.
Your schema has set last_reminded_at to a time. You want a datetime as you care about the date too.

Ruby:Change UTC time zone to UTC+ or UTC - 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

Attribute available before_type_cast but nil when accessed directly

I have an ActiveRecord object that has a before_create hook that generates a SHA hash and stores the result in an attribute of the object:
before_create :store_api_id
attr_reader :api_id
def store_api_id
self.api_id = generate_api_id
end
private
def generate_api_id
Digest::SHA1.hexdigest([Time.now.nsec, rand].join).encode('UTF-8')
end
This works in that the api_id attribute is created and stored in the database as text (which is forced by the call to .encode('UTF-8') otherwise SQLite will try to store the result as binary data.
However the following specs are failing:
it "should be available as ad.api_id" do
#ad.save!
#ad.api_id.should_not be_nil
end
it "should match the directly accessed attribute" do
#ad.save!
#ad.api_id.should == #ad.attributes['api_id']
end
I can get the correct hash by using ad.api_id_before_type_cast and ad.attributes['api_id'] but not when using ad.api_id.
Ad.find_by_api_id('string api id') also works as expected, but still returns null when calling .api_id on the returned object.
I have double-checked the type in the database as follows:
sqlite> select typeof(api_id) from ads;
text
Here is an example rails console session on a fresh Rails 3.2.2 / ruby 1.9.3-p125 app:
Loading development environment (Rails 3.2.2)
irb(main):001:0> ex = Example.new
=> #<Example id: nil, api_id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> ex.save! (0.1ms) begin transaction
SQL (7.3ms) INSERT INTO "examples" ("api_id", "created_at", "updated_at")
VALUES (?, ?, ?) [["api_id", "fc83bb94420cf8fb689b9b33195318778d771c4e"],
["created_at", Fri, 23 Mar 2012 10:17:24 UTC +00:00],
["updated_at", Fri, 23 Mar 2012 10:17:24 UTC +00:00]]
(1.0ms) commit transaction
=> true
irb(main):003:0> ex.api_id
=> nil
irb(main):004:0> ex.api_id_before_type_cast
=> "fc83bb94420cf8fb689b9b33195318778d771c4e"
irb(main):005:0> ex.attributes['api_id']
=> "fc83bb94420cf8fb689b9b33195318778d771c4e"
irb(main):006:0>
As I wrote above, using attr_readonly instead of attr_reader to protect the attribute fixed this issue for me, and in this case is actually closer to what I want.
As the API docs note:
Attributes listed as readonly will be used to create a new record but update operations will ignore these fields.

Resources