Changeset 8679
- Timestamp:
- 01/21/08 03:55:54 (8 months ago)
- Files:
-
- trunk/activesupport/CHANGELOG (modified) (1 diff)
- trunk/activesupport/lib/active_support/values/time_zone.rb (modified) (8 diffs)
- trunk/activesupport/test/abstract_unit.rb (modified) (1 diff)
- trunk/activesupport/test/time_zone_test.rb (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activesupport/CHANGELOG
r8664 r8679 1 1 *SVN* 2 3 * Replace non-dst-aware TimeZone class with dst-aware class from tzinfo_timezone plugin. TimeZone#adjust and #unadjust are no longer available; tzinfo gem must now be present in order to perform time zone calculations, via #local_to_utc and #utc_to_local methods. [Geoff Buesing] 2 4 3 5 * Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher. #10727 [Josh Peek] trunk/activesupport/lib/active_support/values/time_zone.rb
r8635 r8679 1 # A value object representing a time zone. A time zone is simply a named2 # offset (in seconds) from GMT. Note that two time zone objects are only3 # equivalent if they have both the same offset, and the same name.4 #5 # A TimeZone instance may be used to convert a Time value to the corresponding6 # time zone.7 #8 # The class also includes #all, which returns a list of all TimeZone objects.9 1 class TimeZone 2 MAPPING = { 3 "International Date Line West" => "Pacific/Midway", 4 "Midway Island" => "Pacific/Midway", 5 "Samoa" => "Pacific/Pago_Pago", 6 "Hawaii" => "Pacific/Honolulu", 7 "Alaska" => "America/Juneau", 8 "Pacific Time (US & Canada)" => "America/Los_Angeles", 9 "Tijuana" => "America/Tijuana", 10 "Mountain Time (US & Canada)" => "America/Denver", 11 "Arizona" => "America/Phoenix", 12 "Chihuahua" => "America/Chihuahua", 13 "Mazatlan" => "America/Mazatlan", 14 "Central Time (US & Canada)" => "America/Chicago", 15 "Saskatchewan" => "America/Regina", 16 "Guadalajara" => "America/Mexico_City", 17 "Mexico City" => "America/Mexico_City", 18 "Monterrey" => "America/Monterrey", 19 "Central America" => "America/Guatemala", 20 "Eastern Time (US & Canada)" => "America/New_York", 21 "Indiana (East)" => "America/Indiana/Indianapolis", 22 "Bogota" => "America/Bogota", 23 "Lima" => "America/Lima", 24 "Quito" => "America/Lima", 25 "Atlantic Time (Canada)" => "America/Halifax", 26 "Caracas" => "America/Caracas", 27 "La Paz" => "America/La_Paz", 28 "Santiago" => "America/Santiago", 29 "Newfoundland" => "America/St_Johns", 30 "Brasilia" => "America/Argentina/Buenos_Aires", 31 "Buenos Aires" => "America/Argentina/Buenos_Aires", 32 "Georgetown" => "America/Argentina/San_Juan", 33 "Greenland" => "America/Godthab", 34 "Mid-Atlantic" => "Atlantic/South_Georgia", 35 "Azores" => "Atlantic/Azores", 36 "Cape Verde Is." => "Atlantic/Cape_Verde", 37 "Dublin" => "Europe/Dublin", 38 "Edinburgh" => "Europe/Dublin", 39 "Lisbon" => "Europe/Lisbon", 40 "London" => "Europe/London", 41 "Casablanca" => "Africa/Casablanca", 42 "Monrovia" => "Africa/Monrovia", 43 "Belgrade" => "Europe/Belgrade", 44 "Bratislava" => "Europe/Bratislava", 45 "Budapest" => "Europe/Budapest", 46 "Ljubljana" => "Europe/Ljubljana", 47 "Prague" => "Europe/Prague", 48 "Sarajevo" => "Europe/Sarajevo", 49 "Skopje" => "Europe/Skopje", 50 "Warsaw" => "Europe/Warsaw", 51 "Zagreb" => "Europe/Zagreb", 52 "Brussels" => "Europe/Brussels", 53 "Copenhagen" => "Europe/Copenhagen", 54 "Madrid" => "Europe/Madrid", 55 "Paris" => "Europe/Paris", 56 "Amsterdam" => "Europe/Amsterdam", 57 "Berlin" => "Europe/Berlin", 58 "Bern" => "Europe/Berlin", 59 "Rome" => "Europe/Rome", 60 "Stockholm" => "Europe/Stockholm", 61 "Vienna" => "Europe/Vienna", 62 "West Central Africa" => "Africa/Algiers", 63 "Bucharest" => "Europe/Bucharest", 64 "Cairo" => "Africa/Cairo", 65 "Helsinki" => "Europe/Helsinki", 66 "Kyev" => "Europe/Kiev", 67 "Riga" => "Europe/Riga", 68 "Sofia" => "Europe/Sofia", 69 "Tallinn" => "Europe/Tallinn", 70 "Vilnius" => "Europe/Vilnius", 71 "Athens" => "Europe/Athens", 72 "Istanbul" => "Europe/Istanbul", 73 "Minsk" => "Europe/Minsk", 74 "Jerusalem" => "Asia/Jerusalem", 75 "Harare" => "Africa/Harare", 76 "Pretoria" => "Africa/Johannesburg", 77 "Moscow" => "Europe/Moscow", 78 "St. Petersburg" => "Europe/Moscow", 79 "Volgograd" => "Europe/Moscow", 80 "Kuwait" => "Asia/Kuwait", 81 "Riyadh" => "Asia/Riyadh", 82 "Nairobi" => "Africa/Nairobi", 83 "Baghdad" => "Asia/Baghdad", 84 "Tehran" => "Asia/Tehran", 85 "Abu Dhabi" => "Asia/Muscat", 86 "Muscat" => "Asia/Muscat", 87 "Baku" => "Asia/Baku", 88 "Tbilisi" => "Asia/Tbilisi", 89 "Yerevan" => "Asia/Yerevan", 90 "Kabul" => "Asia/Kabul", 91 "Ekaterinburg" => "Asia/Yekaterinburg", 92 "Islamabad" => "Asia/Karachi", 93 "Karachi" => "Asia/Karachi", 94 "Tashkent" => "Asia/Tashkent", 95 "Chennai" => "Asia/Calcutta", 96 "Kolkata" => "Asia/Calcutta", 97 "Mumbai" => "Asia/Calcutta", 98 "New Delhi" => "Asia/Calcutta", 99 "Kathmandu" => "Asia/Katmandu", 100 "Astana" => "Asia/Dhaka", 101 "Dhaka" => "Asia/Dhaka", 102 "Sri Jayawardenepura" => "Asia/Dhaka", 103 "Almaty" => "Asia/Almaty", 104 "Novosibirsk" => "Asia/Novosibirsk", 105 "Rangoon" => "Asia/Rangoon", 106 "Bangkok" => "Asia/Bangkok", 107 "Hanoi" => "Asia/Bangkok", 108 "Jakarta" => "Asia/Jakarta", 109 "Krasnoyarsk" => "Asia/Krasnoyarsk", 110 "Beijing" => "Asia/Shanghai", 111 "Chongqing" => "Asia/Chongqing", 112 "Hong Kong" => "Asia/Hong_Kong", 113 "Urumqi" => "Asia/Urumqi", 114 "Kuala Lumpur" => "Asia/Kuala_Lumpur", 115 "Singapore" => "Asia/Singapore", 116 "Taipei" => "Asia/Taipei", 117 "Perth" => "Australia/Perth", 118 "Irkutsk" => "Asia/Irkutsk", 119 "Ulaan Bataar" => "Asia/Ulaanbaatar", 120 "Seoul" => "Asia/Seoul", 121 "Osaka" => "Asia/Tokyo", 122 "Sapporo" => "Asia/Tokyo", 123 "Tokyo" => "Asia/Tokyo", 124 "Yakutsk" => "Asia/Yakutsk", 125 "Darwin" => "Australia/Darwin", 126 "Adelaide" => "Australia/Adelaide", 127 "Canberra" => "Australia/Melbourne", 128 "Melbourne" => "Australia/Melbourne", 129 "Sydney" => "Australia/Sydney", 130 "Brisbane" => "Australia/Brisbane", 131 "Hobart" => "Australia/Hobart", 132 "Vladivostok" => "Asia/Vladivostok", 133 "Guam" => "Pacific/Guam", 134 "Port Moresby" => "Pacific/Port_Moresby", 135 "Magadan" => "Asia/Magadan", 136 "Solomon Is." => "Asia/Magadan", 137 "New Caledonia" => "Pacific/Noumea", 138 "Fiji" => "Pacific/Fiji", 139 "Kamchatka" => "Asia/Kamchatka", 140 "Marshall Is." => "Pacific/Majuro", 141 "Auckland" => "Pacific/Auckland", 142 "Wellington" => "Pacific/Auckland", 143 "Nuku'alofa" => "Pacific/Tongatapu" 144 } 145 10 146 include Comparable 11 12 147 attr_reader :name, :utc_offset 13 148 14 # Create a new TimeZone object with the given name and offset. The offset is15 # the number of seconds that this time zone is offset from UTC (GMT). Seconds16 # were chosen as the offset unit because that is the unit that Ruby uses17 # to represent time zone offsets (see Time#utc_offset).149 # Create a new TimeZone object with the given name and offset. The 150 # offset is the number of seconds that this time zone is offset from UTC 151 # (GMT). Seconds were chosen as the offset unit because that is the unit that 152 # Ruby uses to represent time zone offsets (see Time#utc_offset). 18 153 def initialize(name, utc_offset) 19 154 @name = name … … 25 160 # string. If +colon+ is false, a colon will not be inserted into the 26 161 # result. 27 def formatted_offset( colon=true ) 28 return "" if utc_offset == 0 162 def formatted_offset(colon=true) 163 utc_offset == 0 ? '' : offset(colon) 164 end 165 166 # Returns the offset of this time zone as a formatted string, of the 167 # format "+HH:MM". 168 def offset(colon=true) 29 169 utc_offset.to_utc_offset_s(colon) 30 end31 32 # Compute and return the current time, in the time zone represented by33 # +self+.34 def now35 adjust(Time.now)36 end37 38 # Return the current date in this time zone.39 def today40 now.to_date41 end42 43 # Adjust the given time to the time zone represented by +self+.44 def adjust(time)45 time = time.to_time unless time.is_a?(::Time)46 time + utc_offset - time.utc_offset47 end48 49 # Reinterprets the given time value as a time in the current time50 # zone, and then adjusts it to return the corresponding time in the51 # local time zone.52 def unadjust(time)53 time = time.to_time unless time.is_a?(::Time)54 time = time.localtime55 time - utc_offset - time.utc_offset56 170 end 57 171 … … 66 180 # Returns a textual representation of this time zone. 67 181 def to_s 68 "(UTC#{formatted_offset}) #{name}" 182 "(GMT#{formatted_offset}) #{name}" 183 end 184 185 begin # the following methods depend on the tzinfo gem 186 require_library_or_gem "tzinfo" unless Object.const_defined?(:TZInfo) 187 188 # Compute and return the current time, in the time zone represented by 189 # +self+. 190 def now 191 tzinfo.now 192 end 193 194 # Return the current date in this time zone. 195 def today 196 now.to_date 197 end 198 199 # Adjust the given time to the time zone represented by +self+. 200 def utc_to_local(time) 201 tzinfo.utc_to_local(time) 202 end 203 204 def local_to_utc(time, dst=true) 205 tzinfo.local_to_utc(time, dst) 206 end 207 208 # Available so that TimeZone instances respond like TZInfo::Timezone instances 209 def period_for_local(time, dst=true) 210 tzinfo.period_for_local(time, dst) 211 end 212 213 def tzinfo 214 return @tzinfo if @tzinfo 215 @tzinfo = MAPPING[name] 216 if String === @tzinfo 217 @tzinfo = TZInfo::Timezone.get(@tzinfo) 218 MAPPING[name] = @tzinfo 219 end 220 @tzinfo 221 end 222 223 rescue LoadError # Tzinfo gem is not available 224 # re-raise LoadError only when a tzinfo-dependent method is called: 225 %w(now today utc_to_local local_to_utc period_for_local tzinfo).each do |method| 226 define_method(method) {|*args| raise LoadError, "TZInfo gem is required for TimeZone##{method}. `gem install tzinfo` and try again."} 227 end 69 228 end 70 229 … … 72 231 73 232 class << self 74 # Create a new TimeZone instance with the given name and offset. 75 def create(name, offset) 76 zone = allocate 77 zone.send!(:initialize, name, offset) 78 zone 79 end 233 alias_method :create, :new 80 234 81 235 # Return a TimeZone instance with the given name, or +nil+ if no … … 86 240 end 87 241 88 # Return an array of all TimeZone objects. There are multiple TimeZone89 # objects per time zone, in many cases, to make it easier for users to90 # f ind their own time zone.242 # Return an array of all TimeZone objects. There are multiple 243 # TimeZone objects per time zone, in many cases, to make it easier 244 # for users to find their own time zone. 91 245 def all 92 246 unless @@zones 93 247 @@zones = [] 94 [[-43_200, "International Date Line West" ],95 [-39_600, "Midway Island", "Samoa" ],248 @@zones_map = {} 249 [[-39_600, "International Date Line West", "Midway Island", "Samoa" ], 96 250 [-36_000, "Hawaii" ], 97 251 [-32_400, "Alaska" ], 98 252 [-28_800, "Pacific Time (US & Canada)", "Tijuana" ], 99 [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "Mazatlan", 253 [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "Mazatlan", 100 254 "Arizona" ], 101 255 [-21_600, "Central Time (US & Canada)", "Saskatchewan", "Guadalajara", … … 142 296 [ 46_800, "Nuku'alofa" ]]. 143 297 each do |offset, *places| 144 places.each { |place| @@zones << create(place, offset).freeze } 298 places.each do |place| 299 zone = create(place, offset) 300 @@zones << zone 301 @@zones_map[place] = zone 302 end 145 303 end 146 304 @@zones.sort! … … 157 315 case arg 158 316 when String 159 all.find { |z| z.name == arg } 317 all # force the zones to be loaded 318 @@zones_map[arg] 160 319 when Numeric 161 320 arg *= 3600 if arg.abs <= 13 … … 168 327 # A regular expression that matches the names of all time zones in 169 328 # the USA. 170 US_ZONES = /US|Arizona|Indiana|Hawaii|Alaska/ unless defined?(US_ZONES)329 US_ZONES = /US|Arizona|Indiana|Hawaii|Alaska/ 171 330 172 331 # A convenience method for returning a collection of TimeZone objects trunk/activesupport/test/abstract_unit.rb
r7773 r8679 5 5 require 'active_support' 6 6 7 def uses_gem(gem_name, test_name, version = '> 0') 8 require 'rubygems' 9 gem gem_name.to_s, version 10 require gem_name.to_s 11 yield 12 rescue LoadError 13 $stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again." 14 end 15 7 16 # Wrap tests that use Mocha and skip if unavailable. 8 17 unless defined? uses_mocha 9 def uses_mocha(test_name) 10 require 'rubygems' 11 gem 'mocha', '>= 0.5.5' 12 require 'mocha' 13 yield 14 rescue LoadError 15 $stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again." 18 def uses_mocha(test_name, &block) 19 uses_gem('mocha', test_name, '>= 0.5.5', &block) 20 end 21 end 22 23 # Wrap tests that use TZInfo and skip if unavailable. 24 unless defined? uses_tzinfo 25 def uses_tzinfo(test_name, &block) 26 uses_gem('tzinfo', test_name, &block) 16 27 end 17 28 end trunk/activesupport/test/time_zone_test.rb
r8563 r8679 2 2 3 3 class TimeZoneTest < Test::Unit::TestCase 4 class MockTime 5 def self.now 6 Time.utc( 2004, 7, 25, 14, 49, 00 ) 4 5 uses_tzinfo 'TestTimeZoneCalculations' do 6 7 def test_utc_to_local 8 silence_warnings do # silence warnings raised by tzinfo gem 9 zone = TimeZone['Eastern Time (US & Canada)'] 10 assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500 11 assert_equal Time.utc(2000, 6, 30, 20), zone.utc_to_local(Time.utc(2000, 7)) # dst offset -0400 12 end 13 end 14 15 def test_local_to_utc 16 silence_warnings do # silence warnings raised by tzinfo gem 17 zone = TimeZone['Eastern Time (US & Canada)'] 18 assert_equal Time.utc(2000, 1, 1, 5), zone.local_to_utc(Time.utc(2000, 1)) # standard offset -0500 19 assert_equal Time.utc(2000, 7, 1, 4), zone.local_to_utc(Time.utc(2000, 7)) # dst offset -0400 20 end 21 end 22 23 def test_period_for_local 24 silence_warnings do # silence warnings raised by tzinfo gem 25 zone = TimeZone['Eastern Time (US & Canada)'] 26 assert_instance_of TZInfo::TimezonePeriod, zone.period_for_local(Time.utc(2000)) 27 end 28 end 29 30 TimeZone::MAPPING.keys.each do |name| 31 define_method("test_map_#{name.downcase.gsub(/[^a-z]/, '_')}_to_tzinfo") do 32 silence_warnings do # silence warnings raised by tzinfo gem 33 zone = TimeZone[name] 34 assert zone.tzinfo.respond_to?(:period_for_local) 35 end 36 end 7 37 end 8 38 9 def self.local(*args) 10 Time.utc(*args) 39 TimeZone.all.each do |zone| 40 name = zone.name.downcase.gsub(/[^a-z]/, '_') 41 define_method("test_from_#{name}_to_map") do 42 silence_warnings do # silence warnings raised by tzinfo gem 43 assert_instance_of TimeZone, TimeZone[zone.name] 44 end 45 end 46 47 define_method("test_utc_offset_for_#{name}") do 48 silence_warnings do # silence warnings raised by tzinfo gem 49 period = zone.tzinfo.period_for_utc(Time.utc(2006,1,1,0,0,0)) 50 assert_equal period.utc_offset, zone.utc_offset 51 end 52 end 53 end 54 55 uses_mocha 'TestTimeZoneNowAndToday' do 56 def test_now 57 TZInfo::DataTimezone.any_instance.stubs(:now).returns(Time.utc(2000)) 58 assert_equal Time.utc(2000), TimeZone['Eastern Time (US & Canada)'].now 59 end 60 61 def test_today 62 TZInfo::DataTimezone.any_instance.stubs(:now).returns(Time.utc(2000)) 63 assert_equal Date.new(2000), TimeZone['Eastern Time (US & Canada)'].today 64 end 11 65 end 12 66 end 13 14 TimeZone::Time = MockTime 15 67 16 68 def test_formatted_offset_positive 17 zone = TimeZone.create( "Test", 4200 ) 18 assert_equal "+01:10", zone.formatted_offset 69 zone = TimeZone['Moscow'] 70 assert_equal "+03:00", zone.formatted_offset 71 assert_equal "+0300", zone.formatted_offset(false) 19 72 end 20 73 21 74 def test_formatted_offset_negative 22 zone = TimeZone.create( "Test", -4200 ) 23 assert_equal "-01:10", zone.formatted_offset 75 zone = TimeZone['Eastern Time (US & Canada)'] 76 assert_equal "-05:00", zone.formatted_offset 77 assert_equal "-0500", zone.formatted_offset(false) 24 78 end 25 26 def test_now 27 zone = TimeZone.create( "Test", 4200 ) 28 assert_equal Time.local(2004,7,25,15,59,00).to_a[0,6], zone.now.to_a[0,6] 29 end 30 31 def test_today 32 zone = TimeZone.create( "Test", 43200 ) 33 assert_equal Date.new(2004,7,26), zone.today 34 end 35 36 def test_adjust_negative 37 zone = TimeZone.create( "Test", -4200 ) # 4200s == 70 mins 38 assert_equal Time.utc(2004,7,24,23,55,0), zone.adjust(Time.utc(2004,7,25,1,5,0)) 39 end 40 41 def test_adjust_positive 42 zone = TimeZone.create( "Test", 4200 ) 43 assert_equal Time.utc(2004,7,26,1,5,0), zone.adjust(Time.utc(2004,7,25,23,55,0)) 44 end 45 46 def test_unadjust 47 zone = TimeZone.create( "Test", 4200 ) 48 expect = Time.utc(2004,7,24,23,55,0).to_a[0,6] 49 actual = zone.unadjust(Time.utc(2004,7,25,1,5,0)).to_a[0,6] 50 assert_equal expect, actual 51 end 52 79 53 80 def test_zone_compare 54 zone1 = TimeZone .create( "Test1", 4200 )55 zone2 = TimeZone .create( "Test1", 5600 )81 zone1 = TimeZone['Central Time (US & Canada)'] # offset -0600 82 zone2 = TimeZone['Eastern Time (US & Canada)'] # offset -0500 56 83 assert zone1 < zone2 57 84 assert zone2 > zone1 58 59 zone1 = TimeZone.create( "Able", 10000 )60 zone2 = TimeZone.create( "Zone", 10000 )61 assert zone1 < zone262 assert zone2 > zone163 64 zone1 = TimeZone.create( "Able", 10000 )65 85 assert zone1 == zone1 66 86 end 67 87 68 88 def test_to_s 69 zone = TimeZone.create( "Test", 4200 ) 70 assert_equal "(UTC+01:10) Test", zone.to_s 89 assert_equal "(GMT+03:00) Moscow", TimeZone['Moscow'].to_s 71 90 end 72 91 73 92 def test_all_sorted 74 93 all = TimeZone.all … … 77 96 end 78 97 end 79 98 80 99 def test_index 81 100 assert_nil TimeZone["bogus"] 82 assert_ not_nilTimeZone["Central Time (US & Canada)"]83 assert_ not_nilTimeZone[8]101 assert_instance_of TimeZone, TimeZone["Central Time (US & Canada)"] 102 assert_instance_of TimeZone, TimeZone[8] 84 103 assert_raises(ArgumentError) { TimeZone[false] } 85 104 end 86 105 87 106 def test_new 88 a = TimeZone.new("Berlin") 89 b = TimeZone.new("Berlin") 90 assert_same a, b 91 assert_nil TimeZone.new("bogus") 107 assert_equal TimeZone["Central Time (US & Canada)"], TimeZone.new("Central Time (US & Canada)") 92 108 end 93 109 … … 97 113 end 98 114 end 115