public
Last active

Ruby Time and Timezone walkthrough

  • Download Gist
rails_timezone_converter.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
# see https://gist.github.com/bf4/3668333
 
require 'active_support/time'
# require 'active_support/core_ext/time'
# require 'active_support/core_ext/date'
# require 'active_support/core_ext/date_time'
# require 'active_support/time_with_zone'
class RailsSimpleZone
def self.all
ActiveSupport::TimeZone::MAPPING.values.map do |tz_info_name|
tz = ActiveSupport::TimeZone[tz_info_name]
new(tz)
end
end
def rails_friendly_name_zone_from_tz(tz='America/Chicago')
ActiveSupport::TimeZone::MAPPING.detect {|rails_zone_key,tz_info_name| tz_info_name == tz }.first
end
def self.tz_offset(tzinfo)
if tzinfo.respond_to?(:formatted_offset)
tzinfo.formatted_offset
else
total_offset = tzinfo.to_i
offset = '%.2d:00' % total_offset
offset.start_with?('-') ? offset : "+#{offset}"
end
end
attr_reader :name, :offset
def initialize(tz)
@name = tz.name
@offset = self.class.tz_offset(tz)
end
def to_str
"Zone: #{name}. Offset: #{offset}"
end
def to_s
to_str
end
def inspect
"<#SimpleZone #{name}. Offset: #{offset}>"
end
end
 
class RailsTimezoneConverter
BadZone = Class.new(StandardError)
def time_in_zone(time, zone)
zone = Zone(zone)
time = Time(time)
 
zone.parse(time)
end
 
def Zone(zone)
if zone.respond_to?(:formatted_offset)
zone
elsif zone.respond_to?(:current_period)
ActiveSupport::TimeZone[zone.name]
else
ActiveSupport::TimeZone[zone] || guess_tz(zone)
end
end
def Time(time)
if time.respond_to?(:strftime)
time
else
Time.parse(time)
end
end
def guess_tz(zone_guess)
raise bad_zone_error(zone_guess) if zone_guess.to_s.size.zero?
guess = zone_guess.to_s.split('/')[-1].downcase
 
zm = zones_mapping.find do |tz|
tz.name.downcase.include?(guess) ||
matching_offset?(tz.offset, guess)
end
if zm
ActiveSupport::TimeZone[zm.name]
else
raise bad_zone_error(zone_guess)
end
end
def bad_zone_error(zone)
BadZone.new("Need a zone to convert to. Got #{zone.inspect}")
end
def matching_offset?(reference,guess)
reference == (guess.to_i.zero? ? guess : RailsSimpleZone.tz_offset(guess))
end
def zones_mapping
@zones_mapping ||= RailsSimpleZone.all
end
end
if $0 == __FILE__
require 'rspec/autorun'
describe RailsTimezoneConverter do
context 'Zone()' do
let(:tz) { ActiveSupport::TimeZone['America/Chicago'] }
example do
expect(tz.name).to eq('America/Chicago')
end
it 'returns the time zone passed in' do
expect(subject.Zone(tz)).to eq(tz)
end
it 'returns the timezone for a valid identifier' do
expect(subject.Zone(tz.name)).to eq(tz)
end
it 'guessed the timezone from an imprecise identifier' do
expect(subject.Zone('Chicago')).to eq(tz)
end
end
context 'Time()' do
let(:time) { Time.now }
example do
expect(time).to respond_to(:strftime)
end
it 'returns the time passed in' do
expect(subject.Time(time)).to eq(time)
end
it 'returns a parsed time object when passed a string' do
time_string = time.xmlschema
expect(subject.Time(time_string).xmlschema).to eq(time_string)
end
end
 
describe "#tz_offset" do
it 'returns a UTC offset of +00:00 for UTC' do
tz = ActiveSupport::TimeZone['UTC']
expected = '+00:00'
expect(RailsSimpleZone.tz_offset(tz)).to eq(expected)
end
it 'returns +00:00 for +00:00' do
offset = '+00:00'
expected = '+00:00'
expect(RailsSimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns +00:00 for 0' do
offset = 0
expected = '+00:00'
expect(RailsSimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns +05:00 for 5' do
offset = 5
expected = '+05:00'
expect(RailsSimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns -05:00 for -5' do
offset = -5
expected = '-05:00'
expect(RailsSimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns +00:00 for -0' do
offset = -0
expected = '+00:00'
expect(RailsSimpleZone.tz_offset(offset)).to eq(expected)
end
end
 
describe '#matching_offset?' do
it 'matches 5 to +05:00' do
reference = '+05:00'
guess = 5
expect(
subject.matching_offset?(reference,guess)
).to be_true
end
 
end
describe "#guess_tz" do
let(:tz) { ActiveSupport::TimeZone['America/Chicago'] }
it 'returns a timezone with a matching identifier' do
expect(subject.guess_tz('Chicago')).to eq(tz)
end
it 'returns a timezone with a matching offset' do
offset = '-05:00'
tz = subject.guess_tz('-05:00')
expect(RailsSimpleZone.tz_offset(tz)).to eq(offset)
end
end
 
describe "#zones_mapping" do
it 'is an array of objects with the tz name and offset' do
expect(subject.zones_mapping.
find{|zm| zm.name == 'Etc/UTC'}.
offset
).to eq('+00:00')
end
end
 
end
end
ruby_time_walkthrough.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
=begin
see
http://time.is/
http://strfti.me/
=end
#
=begin
t = Time.now
# fails undefined method `xmlschema'
t.xmlschema
 
require 'time'
# or require 'tzinfo'
# works "2012-09-07T08:32:03-05:00"
t.xmlschema
=end
# Time.local('America/Chicago')
=begin
 
# http://tzinfo.rubyforge.org/doc/files/README.html
 
# to get the right xmlschema
require 'time'
require 'rubygems'
require 'tzinfo'
def time_in_zone(time,zone)
tzinfo = zone.respond_to?(:current_period) ? zone : TZInfo::Timezone.get(zone) rescue guess_tz(zone)
offset = tz_offset(tzinfo)
if RUBY_VERSION < '1.9'
tzinfo.utc_to_local(time.utc)
else
time.localtime(offset)
end
end
def tz_offset(tzinfo)
'%.02d:00' % (tzinfo.current_period.utc_total_offset / 60 / 60 ) # e.g. '-06:00'
end
def guess_tz(zone_guess)
guess = zone_guess.to_s.split('/')[-1]
TZInfo::Timezone.us_zones.detect {|tz| tz.name =~ /#{guess}/i }
end
 
start_datetime = '2012-11-03T10:00:00-06:00'
time = Time.parse(start_datetime)
tzinfo = guess_tz('honolulu')
offset = tz_offset(tzinfo)
localtime = time_in_zone(time,guess_tz('honolulu'))
localtime.xmlschema
=> "2012-11-03T06:00:00-10:00" # and the time object is changed, too
tzinfo.utc_to_local(time.utc).xmlschema
=> "2012-11-03T06:00:00Z"
# Note that the Time returned will look like it is UTC (Time.zone will return "UTC"). This is because it is not currently possible to change the offset of an individual Time instance.
 
tzinfo.current_period.utc_total_offset
tzinfo.current_period.utc_offset === utc_offset + std_offset.
http://stackoverflow.com/questions/9962038/how-do-i-calculate-the-offset-in-hours-of-a-given-timezone-from-utc-in-ruby
current.dst?
#=> true
To get the base offset of the timezone from UTC in seconds:
 
current.utc_offset
#=> -28800 which is -8 hours; this does NOT include daylight savings
To get the daylight savings offset from standard time:
 
current.std_offset
#=> 3600 which is 1 hour; this is because right now we're in daylight savings
To get the total offset from UTC:
 
current.utc_total_offset
#=> -25200 which is -7 hours
Time.mktime(sec, min, hour, day, month, year, wday, yday, isdst, tz)
Time.utc(sec, min, hour, day, month, year, wday, yday, isdst, tz)
Time.gm(sec, min, hour, day, month, year, wday, yday, isdst, tz)
Time.local(sec, min, hour, day, month, year, wday, yday, isdst, tz)
Time.new(year, month=nil, day=nil, hour=nil, min=nil, sec=nil, utc_offset=nil)
Time.new(2007,11,5,13,45,0, "-05:00") # EST (Detroit)
starts_at.localtime('-06:00')
Time.zone_offset('CDT')
tzinfo.current_period.zone_identifier == :PDT
DateTime rfc2822, rfc3339, rfc3339, rfc822
starts_at.gmt_offset
starts_at.getlocal
 
=end
class ShowTime
def timezone_from_tz(tz='America/Chicago')
TZInfo::Timezone.get(tz)
end
# utc = tz.local_to_utc(local)
# period = tz.period_for_utc(DateTime.new(2005,8,29,15,35,0))
# id = period.zone_identifier
# us = TZInfo::Country.get('US')
# timezones = us.zone_identifiers
# The zone_info method of Country provides an additional description and location for each Timezone in the Country.
 
def demonstrate(time)
puts "#{desc('default:')}\t#{time}"
[:to_i, :utc, :xmlschema, :rfc2822, :httpdate, :iso8601].each do |meth|
puts "#{desc meth}:\t#{time.method(meth).call}"
end
describe_time(time)
# Rails Time.zone
demonstrate_zone(time,Time.zone) if defined?(TZInfo)
end
 
def describe_time(time)
puts "#{desc 'Zone:'}\t#{time.zone.inspect}"
puts "#{desc 'Strftime:'}\t#{time.strftime("%c").inspect}"
end
 
def desc(s)
"%10s" % s.to_s
end
require 'timecop'
def with_time(time,&block)
Timecop.freeze(time)
block.call
Timecop.return # "turn off" Timecop
end
def midnight_in_chicago
Time.local(2012, 03, 28, 0, 0, 0)
end
def spring_forward_2012
Time.local(2012, 03, 10, 2, 0, 0)
end
def fall_back_2012
Time.local(2012, 11, 03, 2, 0, 0)
end
module Rails
# activesupport: Time.zone
def demonstrate_rails_start_of_day(time)
puts 3.days.from_now.utc.beginning_of_day
puts Time.now.utc.beginning_of_day + 3.days #end_of_day
end
 
def get_tzinfo_zone_from_rails(tz ='America/Chicago')
zone = rails_friendly_name_zone_from_tz(tz)
Time.zone = zone
# Time.zone.class => ActiveSupport::TimeZone
Time.zone
end
def rails_friendly_name_zone_from_tz(tz='America/Chicago')
ActiveSupport::TimeZone::MAPPING.detect {|rails_zone_key,tz_info_name| tz_info_name == tz }.first
end
def demonstrate_zone(time,tz_info)
Time.zone = nil
puts "I got #{time.xmlschema}\t zone #{Time.zone.inspect}"
puts "\tsetting Time.zone to #{tz_info}"
Time.zone = tz_info
puts "\tHey, Time.zone #{Time.zone.inspect}"
rails_friendly_name = rails_friendly_name_from_tz(tz_info)
puts "\tsetting Time.zone to #{rails_friendly_name}"
Time.zone = rails_friendly_name
puts "I got #{time.xmlschema}\t zone #{Time.zone.inspect}"
puts "Getting the ActiveSupport::Timezone"
zone = the_zone_i_need(rails_friendly_name)
puts "Trying one.parse(time)"
puts zone.parse(time).xmlschema
end
def demonstrate_to_s(time,format = :db)
time.to_s(format) #uses utc.to_s(:db)
end
def the_zone_i_need(rails_friendly_name)
Time.zone = rails_friendly_name
Time.zone
end
# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods.
# * t.to_s(:rfc822)
# * def to_s(format = :default)
# if format == :db
# utc.to_s(format)
# * ActiveSupport::TimeWithZone
# begin
# @time_zone.period_for_local(@time)
# rescue ::TZInfo::PeriodNotFound
# # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again
# @time += 1.hour
# retry
# end
# end
 
end
end
 
 
# # leap second
=begin
* 12 a.m. Wednesday March 28th in Chicago is Tuesday March 27th in Denver and Palo Alto
* 2004-04-01T16:32:45-06:00
* Note that iso8601 is the both human-readable and machine, unambiguous, and sortable as a string. It is the format used in the microformats
* UTC 'timezone' designator is "Z" e.g 1994-11-05T13:15:30Z corresponds to 1994-11-05T08:15:30-05:00
=end
 
=begin
time = midnight_in_chicago
ShowTime::Rails.demonstrate_rails_beginning_of_day(time)
tz = 'America/Phoenix'
tzinf = timezone_from_tz(tz)
rails_friendly_name = ShowTime::Rails.rails_friendly_name_zone_from_tz(tz)
zone = Showtime::Rails.get_tzinfo_zone_from_rails(tz)
Time.zone = zone
Time.zone.parse("2012-09-07 13:15")
# Time.zone = zone
# ActiveSupport::TimeWithZone.new(rails_friendly_name)
# Showtime:Rails.demonstrate
=end
# make it 10am in chicago, palo alto, madrid
# Time
# - require 'time'; Time.parse("30/12/2001") .. in 1.9.2 parses as dd/mm/yyyy .. was mm/dd/yyyy in 1.8!
=begin
TODO:
Date vs. Time vs. DateTime
with require 'time'
with require 'tzinfo'
with require 'activesupport'
Compare 1.hour to Date.civil(
 
ActiveSupport
rails 3.1
require 'active_support/time/autoload.rb'
loads 'active_support/duration'
loads 'active_support/time_with_zone'
loads 'active_support/values/time_zone'
rails 2.3
require 'active_support/values/time_zone.rb'
ActiveSupport::TimeZone
active_support/core_ext/time/behavior
active_support/core_ext/time/calculations
active_support/core_ext/time/conversions
=end
timezone_converter.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
# see https://gist.github.com/bf4/3668333
# http://tzinfo.rubyforge.org/doc/files/README.html
 
# to get the right xmlschema
require 'time'
require 'rubygems'
require 'tzinfo'
class SimpleZone
def self.all
TZInfo::Timezone.all_country_zones.map do |tz|
new(tz)
end
end
def self.tz_offset(tzinfo)
if tzinfo.respond_to?(:current_period)
total_offset = (tzinfo.current_period.utc_total_offset / 60 / 60 ) # e.g. '-06:00'
else
total_offset = tzinfo.to_i
end
offset = '%.2d:00' % total_offset
offset.start_with?('-') ? offset : "+#{offset}"
end
attr_reader :name, :offset
def initialize(tz)
@name = tz.name
@offset = self.class.tz_offset(tz)
end
def to_str
"Zone: #{name}. Offset: #{offset}"
end
def to_s
to_str
end
def inspect
"<#SimpleZone #{name}. Offset: #{offset}>"
end
end
 
class TimezoneConverter
BadZone = Class.new(StandardError)
def time_in_zone(time, zone)
zone = Zone(zone)
time = Time(time)
 
offset = SimpleZone.tz_offset(zone)
if RUBY_VERSION < '1.9'
zone.utc_to_local(time.utc)
else
time.localtime(offset)
end
end
 
def Zone(zone)
if zone.respond_to?(:current_period)
zone
else
begin
TZInfo::Timezone.get(zone)
rescue TZInfo::InvalidTimezoneIdentifier
guess_tz(zone)
end
end
end
def Time(time)
if time.respond_to?(:strftime)
time
else
Time.parse(time)
end
end
def guess_tz(zone_guess)
raise bad_zone_error(zone_guess) if zone_guess.to_s.size.zero?
guess = zone_guess.to_s.split('/')[-1].downcase
 
zm = zones_mapping.find do |tz|
tz.name.downcase.include?(guess) ||
matching_offset?(tz.offset, guess)
end
if zm
TZInfo::Timezone.get(zm.name)
else
raise bad_zone_error(zone_guess)
end
end
def bad_zone_error(zone)
BadZone.new("Need a zone to convert to. Got #{zone.inspect}")
end
# if guess is text, match exact, else try to convert to offset
def matching_offset?(reference,guess)
reference == (guess.to_i.zero? ? guess : SimpleZone.tz_offset(guess))
end
def zones_mapping
@zones_mapping ||= SimpleZone.all
end
end
if $0 == __FILE__
require 'rspec/autorun'
describe TimezoneConverter do
context 'Zone()' do
let(:tz) { TZInfo::Timezone.get('America/Chicago') }
example do
expect(tz.name).to eq('America/Chicago')
end
it 'returns the time zone passed in' do
expect(subject.Zone(tz)).to eq(tz)
end
it 'returns the timezone for a valid identifier' do
expect(subject.Zone(tz.name)).to eq(tz)
end
it 'guessed the timezone from an imprecise identifier' do
expect(subject.Zone('Chicago')).to eq(tz)
end
end
context 'Time()' do
let(:time) { Time.now }
example do
expect(time).to respond_to(:strftime)
end
it 'returns the time passed in' do
expect(subject.Time(time)).to eq(time)
end
it 'returns a parsed time object when passed a string' do
time_string = time.xmlschema
expect(subject.Time(time_string).xmlschema).to eq(time_string)
end
end
 
describe "#tz_offset" do
it 'returns a UTC offset of +00:00 for UTC' do
tz = TZInfo::Timezone.get('UTC')
expected = '+00:00'
expect(SimpleZone.tz_offset(tz)).to eq(expected)
end
it 'returns +00:00 for +00:00' do
offset = '+00:00'
expected = '+00:00'
expect(SimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns +00:00 for 0' do
offset = 0
expected = '+00:00'
expect(SimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns +05:00 for 5' do
offset = 5
expected = '+05:00'
expect(SimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns -05:00 for -5' do
offset = -5
expected = '-05:00'
expect(SimpleZone.tz_offset(offset)).to eq(expected)
end
it 'returns +00:00 for -0' do
offset = -0
expected = '+00:00'
expect(SimpleZone.tz_offset(offset)).to eq(expected)
end
end
 
describe '#matching_offset?' do
it 'matches 5 to +05:00' do
reference = '+05:00'
guess = 5
expect(
subject.matching_offset?(reference,guess)
).to be_true
end
 
end
describe "#guess_tz" do
let(:tz) { TZInfo::Timezone.get('America/Chicago') }
it 'returns a timezone with a matching identifier' do
expect(subject.guess_tz('Chicago')).to eq(tz)
end
it 'returns a timezone with a matching offset' do
offset = '-05:00'
tz = subject.guess_tz('-05:00')
expect(SimpleZone.tz_offset(tz)).to eq(offset)
end
end
 
describe "#zones_mapping" do
it 'is an array of objects with the tz name and offset' do
expect(subject.zones_mapping.
find{|zm| zm.name == 'America/Chicago'}.
offset
).to eq('-05:00')
end
end
 
end
end

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.