File: active_support/values/time_zone.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: ActiveSupport#22
  class: TimeZone#23
includes
  Comparable ( Builtin-Module )
inherits from
  Object ( Builtin-Module )
has properties
constant: MAPPING #26
attribute: name [R] #174
attribute: tzinfo [R] #175
method: initialize / 3 #181
method: utc_offset #188
method: formatted_offset / 2 #199
method: <=> / 1 #205
method: =~ / 1 #213
method: to_s #218
method: local / 1 #226
method: at / 1 #236
method: parse / 2 #250
method: now #266
method: today #271
method: utc_to_local / 1 #277
method: local_to_utc / 2 #282
method: period_for_utc / 1 #287
method: period_for_local / 2 #292
class method: find_tzinfo / 1 #297
class method: new / 1 #310
class method: all #317
class method: zones_map #321
class method: [] / 1 #387
class method: us_zones #405
class method: lookup / 1 #411

Class Hierarchy

Code

   1  # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following:
   2  #
   3  # * Limit the set of zones provided by TZInfo to a meaningful subset of 142 zones.
   4  # * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
   5  # * Lazily load TZInfo::Timezone instances only when they're needed.
   6  # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods.
   7  #
   8  # If you set <tt>config.time_zone</tt> in the Rails Initializer, you can access this TimeZone object via <tt>Time.zone</tt>:
   9  #
  10  #   # environment.rb:
  11  #   Rails::Initializer.run do |config|
  12  #     config.time_zone = "Eastern Time (US & Canada)"
  13  #   end
  14  #
  15  #   Time.zone       # => #<TimeZone:0x514834...>
  16  #   Time.zone.name  # => "Eastern Time (US & Canada)"
  17  #   Time.zone.now   # => Sun, 18 May 2008 14:30:44 EDT -04:00
  18  #
  19  # The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones
  20  # defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem
  21  # (if a recent version of the gem is installed locally, this will be used instead of the bundled version.)
  22  module ActiveSupport
  23    class TimeZone
  24      unless const_defined?(:MAPPING)
  25        # Keys are Rails TimeZone names, values are TZInfo identifiers
  26        MAPPING = {
  27          "International Date Line West" => "Pacific/Midway",
  28          "Midway Island"                => "Pacific/Midway",
  29          "Samoa"                        => "Pacific/Pago_Pago",
  30          "Hawaii"                       => "Pacific/Honolulu",
  31          "Alaska"                       => "America/Juneau",
  32          "Pacific Time (US & Canada)"   => "America/Los_Angeles",
  33          "Tijuana"                      => "America/Tijuana",
  34          "Mountain Time (US & Canada)"  => "America/Denver",
  35          "Arizona"                      => "America/Phoenix",
  36          "Chihuahua"                    => "America/Chihuahua",
  37          "Mazatlan"                     => "America/Mazatlan",
  38          "Central Time (US & Canada)"   => "America/Chicago",
  39          "Saskatchewan"                 => "America/Regina",
  40          "Guadalajara"                  => "America/Mexico_City",
  41          "Mexico City"                  => "America/Mexico_City",
  42          "Monterrey"                    => "America/Monterrey",
  43          "Central America"              => "America/Guatemala",
  44          "Eastern Time (US & Canada)"   => "America/New_York",
  45          "Indiana (East)"               => "America/Indiana/Indianapolis",
  46          "Bogota"                       => "America/Bogota",
  47          "Lima"                         => "America/Lima",
  48          "Quito"                        => "America/Lima",
  49          "Atlantic Time (Canada)"       => "America/Halifax",
  50          "Caracas"                      => "America/Caracas",
  51          "La Paz"                       => "America/La_Paz",
  52          "Santiago"                     => "America/Santiago",
  53          "Newfoundland"                 => "America/St_Johns",
  54          "Brasilia"                     => "America/Sao_Paulo",
  55          "Buenos Aires"                 => "America/Argentina/Buenos_Aires",
  56          "Georgetown"                   => "America/Argentina/San_Juan",
  57          "Greenland"                    => "America/Godthab",
  58          "Mid-Atlantic"                 => "Atlantic/South_Georgia",
  59          "Azores"                       => "Atlantic/Azores",
  60          "Cape Verde Is."               => "Atlantic/Cape_Verde",
  61          "Dublin"                       => "Europe/Dublin",
  62          "Edinburgh"                    => "Europe/Dublin",
  63          "Lisbon"                       => "Europe/Lisbon",
  64          "London"                       => "Europe/London",
  65          "Casablanca"                   => "Africa/Casablanca",
  66          "Monrovia"                     => "Africa/Monrovia",
  67          "UTC"                          => "Etc/UTC",
  68          "Belgrade"                     => "Europe/Belgrade",
  69          "Bratislava"                   => "Europe/Bratislava",
  70          "Budapest"                     => "Europe/Budapest",
  71          "Ljubljana"                    => "Europe/Ljubljana",
  72          "Prague"                       => "Europe/Prague",
  73          "Sarajevo"                     => "Europe/Sarajevo",
  74          "Skopje"                       => "Europe/Skopje",
  75          "Warsaw"                       => "Europe/Warsaw",
  76          "Zagreb"                       => "Europe/Zagreb",
  77          "Brussels"                     => "Europe/Brussels",
  78          "Copenhagen"                   => "Europe/Copenhagen",
  79          "Madrid"                       => "Europe/Madrid",
  80          "Paris"                        => "Europe/Paris",
  81          "Amsterdam"                    => "Europe/Amsterdam",
  82          "Berlin"                       => "Europe/Berlin",
  83          "Bern"                         => "Europe/Berlin",
  84          "Rome"                         => "Europe/Rome",
  85          "Stockholm"                    => "Europe/Stockholm",
  86          "Vienna"                       => "Europe/Vienna",
  87          "West Central Africa"          => "Africa/Algiers",
  88          "Bucharest"                    => "Europe/Bucharest",
  89          "Cairo"                        => "Africa/Cairo",
  90          "Helsinki"                     => "Europe/Helsinki",
  91          "Kyev"                         => "Europe/Kiev",
  92          "Riga"                         => "Europe/Riga",
  93          "Sofia"                        => "Europe/Sofia",
  94          "Tallinn"                      => "Europe/Tallinn",
  95          "Vilnius"                      => "Europe/Vilnius",
  96          "Athens"                       => "Europe/Athens",
  97          "Istanbul"                     => "Europe/Istanbul",
  98          "Minsk"                        => "Europe/Minsk",
  99          "Jerusalem"                    => "Asia/Jerusalem",
 100          "Harare"                       => "Africa/Harare",
 101          "Pretoria"                     => "Africa/Johannesburg",
 102          "Moscow"                       => "Europe/Moscow",
 103          "St. Petersburg"               => "Europe/Moscow",
 104          "Volgograd"                    => "Europe/Moscow",
 105          "Kuwait"                       => "Asia/Kuwait",
 106          "Riyadh"                       => "Asia/Riyadh",
 107          "Nairobi"                      => "Africa/Nairobi",
 108          "Baghdad"                      => "Asia/Baghdad",
 109          "Tehran"                       => "Asia/Tehran",
 110          "Abu Dhabi"                    => "Asia/Muscat",
 111          "Muscat"                       => "Asia/Muscat",
 112          "Baku"                         => "Asia/Baku",
 113          "Tbilisi"                      => "Asia/Tbilisi",
 114          "Yerevan"                      => "Asia/Yerevan",
 115          "Kabul"                        => "Asia/Kabul",
 116          "Ekaterinburg"                 => "Asia/Yekaterinburg",
 117          "Islamabad"                    => "Asia/Karachi",
 118          "Karachi"                      => "Asia/Karachi",
 119          "Tashkent"                     => "Asia/Tashkent",
 120          "Chennai"                      => "Asia/Kolkata",
 121          "Kolkata"                      => "Asia/Kolkata",
 122          "Mumbai"                       => "Asia/Kolkata",
 123          "New Delhi"                    => "Asia/Kolkata",
 124          "Kathmandu"                    => "Asia/Katmandu",
 125          "Astana"                       => "Asia/Dhaka",
 126          "Dhaka"                        => "Asia/Dhaka",
 127          "Sri Jayawardenepura"          => "Asia/Colombo",
 128          "Almaty"                       => "Asia/Almaty",
 129          "Novosibirsk"                  => "Asia/Novosibirsk",
 130          "Rangoon"                      => "Asia/Rangoon",
 131          "Bangkok"                      => "Asia/Bangkok",
 132          "Hanoi"                        => "Asia/Bangkok",
 133          "Jakarta"                      => "Asia/Jakarta",
 134          "Krasnoyarsk"                  => "Asia/Krasnoyarsk",
 135          "Beijing"                      => "Asia/Shanghai",
 136          "Chongqing"                    => "Asia/Chongqing",
 137          "Hong Kong"                    => "Asia/Hong_Kong",
 138          "Urumqi"                       => "Asia/Urumqi",
 139          "Kuala Lumpur"                 => "Asia/Kuala_Lumpur",
 140          "Singapore"                    => "Asia/Singapore",
 141          "Taipei"                       => "Asia/Taipei",
 142          "Perth"                        => "Australia/Perth",
 143          "Irkutsk"                      => "Asia/Irkutsk",
 144          "Ulaan Bataar"                 => "Asia/Ulaanbaatar",
 145          "Seoul"                        => "Asia/Seoul",
 146          "Osaka"                        => "Asia/Tokyo",
 147          "Sapporo"                      => "Asia/Tokyo",
 148          "Tokyo"                        => "Asia/Tokyo",
 149          "Yakutsk"                      => "Asia/Yakutsk",
 150          "Darwin"                       => "Australia/Darwin",
 151          "Adelaide"                     => "Australia/Adelaide",
 152          "Canberra"                     => "Australia/Melbourne",
 153          "Melbourne"                    => "Australia/Melbourne",
 154          "Sydney"                       => "Australia/Sydney",
 155          "Brisbane"                     => "Australia/Brisbane",
 156          "Hobart"                       => "Australia/Hobart",
 157          "Vladivostok"                  => "Asia/Vladivostok",
 158          "Guam"                         => "Pacific/Guam",
 159          "Port Moresby"                 => "Pacific/Port_Moresby",
 160          "Magadan"                      => "Asia/Magadan",
 161          "Solomon Is."                  => "Asia/Magadan",
 162          "New Caledonia"                => "Pacific/Noumea",
 163          "Fiji"                         => "Pacific/Fiji",
 164          "Kamchatka"                    => "Asia/Kamchatka",
 165          "Marshall Is."                 => "Pacific/Majuro",
 166          "Auckland"                     => "Pacific/Auckland",
 167          "Wellington"                   => "Pacific/Auckland",
 168          "Nuku'alofa"                   => "Pacific/Tongatapu"
 169        }.each { |name, zone| name.freeze; zone.freeze }
 170        MAPPING.freeze
 171      end
 172 
 173      include Comparable
 174      attr_reader :name
 175      attr_reader :tzinfo
 176 
 177      # Create a new TimeZone object with the given name and offset. The
 178      # offset is the number of seconds that this time zone is offset from UTC
 179      # (GMT). Seconds were chosen as the offset unit because that is the unit that
 180      # Ruby uses to represent time zone offsets (see Time#utc_offset).
 181      def initialize(name, utc_offset = nil, tzinfo = nil)
 182        @name = name
 183        @utc_offset = utc_offset
 184        @tzinfo = tzinfo || TimeZone.find_tzinfo(name)
 185        @current_period = nil
 186      end
 187 
 188      def utc_offset
 189        if @utc_offset
 190          @utc_offset
 191        else
 192          @current_period ||= tzinfo.current_period
 193          @current_period.utc_offset
 194        end
 195      end
 196 
 197      # Returns the offset of this time zone as a formatted string, of the
 198      # format "+HH:MM".
 199      def formatted_offset(colon=true, alternate_utc_string = nil)
 200        utc_offset == 0 && alternate_utc_string || utc_offset.to_utc_offset_s(colon)
 201      end
 202 
 203      # Compare this time zone to the parameter. The two are comapred first on
 204      # their offsets, and then by name.
 205      def <=>(zone)
 206        result = (utc_offset <=> zone.utc_offset)
 207        result = (name <=> zone.name) if result == 0
 208        result
 209      end
 210 
 211      # Compare #name and TZInfo identifier to a supplied regexp, returning true
 212      # if a match is found.
 213      def =~(re)
 214        return true if name =~ re || MAPPING[name] =~ re
 215      end
 216 
 217      # Returns a textual representation of this time zone.
 218      def to_s
 219        "(GMT#{formatted_offset}) #{name}"
 220      end
 221 
 222      # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example:
 223      #
 224      #   Time.zone = "Hawaii"                      # => "Hawaii"
 225      #   Time.zone.local(2007, 2, 1, 15, 30, 45)   # => Thu, 01 Feb 2007 15:30:45 HST -10:00
 226      def local(*args)
 227        time = Time.utc_time(*args)
 228        ActiveSupport::TimeWithZone.new(nil, self, time)
 229      end
 230 
 231      # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example:
 232      #
 233      #   Time.zone = "Hawaii"        # => "Hawaii"
 234      #   Time.utc(2000).to_f         # => 946684800.0
 235      #   Time.zone.at(946684800.0)   # => Fri, 31 Dec 1999 14:00:00 HST -10:00
 236      def at(secs)
 237        utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs)
 238        utc.in_time_zone(self)
 239      end
 240 
 241      # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example:
 242      #
 243      #   Time.zone = "Hawaii"                      # => "Hawaii"
 244      #   Time.zone.parse('1999-12-31 14:00:00')    # => Fri, 31 Dec 1999 14:00:00 HST -10:00
 245      #
 246      # If upper components are missing from the string, they are supplied from TimeZone#now:
 247      #
 248      #   Time.zone.now                 # => Fri, 31 Dec 1999 14:00:00 HST -10:00
 249      #   Time.zone.parse('22:30:00')   # => Fri, 31 Dec 1999 22:30:00 HST -10:00
 250      def parse(str, now=now)
 251        date_parts = Date._parse(str)
 252        return if date_parts.blank?
 253        time = Time.parse(str, now) rescue DateTime.parse(str)
 254        if date_parts[:offset].nil?
 255          ActiveSupport::TimeWithZone.new(nil, self, time)
 256        else
 257          time.in_time_zone(self)
 258        end
 259      end
 260 
 261      # Returns an ActiveSupport::TimeWithZone instance representing the current time
 262      # in the time zone represented by +self+. Example:
 263      #
 264      #   Time.zone = 'Hawaii'  # => "Hawaii"
 265      #   Time.zone.now         # => Wed, 23 Jan 2008 20:24:27 HST -10:00
 266      def now
 267        Time.now.utc.in_time_zone(self)
 268      end
 269 
 270      # Return the current date in this time zone.
 271      def today
 272        tzinfo.now.to_date
 273      end
 274 
 275      # Adjust the given time to the simultaneous time in the time zone represented by +self+. Returns a
 276      # Time.utc() instance -- if you want an ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead.
 277      def utc_to_local(time)
 278        tzinfo.utc_to_local(time)
 279      end
 280 
 281      # Adjust the given time to the simultaneous time in UTC. Returns a Time.utc() instance.
 282      def local_to_utc(time, dst=true)
 283        tzinfo.local_to_utc(time, dst)
 284      end
 285 
 286      # Available so that TimeZone instances respond like TZInfo::Timezone instances
 287      def period_for_utc(time)
 288        tzinfo.period_for_utc(time)
 289      end
 290 
 291      # Available so that TimeZone instances respond like TZInfo::Timezone instances
 292      def period_for_local(time, dst=true)
 293        tzinfo.period_for_local(time, dst)
 294      end
 295 
 296      # TODO: Preload instead of lazy load for thread safety
 297      def self.find_tzinfo(name)
 298        require 'tzinfo' unless defined?(TZInfo)
 299        ::TZInfo::Timezone.get(MAPPING[name] || name)
 300      rescue TZInfo::InvalidTimezoneIdentifier
 301        nil
 302      end
 303 
 304      class << self
 305        alias_method :create, :new
 306 
 307        # Return a TimeZone instance with the given name, or +nil+ if no
 308        # such TimeZone instance exists. (This exists to support the use of
 309        # this class with the +composed_of+ macro.)
 310        def new(name)
 311          self[name]
 312        end
 313 
 314        # Return an array of all TimeZone objects. There are multiple
 315        # TimeZone objects per time zone, in many cases, to make it easier
 316        # for users to find their own time zone.
 317        def all
 318          @zones ||= zones_map.values.sort
 319        end
 320 
 321        def zones_map
 322          unless defined?(@zones_map)
 323            @zones_map = {}
 324            [[-39_600, "International Date Line West", "Midway Island", "Samoa" ],
 325             [-36_000, "Hawaii" ],
 326             [-32_400, "Alaska" ],
 327             [-28_800, "Pacific Time (US & Canada)", "Tijuana" ],
 328             [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "Mazatlan",
 329                       "Arizona" ],
 330             [-21_600, "Central Time (US & Canada)", "Saskatchewan", "Guadalajara",
 331                       "Mexico City", "Monterrey", "Central America" ],
 332             [-18_000, "Eastern Time (US & Canada)", "Indiana (East)", "Bogota",
 333                       "Lima", "Quito" ],
 334             [-16_200, "Caracas" ],
 335             [-14_400, "Atlantic Time (Canada)", "La Paz", "Santiago" ],
 336             [-12_600, "Newfoundland" ],
 337             [-10_800, "Brasilia", "Buenos Aires", "Georgetown", "Greenland" ],
 338             [ -7_200, "Mid-Atlantic" ],
 339             [ -3_600, "Azores", "Cape Verde Is." ],
 340             [      0, "Dublin", "Edinburgh", "Lisbon", "London", "Casablanca",
 341                       "Monrovia", "UTC" ],
 342             [  3_600, "Belgrade", "Bratislava", "Budapest", "Ljubljana", "Prague",
 343                       "Sarajevo", "Skopje", "Warsaw", "Zagreb", "Brussels",
 344                       "Copenhagen", "Madrid", "Paris", "Amsterdam", "Berlin",
 345                       "Bern", "Rome", "Stockholm", "Vienna",
 346                       "West Central Africa" ],
 347             [  7_200, "Bucharest", "Cairo", "Helsinki", "Kyev", "Riga", "Sofia",
 348                       "Tallinn", "Vilnius", "Athens", "Istanbul", "Minsk",
 349                       "Jerusalem", "Harare", "Pretoria" ],
 350             [ 10_800, "Moscow", "St. Petersburg", "Volgograd", "Kuwait", "Riyadh",
 351                       "Nairobi", "Baghdad" ],
 352             [ 12_600, "Tehran" ],
 353             [ 14_400, "Abu Dhabi", "Muscat", "Baku", "Tbilisi", "Yerevan" ],
 354             [ 16_200, "Kabul" ],
 355             [ 18_000, "Ekaterinburg", "Islamabad", "Karachi", "Tashkent" ],
 356             [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi", "Sri Jayawardenepura" ],
 357             [ 20_700, "Kathmandu" ],
 358             [ 21_600, "Astana", "Dhaka", "Almaty",
 359                       "Novosibirsk" ],
 360             [ 23_400, "Rangoon" ],
 361             [ 25_200, "Bangkok", "Hanoi", "Jakarta", "Krasnoyarsk" ],
 362             [ 28_800, "Beijing", "Chongqing", "Hong Kong", "Urumqi",
 363                       "Kuala Lumpur", "Singapore", "Taipei", "Perth", "Irkutsk",
 364                       "Ulaan Bataar" ],
 365             [ 32_400, "Seoul", "Osaka", "Sapporo", "Tokyo", "Yakutsk" ],
 366             [ 34_200, "Darwin", "Adelaide" ],
 367             [ 36_000, "Canberra", "Melbourne", "Sydney", "Brisbane", "Hobart",
 368                       "Vladivostok", "Guam", "Port Moresby" ],
 369             [ 39_600, "Magadan", "Solomon Is.", "New Caledonia" ],
 370             [ 43_200, "Fiji", "Kamchatka", "Marshall Is.", "Auckland",
 371                       "Wellington" ],
 372             [ 46_800, "Nuku'alofa" ]].
 373             each do |offset, *places|
 374               places.each do |place|
 375                 @zones_map[place] = create(place, offset)
 376               end
 377             end
 378          end
 379          @zones_map
 380        end
 381 
 382        # Locate a specific time zone object. If the argument is a string, it
 383        # is interpreted to mean the name of the timezone to locate. If it is a
 384        # numeric value it is either the hour offset, or the second offset, of the
 385        # timezone to find. (The first one with that offset will be returned.)
 386        # Returns +nil+ if no such time zone is known to the system.
 387        def [](arg)
 388          case arg
 389            when String
 390              if tz = zones_map[arg]
 391                tz
 392              elsif tz = lookup(arg)
 393                zones_map[arg] = tz
 394              end
 395            when Numeric, ActiveSupport::Duration
 396              arg *= 3600 if arg.abs <= 13
 397              all.find { |z| z.utc_offset == arg.to_i }
 398            else
 399              raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
 400          end
 401        end
 402 
 403        # A convenience method for returning a collection of TimeZone objects
 404        # for time zones in the USA.
 405        def us_zones
 406          @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
 407        end
 408        
 409        private
 410 
 411          def lookup(name)
 412            (tzinfo = find_tzinfo(name)) && create(tzinfo.name.freeze)
 413          end
 414      end
 415    end
 416  end