Skip to content

Instantly share code, notes, and snippets.

@JamesYang76
Last active April 4, 2024 22:00
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JamesYang76/2b99e4b222599bcf5b87f57d36e98309 to your computer and use it in GitHub Desktop.
Save JamesYang76/2b99e4b222599bcf5b87f57d36e98309 to your computer and use it in GitHub Desktop.
Time Zone for rails

Time zones

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

Cautions

  • 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 of datetime type for handing only date if an app dose not support multiple time zones
  • datetime and in_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 with new for DateTime or Time to make specific time zone time

Difference among date/time API

Application vs System time

  • Application time zone - Default is UTC unless Time.zone="Alaska" or config.time_zone = "Pacific/Auckland"
  • System - This depends on how Server OS time is set up

Do’s and Don'ts with time zones

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)

Convert time zone

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

Save data

# 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"

Read data

# 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 

Incorrect date

When saving and displaying date, the date could be incorrect if timezone is just default UTC

Read

# 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

Save & Query

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"))

How to display date time

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

Change datetime to broswer time

  • There are javascript functions(Date(string)) and libraries
  • gem local_time could be usefull

Save timezone information

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) %>

Sending zone information

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.

How to save and query date

Single time zone

config.time_zone should be set.

date

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

datetime

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)

Multiple time zones

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 vs find_zone

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"

Reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment