There are trhee diffrent time zones in rails
- System - The server time zone
- Application - Rails application use own time zone
- Database - Default is
UTC
, but do not change it
config.time_zone
should be set when an app dose not support multiple time zones- Do not use Date/Time API using system time zone
- Date could be incorrect by being converted from datetime or time to date
- Use
date
type instead ofdatetime
type for handing only date if an app dose not support multiple time zones datetime
andin_time_zone
should be used in multiple time zones- Do not use
DateTime.new
for saving and querying because time zone is not applied unless an app use UTC time zone - Use
find_zone
to make specific time zone time - Do not use
in_time_zone
withnew
forDateTime
orTime
to make specific time zone time
- Application time zone - Default is
UTC
unlessTime.zone="Alaska"
orconfig.time_zone = "Pacific/Auckland"
- System - This depends on how Server OS time is set up
Do not use date/time API using the system time zone
# Do not use below, because it uses the system time zone
Time.now
DateTime.now
Date.today
Date.today.to_time
Time.parse("2015-07-04 17:05:37")
Time.strptime(string, "%Y-%m-%dT%H:%M:%S%z")
Time.strptime("2020-02-19 00:00","%Y-%m-%d %H:%M").in_time_zone
# Use below
Time.zone.now
Time.zone.today
Time.current
2.hours.ago
Date.current
1.day.from_now
Time.zone.parse("2015-07-04 17:05:37")
Also, Do not use Time.zone
in your application code
# bad smell
Time.zone = "Alaska"
# use below
# After executing the block, the original time zone is set back
Time.use_zone("Alaska", &block)
When datetime or time data is saved or read from database, the data is converted accroing to timezone
- Save & Query - The datetime is changed to
UTC
- Read - The datetime is changed to application timezone
# published is datetime
# and current time is Tue, 25 Feb 2020 12:26:36 in New Zealand
# time zone is set with "Pacific/Auckland"
Product.create!(published: Date.new(2020,02,24))
# The published is 2020-02-23 11:00:00, which is UTC
Product.create!(published: Time.new(2020,02,24))
# The published is 2020-02-23 11:00:00
Product.create!(published: Date.today)
# The published is 2020-02-24 11:00:00
Product.create!(published: Date.current)
# The published is 2020-02-24 11:00:00
# time zone is not set, so "UTC"
Product.create!(published: Date.new(2020,02,24))
# The published is 2020-02-24 00:00:00
Product.create!(published: Date.new(2020,02,24).in_time_zone("Pacific/Auckland"))
# The published is 2020-02-23 11:00:00
Product.create!(published: Time.new(2020,02,24))
# The published is 2020-02-23 11:00:00
Product.create!(published: Date.today)
# The published is 2020-02-25 00:00:00
Product.create!(published: Date.current)
# The published is 2020-02-24 00:00:00
All other datetime columns like created_at
and updated_at
are also UTC
Rails record is converted to UTC using to_s(:db)
Date.new(2020,02,24).to_s(:db)
=> "2020-02-24"
# in order to check saving to datetime type column, should add in_time_zone
Date.new(2020,02,24).in_time_zone.to_s(:db)
=> "2020-02-23 11:00:00"
Time.new(2020,02,24).in_time_zone.to_s(:db)
=> "2020-02-23 11:00:00"
# time zone is set with "Pacific/Auckland"
# The published is 2020-02-23 11:00:00
Product.last.published
=> Mon, 24 Feb 2020 00:00:00 NZDT +13:00
# The published is "2020-02-24 00:00:00
Product.last.published
=> Mon, 24 Feb 2020 13:00:00 NZDT +13:00
# time zone is not set, so "UTC"
# The published is 2020-02-23 11:00:00
Product.last.published
=> Sun, 23 Feb 2020 11:00:00 UTC +00:00
# The published is "2020-02-24 00:00:00
Product.last.published
=> Mon, 24 Feb 2020 00:00:00 UTC +00:00
When saving and displaying date, the date could be incorrect if timezone is just default UTC
# timezone is UTC
# created_at is 2020-02-23 23:26:36
Product.last.created_at
=> Sun, 23 Feb 2020 23:26:36 UTC +00:00
Product.last.created_at.to_date
=> Sun, 23 Feb 2020 # Should be Mon, 24 Feb 2020
# time zone is set with "Pacific/Auckland"
Product.last.created_at
=> Mon, 24 Feb 2020 12:26:36 NZDT +13:00
Product.last.created_at.to_date
Product.last.created_at.in_time_zone("Pacific/Auckland")
=> Mon, 24 Feb 2020
Also date could be incorrect when time information is cutted
# timezone is just default timezone, so UTC
# current time is Mon, 24 Feb 2020 12:26:36 in New Zealand
# published_date is date type
Product.create!(published_date:Time.current)
# published_date is 2020-02-23 because Sun, 23 Feb 2020 23:26:36 UTC +00:00
Product.create!(published_date:Date.current)
# published_date is 2020-02-23 because Date.current is Sun, 23 Feb 2020 23:26:36 UTC +00:00 in UTC
Product.create!(published: Date.current.in_time_zone("Pacific/Auckland"))
# The published is 2020-02-22 11:00:00 because Date.current is 23
Product.where(published: Date.current.in_time_zone("Pacific/Auckland"))
An application does not support multi-time zone, we can disply datetime correctly by setting config.time_zone
However, if an application support it, displaying datetime could be a bit complicated.
- Change datetime to Browser local time
- Save timezone information into database and use it
- Sending zone information
- There are javascript functions(
Date(string)
) and libraries gem local_time
could be usefull
when the request completes, the original time zone is set back.
create_table :users do |t|
t.string :time_zone, default: "UTC"
end
# input user time zone, simpleform can support :time_zone
<%= f.input :time_zone %>
class ApplicationController < ActionController::Base
around_action :set_current_user_timezone, if: :current_user
def set_current_user_timezone(&block)
Time.use_zone(current_user.time_zone, &block)
end
end
# Displaying datetime
<%= time.in_time_zone(current_user.time_zone) %>
When request for displaying or query,
- Sending time zone information(
jstz
) - Sending zone offset(
getTimezoneOffset()
)
These information can be saved in session or with every request.
config.time_zone
should be set.
When saving and query date, date
type could be useful if an application do not care less about multiple time zones
# published_date is date type
Product.create!(published_date: Date.current)
Product.create!(published_date: Date.new(2020,02,24))
Product.where(published_date:Date.current)
Product.where(published_date: Date.new(2020,02,24)..Date.new(2020,02,25))
Product.where("published_date <= :date", date: Date.new(2011,6,1)).order(published_date: :asc).last
When using datetime
type for date, insert and query data could be a little complicated than date
Shoud use in_time_zone or beginning_of_day
/ end_of_day
becasue it includes time information
# published is datedate type
# 2020-02-24 11:00:00
# 2020-02-23 11:00:00
Product.create!(published: Date.current)
Date.current
=> Tue, 25 Feb 2020
Date.current.in_time_zone
=> Tue, 25 Feb 2020 00:00:00 NZDT +13:00
Date.current.beginning_of_day
=> Tue, 25 Feb 2020 00:00:00 NZDT +13:00
# Today
Product.where(published: Date.current.in_time_zone)
Product.where(published: Date.current.beginning_of_day .. Date.current.end_of_day)
Product.where(published: Date.current.all_day)
1.day.ago
=> Mon, 24 Feb 2020 16:49:25 NZDT +13:00
Product.where(published: 1.day.ago.beginning_of_day .. Date.current.end_of_day)
Product.where(published: Date.new(2020,2,24).in_time_zone .. Date.new(2020,2,25).in_time_zone)
If an application supports several time zones, datetime
and in_time_zone
should be used
To avoid trublemsome, save datetime or time format
# published is datetime
Product.create!(published: DateTime.current)
# published is saved as "2020-02-25 21:10:36.033901"
Product.where(published: DateTime.current.in_time_zone("Alaska").all_day)
# To use find_zone when saving or query specific date and time
Time.find_zone("Alaska").local(2020,02,19).to_s(:db)
=> "2020-02-19 09:00:00"
in_time_zone
is used for changing datetime which was made to time zone date time
To make time zone datetime , use find_zone
- DateTime - Has date and time information, utc time
- Date - Only has information about date, application time zone
- Time - It is system time
Date.new(2020,02,19)
=> Wed, 19 Feb 2020
Date.new(2020,02,19).in_time_zone("Alaska")
=> Wed, 19 Feb 2020 00:00:00 AKST -09:00
Date.new(2020,02,19).in_time_zone("Alaska").to_s(:db)
=> "2020-02-19 09:00:00"
DateTime.new(2020,02,19)
=> Wed, 19 Feb 2020 00:00:00 +0000
DateTime.new(2020,02,19).in_time_zone("Alaska")
=> Tue, 18 Feb 2020 15:00:00 AKST -09:00
DateTime.new(2020,02,19).in_time_zone("Alaska").to_s(:db)
=> "2020-02-19 00:00:00"
Time.new(2020,2,19) #system time zone
=> 2020-02-19 00:00:00 +1300
# Time.new creates system time, which is 2020-02-19 00:00:00 +1300
Time.new(2020,02,19).in_time_zone("Alaska")
=> Tue, 18 Feb 2020 02:00:00 AKST -09:00
Time.new(2020,02,19).in_time_zone("Alaska").to_s(:db)
=> "2020-02-18 11:00:00" #system is "Pacific/Auckland"
Time.new(2020,02,19).in_time_zone("UTC").to_s(:db)
=> "2020-02-18 11:00:00
Time.find_zone("Alaska").local(2020,02,19).to_s(:db)
=> "2020-02-19 09:00:00"