File: active_support/vendor/tzinfo-0.3.12/tzinfo/timezone.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: TZInfo#28
  class: AmbiguousTime#34
inherits from
  StandardError ( Builtin-Module )
  class: PeriodNotFound#38
inherits from
  StandardError ( Builtin-Module )
  class: InvalidTimezoneIdentifier#42
inherits from
  StandardError ( Builtin-Module )
  class: UnknownTimezone#46
inherits from
  StandardError ( Builtin-Module )
  class: Timezone#64
includes
  Comparable ( Builtin-Module )
inherits from
  Object ( Builtin-Module )
has properties
class method: get / 1 #78
class method: get_proxy / 1 #120
class method: new / 1 #126
class method: all #138
class method: all_identifiers #144
class method: all_data_zones #154
class method: all_data_zone_identifiers #160
class method: all_linked_zones #170
class method: all_linked_zone_identifiers #176
class method: all_country_zones #187
class method: all_country_zone_identifiers #197
class method: us_zones #208
class method: us_zone_identifiers #214
method: identifier #219
method: name #224
method: to_s #230
method: inspect #235
method: friendly_identifier / 1 #249
method: period_for_utc / 1 #288
method: periods_for_local / 1 #296
method: period_for_local / 2 #334
method: utc_to_local / 1 #373
method: local_to_utc / 2 #412
method: now #425
method: current_period #430
method: current_period_and_time #436
alias: current_time_and_period current_period_and_time #442
method: strftime / 2 #448
method: <=> / 1 #468
method: eql? / 1 #474
method: hash #479
method: _dump / 1 #484
class method: _load / 1 #489
class method: load_index #495
class method: get_proxies / 1 #504

Class Hierarchy

Code

   1  #--
   2  # Copyright (c) 2005-2006 Philip Ross
   3  # 
   4  # Permission is hereby granted, free of charge, to any person obtaining a copy
   5  # of this software and associated documentation files (the "Software"), to deal
   6  # in the Software without restriction, including without limitation the rights
   7  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8  # copies of the Software, and to permit persons to whom the Software is
   9  # furnished to do so, subject to the following conditions:
  10  # 
  11  # The above copyright notice and this permission notice shall be included in all
  12  # copies or substantial portions of the Software.
  13  #
  14  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20  # THE SOFTWARE.
  21  #++
  22 
  23  require 'date'
  24  # require 'tzinfo/country'
  25  require 'tzinfo/time_or_datetime'
  26  require 'tzinfo/timezone_period'
  27 
  28  module TZInfo
  29    # Indicate a specified time in a local timezone has more than one
  30    # possible time in UTC. This happens when switching from daylight savings time 
  31    # to normal time where the clocks are rolled back. Thrown by period_for_local
  32    # and local_to_utc when using an ambiguous time and not specifying any 
  33    # means to resolve the ambiguity.
  34    class AmbiguousTime < StandardError
  35    end
  36    
  37    # Thrown to indicate that no TimezonePeriod matching a given time could be found.
  38    class PeriodNotFound < StandardError
  39    end
  40    
  41    # Thrown by Timezone#get if the identifier given is not valid.
  42    class InvalidTimezoneIdentifier < StandardError
  43    end
  44    
  45    # Thrown if an attempt is made to use a timezone created with Timezone.new(nil).
  46    class UnknownTimezone < StandardError
  47    end
  48    
  49    # Timezone is the base class of all timezones. It provides a factory method
  50    # get to access timezones by identifier. Once a specific Timezone has been
  51    # retrieved, DateTimes, Times and timestamps can be converted between the UTC 
  52    # and the local time for the zone. For example:
  53    #
  54    #   tz = TZInfo::Timezone.get('America/New_York')
  55    #   puts tz.utc_to_local(DateTime.new(2005,8,29,15,35,0)).to_s
  56    #   puts tz.local_to_utc(Time.utc(2005,8,29,11,35,0)).to_s
  57    #   puts tz.utc_to_local(1125315300).to_s
  58    #
  59    # Each time conversion method returns an object of the same type it was 
  60    # passed.
  61    #
  62    # The timezone information all comes from the tz database
  63    # (see http://www.twinsun.com/tz/tz-link.htm)
  64    class Timezone
  65      include Comparable
  66      
  67      # Cache of loaded zones by identifier to avoid using require if a zone
  68      # has already been loaded.
  69      @@loaded_zones = {}
  70      
  71      # Whether the timezones index has been loaded yet.
  72      @@index_loaded = false
  73      
  74      # Returns a timezone by its identifier (e.g. "Europe/London", 
  75      # "America/Chicago" or "UTC").
  76      #
  77      # Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
  78      def self.get(identifier)
  79        instance = @@loaded_zones[identifier]
  80        unless instance  
  81          raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-z0-9\+\-_]+(\/[A-z0-9\+\-_]+)*$/
  82          identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
  83          begin
  84            # Use a temporary variable to avoid an rdoc warning
  85            file = "tzinfo/definitions/#{identifier}"
  86            require file
  87            
  88            m = Definitions
  89            identifier.split(/\//).each {|part|
  90              m = m.const_get(part)
  91            }
  92            
  93            info = m.get
  94            
  95            # Could make Timezone subclasses register an interest in an info
  96            # type. Since there are currently only two however, there isn't
  97            # much point.
  98            if info.kind_of?(DataTimezoneInfo)
  99              instance = DataTimezone.new(info)
 100            elsif info.kind_of?(LinkedTimezoneInfo)
 101              instance = LinkedTimezone.new(info)
 102            else
 103              raise InvalidTimezoneIdentifier, "No handler for info type #{info.class}"
 104            end
 105            
 106            @@loaded_zones[instance.identifier] = instance         
 107          rescue LoadError, NameError => e
 108            raise InvalidTimezoneIdentifier, e.message
 109          end
 110        end
 111        
 112        instance
 113      end
 114      
 115      # Returns a proxy for the Timezone with the given identifier. The proxy
 116      # will cause the real timezone to be loaded when an attempt is made to 
 117      # find a period or convert a time. get_proxy will not validate the 
 118      # identifier. If an invalid identifier is specified, no exception will be 
 119      # raised until the proxy is used. 
 120      def self.get_proxy(identifier)
 121        TimezoneProxy.new(identifier)
 122      end
 123      
 124      # If identifier is nil calls super(), otherwise calls get. An identfier 
 125      # should always be passed in when called externally.
 126      def self.new(identifier = nil)
 127        if identifier        
 128          get(identifier)
 129        else
 130          super()
 131        end
 132      end
 133      
 134      # Returns an array containing all the available Timezones.
 135      #
 136      # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
 137      # definitions until a conversion is actually required.
 138      def self.all
 139        get_proxies(all_identifiers)
 140      end
 141      
 142      # Returns an array containing the identifiers of all the available 
 143      # Timezones.
 144      def self.all_identifiers
 145        load_index
 146        Indexes::Timezones.timezones
 147      end
 148      
 149      # Returns an array containing all the available Timezones that are based
 150      # on data (are not links to other Timezones).
 151      #
 152      # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
 153      # definitions until a conversion is actually required.
 154      def self.all_data_zones
 155        get_proxies(all_data_zone_identifiers)
 156      end
 157      
 158      # Returns an array containing the identifiers of all the available 
 159      # Timezones that are based on data (are not links to other Timezones)..
 160      def self.all_data_zone_identifiers
 161        load_index
 162        Indexes::Timezones.data_timezones
 163      end
 164      
 165      # Returns an array containing all the available Timezones that are links
 166      # to other Timezones.
 167      #
 168      # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
 169      # definitions until a conversion is actually required.
 170      def self.all_linked_zones
 171        get_proxies(all_linked_zone_identifiers)      
 172      end
 173      
 174      # Returns an array containing the identifiers of all the available 
 175      # Timezones that are links to other Timezones.
 176      def self.all_linked_zone_identifiers
 177        load_index
 178        Indexes::Timezones.linked_timezones
 179      end
 180      
 181      # Returns all the Timezones defined for all Countries. This is not the
 182      # complete set of Timezones as some are not country specific (e.g. 
 183      # 'Etc/GMT').
 184      # 
 185      # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
 186      # definitions until a conversion is actually required.        
 187      def self.all_country_zones
 188        Country.all_codes.inject([]) {|zones,country|
 189          zones += Country.get(country).zones
 190        }
 191      end
 192      
 193      # Returns all the zone identifiers defined for all Countries. This is not the
 194      # complete set of zone identifiers as some are not country specific (e.g. 
 195      # 'Etc/GMT'). You can obtain a Timezone instance for a given identifier
 196      # with the get method.
 197      def self.all_country_zone_identifiers
 198        Country.all_codes.inject([]) {|zones,country|
 199          zones += Country.get(country).zone_identifiers
 200        }
 201      end
 202      
 203      # Returns all US Timezone instances. A shortcut for 
 204      # TZInfo::Country.get('US').zones.
 205      #
 206      # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
 207      # definitions until a conversion is actually required.
 208      def self.us_zones
 209        Country.get('US').zones
 210      end
 211      
 212      # Returns all US zone identifiers. A shortcut for 
 213      # TZInfo::Country.get('US').zone_identifiers.
 214      def self.us_zone_identifiers
 215        Country.get('US').zone_identifiers
 216      end
 217      
 218      # The identifier of the timezone, e.g. "Europe/Paris".
 219      def identifier
 220        raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
 221      end
 222      
 223      # An alias for identifier.
 224      def name
 225        # Don't use alias, as identifier gets overridden.
 226        identifier
 227      end
 228      
 229      # Returns a friendlier version of the identifier.
 230      def to_s
 231        friendly_identifier
 232      end
 233      
 234      # Returns internal object state as a programmer-readable string.
 235      def inspect
 236        "#<#{self.class}: #{identifier}>"
 237      end
 238      
 239      # Returns a friendlier version of the identifier. Set skip_first_part to 
 240      # omit the first part of the identifier (typically a region name) where
 241      # there is more than one part.
 242      #
 243      # For example:
 244      #
 245      #   Timezone.get('Europe/Paris').friendly_identifier(false)          #=> "Europe - Paris"
 246      #   Timezone.get('Europe/Paris').friendly_identifier(true)           #=> "Paris"
 247      #   Timezone.get('America/Indiana/Knox').friendly_identifier(false)  #=> "America - Knox, Indiana"
 248      #   Timezone.get('America/Indiana/Knox').friendly_identifier(true)   #=> "Knox, Indiana"           
 249      def friendly_identifier(skip_first_part = false)
 250        parts = identifier.split('/')
 251        if parts.empty?
 252          # shouldn't happen
 253          identifier
 254        elsif parts.length == 1        
 255          parts[0]
 256        else
 257          if skip_first_part
 258            result = ''
 259          else
 260            result = parts[0] + ' - '
 261          end
 262          
 263          parts[1, parts.length - 1].reverse_each {|part|
 264            part.gsub!(/_/, ' ')
 265            
 266            if part.index(/[a-z]/)
 267              # Missing a space if a lower case followed by an upper case and the
 268              # name isn't McXxxx.
 269              part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
 270              part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
 271              
 272              # Missing an apostrophe if two consecutive upper case characters.
 273              part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
 274            end
 275            
 276            result << part
 277            result << ', '
 278          }
 279          
 280          result.slice!(result.length - 2, 2)
 281          result
 282        end
 283      end
 284      
 285      # Returns the TimezonePeriod for the given UTC time. utc can either be
 286      # a DateTime, Time or integer timestamp (Time.to_i). Any timezone 
 287      # information in utc is ignored (it is treated as a UTC time).        
 288      def period_for_utc(utc)            
 289        raise UnknownTimezone, 'TZInfo::Timezone constructed directly'      
 290      end
 291      
 292      # Returns the set of TimezonePeriod instances that are valid for the given
 293      # local time as an array. If you just want a single period, use 
 294      # period_for_local instead and specify how ambiguities should be resolved.
 295      # Returns an empty array if no periods are found for the given time.
 296      def periods_for_local(local)
 297        raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
 298      end
 299      
 300      # Returns the TimezonePeriod for the given local time. local can either be
 301      # a DateTime, Time or integer timestamp (Time.to_i). Any timezone 
 302      # information in local is ignored (it is treated as a time in the current 
 303      # timezone).
 304      #
 305      # Warning: There are local times that have no equivalent UTC times (e.g.
 306      # in the transition from standard time to daylight savings time). There are
 307      # also local times that have more than one UTC equivalent (e.g. in the
 308      # transition from daylight savings time to standard time).
 309      #
 310      # In the first case (no equivalent UTC time), a PeriodNotFound exception
 311      # will be raised.
 312      #
 313      # In the second case (more than one equivalent UTC time), an AmbiguousTime
 314      # exception will be raised unless the optional dst parameter or block
 315      # handles the ambiguity. 
 316      #
 317      # If the ambiguity is due to a transition from daylight savings time to
 318      # standard time, the dst parameter can be used to select whether the 
 319      # daylight savings time or local time is used. For example,
 320      #
 321      #   Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
 322      #
 323      # would raise an AmbiguousTime exception.
 324      #
 325      # Specifying dst=true would the daylight savings period from April to 
 326      # October 2004. Specifying dst=false would return the standard period
 327      # from October 2004 to April 2005.
 328      #
 329      # If the dst parameter does not resolve the ambiguity, and a block is 
 330      # specified, it is called. The block must take a single parameter - an
 331      # array of the periods that need to be resolved. The block can select and
 332      # return a single period or return nil or an empty array
 333      # to cause an AmbiguousTime exception to be raised.
 334      def period_for_local(local, dst = nil)            
 335        results = periods_for_local(local)
 336        
 337        if results.empty?
 338          raise PeriodNotFound
 339        elsif results.size < 2
 340          results.first
 341        else
 342          # ambiguous result try to resolve
 343          
 344          if !dst.nil?
 345            matches = results.find_all {|period| period.dst? == dst}
 346            results = matches if !matches.empty?            
 347          end
 348          
 349          if results.size < 2
 350            results.first
 351          else
 352            # still ambiguous, try the block
 353                      
 354            if block_given?
 355              results = yield results
 356            end
 357            
 358            if results.is_a?(TimezonePeriod)
 359              results
 360            elsif results && results.size == 1
 361              results.first
 362            else          
 363              raise AmbiguousTime, "#{local} is an ambiguous local time."
 364            end
 365          end
 366        end      
 367      end
 368      
 369      # Converts a time in UTC to the local timezone. utc can either be
 370      # a DateTime, Time or timestamp (Time.to_i). The returned time has the same
 371      # type as utc. Any timezone information in utc is ignored (it is treated as 
 372      # a UTC time).
 373      def utc_to_local(utc)
 374        TimeOrDateTime.wrap(utc) {|wrapped|
 375          period_for_utc(wrapped).to_local(wrapped)
 376        }
 377      end
 378      
 379      # Converts a time in the local timezone to UTC. local can either be
 380      # a DateTime, Time or timestamp (Time.to_i). The returned time has the same
 381      # type as local. Any timezone information in local is ignored (it is treated
 382      # as a local time).
 383      #
 384      # Warning: There are local times that have no equivalent UTC times (e.g.
 385      # in the transition from standard time to daylight savings time). There are
 386      # also local times that have more than one UTC equivalent (e.g. in the
 387      # transition from daylight savings time to standard time).
 388      #
 389      # In the first case (no equivalent UTC time), a PeriodNotFound exception
 390      # will be raised.
 391      #
 392      # In the second case (more than one equivalent UTC time), an AmbiguousTime
 393      # exception will be raised unless the optional dst parameter or block
 394      # handles the ambiguity. 
 395      #
 396      # If the ambiguity is due to a transition from daylight savings time to
 397      # standard time, the dst parameter can be used to select whether the 
 398      # daylight savings time or local time is used. For example,
 399      #
 400      #   Timezone.get('America/New_York').local_to_utc(DateTime.new(2004,10,31,1,30,0))
 401      #
 402      # would raise an AmbiguousTime exception.
 403      #
 404      # Specifying dst=true would return 2004-10-31 5:30:00. Specifying dst=false
 405      # would return 2004-10-31 6:30:00.
 406      #
 407      # If the dst parameter does not resolve the ambiguity, and a block is 
 408      # specified, it is called. The block must take a single parameter - an
 409      # array of the periods that need to be resolved. The block can return a
 410      # single period to use to convert the time or return nil or an empty array
 411      # to cause an AmbiguousTime exception to be raised.
 412      def local_to_utc(local, dst = nil)
 413        TimeOrDateTime.wrap(local) {|wrapped|
 414          if block_given?
 415            period = period_for_local(wrapped, dst) {|periods| yield periods }
 416          else
 417            period = period_for_local(wrapped, dst)
 418          end
 419          
 420          period.to_utc(wrapped)
 421        }
 422      end
 423      
 424      # Returns the current time in the timezone as a Time.
 425      def now
 426        utc_to_local(Time.now.utc)
 427      end
 428      
 429      # Returns the TimezonePeriod for the current time. 
 430      def current_period
 431        period_for_utc(Time.now.utc)
 432      end
 433      
 434      # Returns the current Time and TimezonePeriod as an array. The first element
 435      # is the time, the second element is the period.
 436      def current_period_and_time
 437        utc = Time.now.utc
 438        period = period_for_utc(utc)
 439        [period.to_local(utc), period]
 440      end
 441      
 442      alias :current_time_and_period :current_period_and_time
 443 
 444      # Converts a time in UTC to local time and returns it as a string 
 445      # according to the given format. The formatting is identical to 
 446      # Time.strftime and DateTime.strftime, except %Z is replaced with the
 447      # timezone abbreviation for the specified time (for example, EST or EDT).        
 448      def strftime(format, utc = Time.now.utc)      
 449        period = period_for_utc(utc)
 450        local = period.to_local(utc)      
 451        local = Time.at(local).utc unless local.kind_of?(Time) || local.kind_of?(DateTime)
 452        abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
 453        
 454        format = format.gsub(/(.?)%Z/) do
 455          if $1 == '%'
 456            # return %%Z so the real strftime treats it as a literal %Z too
 457            '%%Z'
 458          else
 459            "#$1#{abbreviation}"
 460          end
 461        end
 462        
 463        local.strftime(format)
 464      end
 465      
 466      # Compares two Timezones based on their identifier. Returns -1 if tz is less
 467      # than self, 0 if tz is equal to self and +1 if tz is greater than self.
 468      def <=>(tz)
 469        identifier <=> tz.identifier
 470      end
 471      
 472      # Returns true if and only if the identifier of tz is equal to the 
 473      # identifier of this Timezone.
 474      def eql?(tz)
 475        self == tz
 476      end
 477      
 478      # Returns a hash of this Timezone.
 479      def hash
 480        identifier.hash
 481      end
 482      
 483      # Dumps this Timezone for marshalling.
 484      def _dump(limit)
 485        identifier
 486      end
 487      
 488      # Loads a marshalled Timezone.
 489      def self._load(data)
 490        Timezone.get(data)
 491      end
 492      
 493      private
 494        # Loads in the index of timezones if it hasn't already been loaded.
 495        def self.load_index
 496          unless @@index_loaded
 497            require 'tzinfo/indexes/timezones'
 498            @@index_loaded = true
 499          end        
 500        end
 501        
 502        # Returns an array of proxies corresponding to the given array of 
 503        # identifiers.
 504        def self.get_proxies(identifiers)
 505          identifiers.collect {|identifier| get_proxy(identifier)}
 506        end
 507    end        
 508  end