Playing with Ruby and Rails Time and Time Zones
Checklist
What you need to know to handle date and time correctly in Ruby and Rails is, at least: * the input's time zone when parsing time and date. * the user's time zone when calculating time and date in his/her time zone. * Ruby and Rails date and time handling. Use Time.current and Time.zone (Rails) to avoid using the computer's time zone (Ruby).
Things to remember
* Is the given time in the user's time zone (Time.zone.local) or in UTC (Time.zone.utc)? * The correct time zone is needed when you're accessing date components like e.g. year, month, day, hour, start of day, start of month, etc * Rails takes care of time and date handling inside the application. It's when you accept input or want to output data that you need to tread carefully. * Cache carefully. Maybe you need to display date and time using JavaScripts. * Daylight savings, plus some countries or states don't even have DST, but might have had 100 years ago... * The difference between e.g. Time.now and Time.current. * Possible timezones: application, user, server, and database timezone * APIs should output ISO-8601
require 'tzinfo'
tokyo = TZInfo::Timezone.get('Asia/Tokyo')
#
# When it's 12:00 in Tokyo, what hour is it in UTC?
#
time = tokyo.local_to_utc Time.local(2014, 01, 01, 12, 00) # => 2014-01-01 03:00:00 UTC
utc_hour = time.hour # => 3
utc_time = time.utc # => 2014-01-01 03:00:00 UTC
#
# When it's 12:00 in UTC, what hour is it in Tokyo?
#
time = tokyo.utc_to_local Time.local(2014, 01, 01, 12, 00) # => 2014-01-01 21:00:00 UTC
tokyo_hour = time.hour # => 21
tokyo_time = tokyo.local_to_utc(time) # => 2014-01-01 12:00:00 UTC
#
# Convert Tokyo time to local time, i.e. convert user's local time to our local time
#
tokyo.now # => Tue, 18 Nov 2014 03:02:28 JST +09:00
tokyo.now.utc # => 2014-11-17 18:04:20 UTC
# local_to_utc => -0900
tokyo.local_to_utc tokyo.now # => 2014-11-17 18:03:02 UTC
# utc_to_local => +0900
tokyo.utc_to_local tokyo.now # => 2014-11-18 12:03:17 UTC
Time.parse Time.find_zone!('Tokyo').now.to_s
#
# Parse input from Helsinki and input from Stockholm when input contains no time zone information
#
a = Time.find_zone!('Helsinki').parse('201411121415')
b = Time.find_zone!('Stockholm').parse('201411121315')
a == b
a = Time.use_zone 'Helsinki' do
Time.parse('201411121415')
end
b = Time.use_zone 'Stockholm' do
Time.zone.parse('201411121315')
end
a == b # OK
#
# Using the tzinfo library directly
#
TZInfo::Timezone.all_country_zones
zone = TZInfo::Timezone.get('Africa/Harare')
#
# Rails time zones
#
ActiveSupport::TimeZone.zones_map.values.map(&:name).sort
#
# Daylight savings, anyone?
#
tokyo = TZInfo::Timezone.get('Asia/Tokyo')
tokyo.current_period.utc_offset # Doesn't include DST
tokyo.current_period.utc_total_offset # Includes DST
Time.use_zone 'Helsinki' do
Time.local(2014, 10, 26).dst? # => true
Time.local(2014, 10, 27).dst? # => false
end
Time.use_zone 'Pacific Time (US & Canada)' do
# 00:00 01.06.2014 in Los Angeles, during DST
now = Time.zone.local(2014, 6, 1)
puts now.dst? # => true
offset = now.utc_offset
puts offset / 1.hour # => -7
# 00:00 01.12.2014 in Los Angeles, after DST
now = Time.zone.local(2014, 12, 1)
puts now.dst? # => false
offset = now.utc_offset
puts offset / 1.hour # => -8
end
#
# Beginning of day in other time zone
#
Time.use_zone 'Hawaii' do
now = Time.local(2014, 10, 27)
puts now.beginning_of_day.dst? # false, Hawaii doesn't follow DST although the rest of the U.S.A. does.
puts now.beginning_of_day.utc # => 2014-10-26 22:00:00 UTC
puts now.beginning_of_day # => 2014-10-27 00:00:00 +0200
end
#
# Misc stuff
#
Time.now.formatted_offset # => "+02:00"
def without_timezone
times = [
Time.now,
Time.zone.now,
Time.current,
Time.now.utc
]
times.each do |time|
puts times[0].to_i == time.to_i # All OK
end
end
def with_timezone
times = nil
Time.use_zone('Tokyo') do
times = [
Time.now,
Time.zone.now,
Time.current,
Time.now.utc
]
end
Time.use_zone('Stockholm') do
times.each do |time|
puts times[0].to_i == time.to_i # All OK
end
end
end
def parse_time
times = nil
time = '201411121415'
Time.use_zone('Tokyo') do
times = [
Time.parse(time),
Time.parse(time).in_time_zone,
Time.zone.parse(time).in_time_zone, # ERROR: 7 hour time difference
Time.zone.parse(time) # ERROR: 7 hour time difference
]
end
Time.use_zone('Stockholm') do
times.each do |time|
binding.pry unless times[0].to_i == time.to_i
end
end
end
without_timezone
with_timezone
parse_time
Also see Working with time zones in Ruby on Rails.