Save Datetime in UTC, view/query in User's timezone in Rails? - ruby-on-rails

What is the standard for saving times in one zone and viewing them in another? I have this in my environment.rb:
config.time_zone = 'UTC'
But I want to render it, and do queries like "all posts created today" in their timezone, where beginning_of_day returns the beginning of the day in their timezone rather than UTC. Is rails already handling the conversion in the background?
Here's the issue:
#now = Time.now
=> Wed Jan 26 09:50:04 -0600 2011
User.count(:conditions => ["created_at > ?", #now])
SQL (1.3ms) SELECT count(*) AS count_all FROM `users` WHERE (created_at > '2011-01-26 09:50:04')
=> 1
#now = Time.now.utc
=> Wed Jan 26 15:50:10 UTC 2011
User.count(:conditions => ["created_at > ?", #now])
SQL (1.2ms) SELECT count(*) AS count_all FROM `users` WHERE (created_at > '2011-01-26 15:50:10')
=> 0

ActiveSupport has built-in methods to display time values in any time zone. Typically you'd add a time_zone column to your User model and set it to the user's preferred zone.
#user.update_attribute(:time_zone,'Eastern Time (US & Canada)')
Then when displaying a time value, set the zone to the user's zone.
Time.zone = #user.time_zone
Time.zone.now # shows current time according to #user.time_zone
One approach is to set this in ApplicationController so it is done for each request:
class ApplicationController < ActionController::Base
before_filter :set_time_zone
def set_time_zone
Time.zone = current_user.time_zone if current_user
end
end
Note: see the API under ActiveSupport::TimeWithZone

Related

Issue Formatting DateTime in Rails

So my rails application supports different time zones and sets the time zone at each request like so:
def set_current_time_zone
Time.zone = current_user.time_zone
end
All of my formatting and reports site wide work except for a jQuery date time selector.
The form submits a start time in the format of 04/20/2018 5:23 AM. When I parse the time with the code below I get the following output.
# this is the value of params[:job][:start] 04/20/2018 5:23 AM
job.start = DateTime.strptime(params[:job][:start],"%m/%d/%Y %H:%M %p")
#printing job.start outputs this: 2018-04-20 00:23:00 -0500
As you'll notice the time zone has been applied to the time and is now the wrong time. How do I correct this?
Thanks in advance.
Keep in mind DateTime.strptime('04/20/2018 5:23 AM',"%m/%d/%Y %H:%M %p") will return the value in UTC =>Fri, 20 Apr 2018 05:23:00 +0000
However if you store this value say to a database field, in an active record object, it will output the timezone value to whatever value you've set to Time.zone
This may not be a complete answer but could lead you in the right direction.
Add some more details to my last comment above.
I did this in rails console, my model is Shift and has start_time: datetime.
2.1.3 :022 > time = "04/02/2018 5:23 AM"
=> "04/02/2018 5:23 AM"
2.1.3 :023 > Time.zone = "Singapore"
=> "Singapore"
2.1.3 :024 > shift.start_at = time
=> "04/02/2018 5:23 AM"
2.1.3 :025 > shift.save
(0.1ms) begin transaction
SQL (0.5ms) UPDATE "shifts" SET "start_at" = ?, "updated_at" = ? WHERE "shifts"."id" = 2 [["st
art_at", "2018-02-03 21:23:00.000000"], ["updated_at", "2018-05-02 05:43:38.527338"]]
(1.4ms) commit transaction
=> true
2.1.3 :026 > Time.zone = "Dublin"
=> "Dublin"
2.1.3 :027 > shift.start_at = time
=> "04/02/2018 5:23 AM"
2.1.3 :028 > shift.save
(0.2ms) begin transaction
SQL (0.4ms) UPDATE "shifts" SET "start_at" = ?, "updated_at" = ? WHERE "shifts"."id" = 2 [["st
art_at", "2018-02-04 05:23:00.000000"], ["updated_at", "2018-05-02 05:45:44.638291"]]
(1.4ms) commit transaction
=> true
See the data inside the SQL.

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

Time.new subtracting 6 hours AND tacking on CST -6 on Heroku?

I'm sure there's some conversion thing I'm looking over here.
On Heroku's console,
irb(main):052:0> Time.new(2014, 1, 21)
=> 2014-01-21 00:00:00 +0000
However, setting a column to that:
irb(main):042:0> PressRelease.first.update_attribute :published_on, Time.new(2014, 1, 21)
PressRelease Load (1.9ms) SELECT "press_releases".* FROM "press_releases" ORDER BY created_at DESC LIMIT 1
(1.0ms) BEGIN
FriendlyId::Slug Load (0.8ms) SELECT "friendly_id_slugs".* FROM "friendly_id_slugs" WHERE "friendly_id_slugs"."sluggable_id" = 1 AND "friendly_id_slugs"."sluggable_type" = 'PressRelease' ORDER BY "friendly_id_slugs".id DESC LIMIT 1
(0.6ms) COMMIT
=> true
Gives this date:
=> Mon, 20 Jan 2014 18:00:00 CST -06:00
To clarify, application.rb does indeed have the time zone set:
config.time_zone = 'Central Time (US & Canada)'
And when I check on their console:
irb(main):054:0> Time.zone
=> (GMT-06:00) Central Time (US & Canada)
However, doing this locally works fine:
1.9.3-p448 :011 > Time.new(2014, 1, 21)
=> 2014-01-21 00:00:00 -0600
So, it looks like Heroku is subtracting -6 (since our Time Zone is set to CST -6), then tacking on the timezone of CST -6 as well. Why? This is, as you can see, messing up date-specific items.
If you want Rails to use the time_zone setting, you need to use the wrappers provided by ActiveSupport::TimeZone. See docs: http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html
Here's an example:
Time.zone.parse("2014-1-21")
# Outputs Tue, 21 Jan 2014 00:00:00 MST -07:00

Rails: Clarification (rules-of-thumb?) for Date/Time, to_time and time zones

tl;dr: what are the rules for working with Date, Time & Datetime so I'm assured consistency across my apps?
I'm trying to wrap my head around working with Dates & Times & Zones in Rails so I don't accidentally use UTC when I really want all "user facing" dates/times adjusted for time zone. I'm noticing what appears, to me at least, to be some inconsistency and I'm hoping to understand rules or logic behind them so I'm not "surprised" again.
1.9.3p194 :028 > e = Event.find(1)
Event Load (0.3ms) SELECT "events".* FROM "events" WHERE "events"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Event id: 1, start_at: "2012-08-27 19:15:00", end_at: "2012-08-27 21:00:00", created_at: "2012-08-22 07:43:31", updated_at: "2012-08-23 03:01:59">
1.9.3p194 :037 > e.start_at # <== start_at is DateTime in model
=> Mon, 27 Aug 2012 12:15:00 PDT -07:00
1.9.3p194 :036 > e.start_at.to_time
=> 2012-08-27 19:15:00 UTC ### <=== This is in UTC.. ok...
1.9.3p194 :034 > DateTime.now
=> Thu, 23 Aug 2012 10:44:16 -0700 # <=== Also a DateTime
1.9.3p194 :035 > DateTime.now.to_time
=> Thu, 23 Aug 2012 10:44:19 -0700 ### <=== But this is in Pacific Time ?!?
2 differing responses from to_time? Or did I miss something?
The fairly cryptic documentation for DateTime's to_time doesn't make any mention of timezone:
to_time()
Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class. If self has an offset other than 0, self will just be returned unaltered, since there’s no clean way to map it to a Time.
So what are "rules" for getting consistent dates & times out of Rails?
I'm assuming you have a start_at column in your database. As a result e.start_at is not an instance of DateTime but an instance of Time. (Try calling e.start_at.class to see it)
Instead of using DateTime.now I'd recommend using Rails' Time.zone.now which will return the current time in the timezone defined in Time.zone.
1.9.3p125 :005 > Time.zone
=> (GMT+00:00) UTC
1.9.3p125 :006 > Time.zone.now
=> Thu, 23 Aug 2012 20:10:34 UTC +00:00
1.9.3p125 :007 > Time.zone = "Berlin"
=> "Berlin"
1.9.3p125 :008 > Time.zone
=> (GMT+01:00) Berlin
1.9.3p125 :009 > Time.zone.now
=> Thu, 23 Aug 2012 22:10:47 CEST +02:00

Application displays wrong hours and minutes

At first: thats not issue of wrong zone. In irb and database everything is fine. Problem occurs when I want to display dates (created_at, updated_at and all defined by myself in every model) in my views. I was trying to set time zone in application.rb and remove time formats from initializers and that didn't solved my issue.
schema information generated by Annotate gem:
# created_at :datetime
# updated_at :datetime
# publish_at :datetime
from irb:
1.9.2-p290 :004 > Time.zone
=> (GMT+00:00) UTC
1.9.2-p290 :005 > Time.zone.now
=> Fri, 24 Feb 2012 12:14:04 UTC +00:00
1.9.2-p290 :006 > Time.now
=> 2012-02-24 13:14:07 +0100
Examples:
1.9.2-p290 :007 > Article.last
Article Load (0.3ms) SELECT `articles`.* FROM `articles` ORDER BY `articles`.`id` DESC LIMIT 1
=> #<... created_at: "2012-02-24 12:04:24", updated_at: "2012-02-24 12:04:24", publish_at: "2012-02-24 12:04:24"...>
App displays:
Created_at 2012-02-24 12:02,
Updated_at 2012-02-24 12:02,
Publish_at 2012-02-24 12:02,
1.9.2-p290 :008 > Article.first
Article Load (0.5ms) SELECT `articles`.* FROM `articles` LIMIT 1
=> #<...created_at: "2012-01-30 10:28:07", updated_at: "2012-02-08 17:20:41", publish_at: "2012-02-08 17:20:33"...>
App displays:
Created_at 2012-01-30 10:01,
Updated_at 2012-02-08 17:02,
Publish_at 2012-02-08 17:02
this applies whole app (including active_admin)
Sorry for my English ;) Any ideas what is the reason of my problem?
I've got it :) That was silly, but maybe someone will need reminder: App gets format from locale yml file first (thats only way to set date format in active admin; formats from initializers doesn't matter):
time:
formats:
default: "%Y-%m-%d"
short: "%b %d"
long: "%Y-%m-%d %H:%m"
and ofc minutes in long format should be %M (not %m)

Resources