What is the best way for me to handle dates and timezones in rails?
Scenerio: I have customers who purchase products on a website from all over the world, and when they log in they will be able to choose which timezone they are from.
So I believe I should be storing everything in the database at UTC, and then on the front-end I should be converting the dates to the users set timezone preference.
Are their any gotchas with Ruby and Rails and datetimes etc?
I'm new to rails, so I am looking for guidance on how to handle this properly.
Fortunately Rails will pretty much handle things for you. As others pointed out, dates are stored by AR in UTC format. If you have a time_zone field for your users table you can do something like this:
# application.rb
config.time_zone = "Mountain Time (US & Canada)" # Default time zone
-
# application_controller.rb
before_filter :set_time_zone, :if => :logged_in?
protected
def set_time_zone
Time.zone = current_user.time_zone if current_user.time_zone
end
All the datetimes should be shown in the proper time zone in your views.
I have had one production app that didn't like using the default time zone, but I can't remember which version of Rails/Ruby it was running.
Ok so take a look at your config/application.rb file.
You should find commented lines:
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
So default is UTC but you can set whatever ou need there.
Yes, they are. In app, whenever you display date or time for user, everything you need is just adding timezone offset (for example: date + 1.hour for GMT+1). Remember that you need to take care of daylight saving, too. For efficency, consider adding 2 columns in your user table: timezone and time_offset. Then you would on each case do something like
= #order.created_at + session[:user].time_offset
Instead of always checking offset for each timezone set in profile.
I found
rake time:zones:all
to be really useful. It shows a list of offsets and then zone name strings under that. eg:
* UTC +12:00 *
Auckland
Fiji
Kamchatka
Magadan
Marshall Is.
Solomon Is.
Wellington
I needed below in application.rb (not intuitive given default time zone string of "Mountain Time (US & Canada)"):
config.time_zone = 'Wellington'
Related
Is there actually a way to have Rails add/update rows with created_at and update_at using the current time zone as set on my server?
I have seen many Stack Overflow solutions where people stated how to display it on a Rails view with a selected time zone.
I have also found other Stack Overflow solutions stating that it should take the time zone from my server and update created_at and updated_at. This is not true at least for the Mac Mini Server I'm running on. I have it set to my local time zone. I also have config.time_zone = 'Central Time (US & Canada)' set in config/application.rb.
I want to be able to look at the raw database data in pgAdmin3 or some kind of database backup and know when records have been created in my time zone.
Any help would be appreciated. I will keep looking.
If you want to set the db timezone and you are using ActiveRecord, then add the following in your application config:
config.active_record.default_timezone = :local
This will use your sever's timezone on the database.
# application.rb:
class Application < Rails::Application
config.time_zone = 'Eastern Time (US & Canada)'
end
Time.zone # => #<TimeZone:0x514834...>
Time.zone.name # => "Eastern Time (US & Canada)"
Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
Please refer following link >>http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html#method-c-5B-5D
You can also run rake time:zones:all in order to list all time zones Rails knows. You can then update your setting in application.rb accordingly.
I am using Ruby on Rails 4 and I would like to handle data present in cookies the proper way. That is, when I store to cookie data as-like (GMT-06:00) Central Time (US & Canada)
cookies.permanent[:time_zone] = "(GMT-06:00) Central Time (US & Canada)"
then in the cookie content it is stored %28GMT-06%3A00%29+Central+Time+%28US+%26+Canada%29.
That way, when I use the cookies[:time_zone] data in my code
Time.use_zone(cookies[:time_zone], &block)
then I get ArgumentError Invalid Timezone: (GMT-06:00) Central Time (US & Canada).
I think the problem is due to the fact that the cookies[:time_zone] data is read from cookies "as it is" (%28GMT-06%3A00%29+Central+Time+%28US+%26+Canada%29), so it generates the error when called in Time.use_zone.
How should I read / write the cookie data?
UPDATE after #Matt Johnson comment
My "hadcode" is
time_zone = cookies[:time_zone] || Time.zone
so that I can use the time_zone variable for my matters. However, I noted that Time.zone returns always
(GMT-06:00) Central Time (US & Canada)
# instead of "Central Time (US & Canada)"
It is the problem: in my "hardcode" when the cookie is nil then Time.zone should return the proper value for my matters, that is Central Time (US & Canada).
So my question become: how should I handle the issue in order to properly set the time_zone variable?
Notes: The Time.zone documentation states that the method
Returns the TimeZone for the current request, if this has been set
(via ::zone=). If Time.zone has not been set for the current request,
returns the TimeZone specified in config.time_zone.
and in my application.rb file I have set
`config.time_zone = 'Central Time (US & Canada)'`
Rails time zones are listed here. In this particular case, you should drop the first part of your string and just pass "Central Time (US & Canada)".
You may also want to read the timezone tag wiki, which has a section about Rails time zones toward the bottom.
Updated Answer
Based on your update, I can see that you are using Time.zone. This doesn't return a string, but returns a TimeZone object. So you are getting the effect of TimeZone.to_s() which includes the GMT portion.
You should instead use Time.zone.name, which just returns the string identifier of the time zone.
I'm confused on how rails 3 timezones are supposed to work.
So I config rails to use Pacific time, and tell active record to store in Pacific time.
# application.rb
config.time_zone = 'Pacific Time (US & Canada)'
config.active_record.default_timezone = 'Pacific Time (US & Canada)'
Now I submit update a model and this comes through in the params:
"start_at"=>"2013-07-24 00:00:00"
From the console now:
>> Sale.last
=> #<Sale id: 24, start_at: "2013-07-24 00:00:00", ...snipped... >
>> Sale.last.start_at
=> Tue, 23 Jul 2013 17:00:00 PDT -07:00
>> Sale.last.start_at.in_time_zone
=> Tue, 23 Jul 2013 17:00:00 PDT -07:00
So after trying to force everything to Pacific time, its creating time objects form the database by factoring in the -7 hours of Pacific time.
If I set a time to 2013-07-24 00:00:00 I would expect Tue, 24 Jul 2013 00:00:00 PDT -07:00 to come back out. And yet it does not. I was having similar confusing issues when active record was using UTC to store dates. I had a few tests that would fail only after 5pm when time to date conversions yielded a different date.
How do I tame this? Storing UTC dates in the database seems like a good idea, and I can use in_time_zone on time objects for display, but does that means that times in forms must be UTC?
Our application functionality is very tied to server time, and certain thing happen every day as specific times. So forcing everything to Pacific time seems like it should be fine. But so far I can't seem to make this behave consistently.
How do I make all this not suck?
This is going to be a 1/2 answer, re-iterating from comment thread above with some additional information
I hope to update with more later. There are many gotchas with this stuff
UPDATE: finally did a blog post on rails timezones
http://jessehouse.com/blog/2013/11/15/working-with-timezones-and-ruby-on-rails/
See also: http://www.elabs.se/blog/36-working-with-time-zones-in-ruby-on-rails
i would use UTC for the application timezone and the activerecord default. Use I18n.localize method (setup formats in config/locales/en.yml) for display of dates and datetimes; set the current threads timezone based on user settings or a default (whatever makes sense for your app)
if the current thread is set to the users timezone activerecord should do the right magic and save the offset UTC value in the db, then when you display that value to the user with I18n it will display it as the user entered it (converting from UTC to the users timezone) - it gets a bit tricky when you are entering a time for say an event that is taking place in another timezone - in that case the thread needs to be set to the timezone of that location and using I18n displays need to be for the location instead of the user
setting current users timezone
see http://railscasts.com/episodes/106-time-zones-revised which uses around filter
example below uses before_filter
both assume a time_zone column on the user account
some code
class ApplicationController < ActionController::Base
protect_from_forgery
# auth the user
before_filter :authenticate_user!
# Set user's time zone
before_filter :set_user_time_zone
# ....
def set_user_time_zone
if current_user and current_user.time_zone.present?
Time.zone = current_user.time_zone
# else some default?
end
end
end
Alternatively - set based on browser settings, etc...
see http://guides.rubyonrails.org/i18n.html
specifically: http://guides.rubyonrails.org/i18n.html#setup-the-rails-application-for-internationalization
display date times
Use I18n.localize aka I18n.l or just l - see http://guides.rubyonrails.org/i18n.html#adding-date-time-formats
changing the timezone display for part of a view
User is set to Pacific but showing an event time for an event taking place in Eastern
Time.use_zone(event.location.time_zone) do
puts event.start_time
end
WARNING: I have found the above does not work correctly if event was pulled using find_by_sql method, regular active record queries work well
looking for a way to get my own time zone that I can pass to config.time_zone in application.rb order to set it in Rails. Background: I'm allowing the user to specify their timezone in a yaml config, but if it's not set, I want to explicitly use the box's timezone. I have to have a value set because I'm using it as reference to convert timezones for display -- each user logging into the Rails app can set their personal timezone and I will do the conversion for them.
However, there seems to be no good API for doing this in Ruby or Rails. Time.now.zone returns the 3-letter code (EDT or CDT, for example), but you can't pass this because it's not specific enough -- the TZInfo class will only accept the "long" descriptions.
Here's what I'm doing now, which seems pretty hacky:
time_zone = CONFIG[:time_zone] # set your local time zone here. use rake time:zones:local to choose a value, or use UTC.
unless time_zone
# try to detect one
if File.exists?('/etc/localtime')
path = File.readlink('/etc/localtime')
items = path.split("zoneinfo/")
if items.length == 2
time_zone = items[1]
end
end
unless time_zone
puts "*** Warning: no time zone is set in config/config.yaml and could not detect system time. Using UTC as the default time; behavior may be unexpected."
time_zone = "UTC"
end
end
config.time_zone = time_zone
Any better ideas guys?
Any reason why this wouldn't work?:
Time.now.zone
Edit:
Before getting the zone, you can do Time.now.gmt_offset to get your GMT offset in seconds. After that, you can do Time.now.zone to get your zone code.
You can try the following:
off_set = Time.now.gmt_offset
p ActiveSupport::TimeZone[off_set].name # "Atlantic Time (Canada)"
or
p ActiveSupport::TimeZone[off_set].tzinfo.name # "America/Halifax"
Since the 3-letter codes are not precise enough, have the user specify the full name from the IANA time zone database.
Then you can use Time.zone = 'America/Halifax' (for example).
Time.zone.name returns back the IANA name, and Time.zone.now returns the current time in the specified timezone.
To find the timezone in this format programically, you may be able to use /etc/timezone if it's available. Other systems have other methods (if there is even one available).
If you want the time zone offset in hours, as some databases do (like Postgres), you can use this method:
2.3.1 :063 > Time.zone.formatted_offset
"-07:00"
or this approach if you need the offset in seconds:
2.3.1 :059 > Time.zone.utc_offset
-25200
My site is deployed on heroku. Time.now will return today, but the created_at field of a record (created right now) will say its tomorrow. I assume this has to do with server time?
Is there a way to make sure they're the same?
Best,
Elliot
Update so I did this "heroku rake time:zones:us"
it gave me:
* UTC -10:00 *
Hawaii
* UTC -09:00 *
Alaska
* UTC -08:00 *
Pacific Time (US & Canada)
* UTC -07:00 *
Arizona
Mountain Time (US & Canada)
* UTC -06:00 *
Central Time (US & Canada)
* UTC -05:00 *
Eastern Time (US & Canada)
Indiana (East)
however, when I set config.time_zone = 'UTC -05:00' in my environment, the app fails to start. any ideas?
Rails always stores UTC time on the database; the created_at field by itself should be offset by exactly your timezone's variation relative to UTC.
Whenever you load a record in your application, the fields get converted to the timezone specified in environment.rb. It might have something like this:
config.time_zone = 'UTC'
For the time to be converted properly to your timezone, you might change this configuration setting to one matching your actual time zone. For instance:
config.time_zone = 'Central Time (US & Canada)'
To see available zones, issue "rake -D time" on your rails directory. This will give you instructions on how to get time zone names for use in configuration.
To add onto Roadmaster's answer, I had a similar challenge: the normal Rails timestamps were stored based on UTC in the database, but I needed to query to find all records created today according to the local time zone.
The query looked like this:
completions.where("created_at BETWEEN ? AND ?",
Date.today, Date.today + 1.day).count >= 1
I fixed this by calling #to_time on the dates, as follows. This converted them into a timestamp having the proper time zone, and the correct records were fetched in the database, effectively making the query timezone-aware.
completions.where("created_at BETWEEN ? AND ?",
Date.today.to_time, Date.today.to_time + 1.day).count >= 1
Just need to uncomment and change to the time zone you wanna.
If you want to check all the time zone, run rake time:zones:all and will output a list.
config/Application.rb
module Clerk
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = 'La Paz'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
end
end
I added the following to lines of code. I wanted the created_at to be on the ActiveRecord data creation.
config/Application.rb
module RailsApp
class Application < Rails::Application
config.time_zone = 'Pacific Time (US & Canada)'
config.active_record.default_timezone = 'Pacific Time (US & Canada)'
end
end