dst snippets

Playing with Ruby and Rails Time and Time Zones

Tagged ruby, time, date, timezone, use_zone, dst  Languages ruby

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.