Time Zone
This article will teach you how to configure the time zone in your rails application and how to switch the time zone according to user configuration.
Table of Contents
- Why do we need to configure the time zone?
- What is UTC time standard?
- Time Zone Configuration
- Switching the time zone according to user configuration
Why do we need to configure the time zone?
Some websites are being developed and used in multiple countries, crossing multiple borders and time zones. Thus, the differences in time zones must be accounted for and standards must be applied when you are building your own project. Every data in your website should follow a single standard time.
What is UTC time standard?
Rails follows the UTC time standard. UTC is the primary time standard by which the world regulates clocks and time. To obtain your local time, you need to offset a certain number of hours from UTC depending on how many time zones you are away from Greenwich, England. In our case, we are 8 time zones ahead of Greenwich, hence, our UTC time zone is +0800
.
The database always stores the date and time in UTC
+0000
format, so you must always adjust the time zone in your rails project based on the user’s local time zone to ensure that the time is displayed correctly in user’s side.
Time Zone Configuration
Before you configure your time zone, let us first observe the behavior of Rails while using the default time zone.
<!-- app/views/posts/index.html.erb -->
<!-- ... -->
<table>
<td><%= Post.human_attribute_name(:title) %></td>
<td><%= Post.human_attribute_name(:content) %></td>
+ <td>created at</td>
<!-- ... -->
</thead>
<% @posts.each do |post| %>
<tr>
<td><%= post.title %></td>
<td><%= post.content %></td>
+ <td><%= post.created_at %></td>
<!-- ... -->
</tr>
<% end %>
</table>
<!-- ... -->
As you can see, the created_at
in each post ends with UTC. And if you create a new post and check the time of its created_at
, it lags behind the original time of creation by 8 hours. This is because the default time zone in rails is UTC
which follows the current time in Greenwich, London, England and has an offset of +0000, while the standard time in the Philippines has an offset of +0800.
To fix this you need to change your project’s time zone in config/application.rb
to a location with a UTC offset of +0800, in our case Hong Kong
.
#config/application.rb
#...
module App
class Application < Rails::Application
#...
- # config.time_zone = "Central Time (US & Canada)"
+ config.time_zone = 'Hong Kong'
#...
end
end
#...
Aside from Hong Kong
you can also choose other locations as your time zone. You can refer to Rails Time Zone or run the following in ruby console:
irb(main):007:0> ActiveSupport::TimeZone.all.map(&:name)
=>
["International Date Line West",
"American Samoa",
"Midway Island",
"Hawaii",
"Alaska",
"Pacific Time (US & Canada)",
"Tijuana",
"Arizona",
"Mazatlan",
#...
]
After restarting your server, check the changes in created_at
column.
If you take a closer look, the Time of created_at
now ends with +0800
instead of UTC
. It has also advanced by 8 hours from the previous time(before we configured the timezone). If you make a new post, the created_at
time will be the same as your current system time, unlike before.
Switching the time zone according to user configuration
A common practice is to add a new field time_zone:string
on User
to store the user’s preferred time zone.
Generate migration to add time zone in users.
root@0122:/usr/src/app# rails generate migration add_time_zone_to_users
invoke active_record
create db/migrate/xxxxxxxxxxxxxx_add_time_zone_to_users.rb
#db/migrate/xxxxxxxxxxxxxx_add_time_zone_to_users.rb
class AddTimeZoneToUsers < ActiveRecord::Migration[7.0]
def change
+ add_column :users, :time_zone, :string
end
end
run rails db:migrate
.
root@0122:/usr/src/app# rails db:migrate
== xxxxxxxxxxxxxx AddTimeZoneToUsers: migrating ===============================
-- add_column(:users, :time_zone, :string)
-> 0.0162s
== xxxxxxxxxxxxxx AddTimeZoneToUsers: migrated (0.0163s) ======================
Add the routes for user.
#config/routes.rb
Rails.application.routes.draw do
#...
+ resource :user
end
The routing design here uses a singular
resource :user
instead of the more commonly usedresources :users
. Routes with singularresource
have noindex
page, and if you check the generated paths/urls, you will see that it doesn’t have id params. For example:resource :user
will generate the edit path/user/edit
, whileresources :users
will generate the edit path/users/:id/edit
. The reason for this design is that the users can only edit their own data, they are prohibited to edit other user’s data. Thus, id params has no use and we will not select our user usingparams[:id]
, we can use the current_user helper to select the user instead. Another side benefit is that the id will not appear on the URL bar.
Reminder:
Regardless whether
resource
is singular or plural, default controller names are always plural.
Generate the users controller.
root@0122:/usr/src/app# rails generate controller users
create app/controllers/users_controller.rb
invoke erb
create app/views/users
invoke helper
create app/helpers/users_helper.rb
#app/controllers/users_controller.rb
class UsersController < ApplicationController
+ before_action :authenticate_user!
+
+ def edit
+ @user = current_user
+ end
+
+ def update
+ @user = current_user
+ if @user.update(user_params)
+ flash[:notice] = 'Updated successfully'
+ redirect_to edit_user_path
+ else
+ render :edit
+ end
+ end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:time_zone)
+ end
end
create the form in app/views/users/edit.html.erb
<!--app/views/users/edit.html.erb-->
<h2>Edit User</h2>
<%= form_for @user do |f| %>
<div class="form-group">
<%= f.label :time_zone %>
<%= f.time_zone_select :time_zone %>
</div>
<div class="form-group">
<%= f.submit 'Save', class: 'btn btn-primary' %>
</div>
<% end %>
Add the link to edit user
in application layout
<!--app/views/layouts/application.html.erb-->
<!DOCTYPE html>
<html>
<!--...-->
<body>
<!--...-->
<% if flash[:alert] %>
<p class="alert"><%= alert %></p>
<% end %>
+ <%= link_to 'edit user', edit_user_path if current_user%>
<!--...-->
</body>
</html>
Now that the users can save their preferred timezone, you must now create a method that changes the time zone to the one that the user sets.
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_locale
+ before_action :set_timezone
#...
+ def set_timezone
+ if current_user && current_user.time_zone
+ Time.zone = current_user.time_zone
+ end
+ end
#...
end
This sets which time zone Rails is currently using. Please try to change the user’s time zone into a different one, and then observe the created_at
time in posts index page.