class TZInfo::TZDataParser - tzinfo-0.3.42 Documentation

# class TZInfo::TZDataParser

Parses tzdata from elsie.nci.nih.gov/pub/ and transforms it into a set of Ruby modules that can be used through Timezone and Country.

Normally, this class wouldn't be used. It is only run to update the timezone data and index modules.

### Constants

MAX_YEAR

  Maximum year that will be considered.

MIN_YEAR

  Minimum year that will be considered.

### Attributes

exclude_zones[RW]

Zones to exclude from generation when not using #only_zones (set to an array containing zone identifiers).

generate_countries[RW]

Whether to generate country definitions (set to false to stop countries being generated).

generate_zones[RW]

Whether to generate zone definitions (set to false to stop zones being generated).

only_zones[RW]

Limit the set of zones to generate (set to an array containing zone identifiers).

### Public Class Methods

new(input_dir, output_dir) click to toggle source

Initializes a new TZDataParser. input_dir must contain the extracted tzdata tarball. output_dir is the location to output the modules (in definitions and indexes directories).

Calls superclass method

    # File lib/tzinfo/tzdataparser.rb, line 59
    def initialize(input_dir, output_dir)
      super()
      @input_dir = input_dir
      @output_dir = output_dir
      @rule_sets = {}
      @zones = {}
      @countries = {}
      @no_rules = TZDataNoRules.new
      @generate_zones = true
      @generate_countries = true
      @only_zones = []
      @exclude_zones = []
    end

parse_month(month) click to toggle source

Parses a month specified in the tz data and converts it to a number between 1 and 12 representing January to December.

    # File lib/tzinfo/tzdataparser.rb, line 117
    def self.parse_month(month)
      lower = month.downcase
      if lower =~ /^jan/
        @month = 1
      elsif lower =~ /^feb/
        @month = 2
      elsif lower =~ /^mar/
        @month = 3
      elsif lower =~ /^apr/
        @month = 4
      elsif lower =~ /^may/
        @month = 5
      elsif lower =~ /^jun/
        @month = 6
      elsif lower =~ /^jul/
        @month = 7
      elsif lower =~ /^aug/
        @month = 8
      elsif lower =~ /^sep/
        @month = 9
      elsif lower =~ /^oct/
        @month = 10
      elsif lower =~ /^nov/
        @month = 11
      elsif lower =~ /^dec/
        @month = 12
      else
        raise "Invalid month: #{month}"
      end
    end

parse_offset(offset) click to toggle source

Parses an offset string [-]h:m:s (minutes and seconds are optional). Returns the offset in seconds.

    # File lib/tzinfo/tzdataparser.rb, line 150
    def self.parse_offset(offset)
      raise "Invalid time: #{offset}" if offset !~ /^(-)?(?:([0-9]+)(?::([0-9]+)(?::([0-9]+))?)?)?$/

      negative = !$1.nil?
      hour = $2.nil? ? 0 : $2.to_i
      minute = $3.nil? ? 0 : $3.to_i
      second = $4.nil? ? 0 : $4.to_i

      seconds = hour
      seconds = seconds * 60
      seconds = seconds + minute
      seconds = seconds * 60
      seconds = seconds + second
      seconds = -seconds if negative
      seconds
    end

quote_str(str) click to toggle source

Encloses the string in single quotes and escapes any single quotes in the content.

    # File lib/tzinfo/tzdataparser.rb, line 169
    def self.quote_str(str)
      "'#{str.gsub('\'', '\\\\\'')}'"
    end

### Public Instance Methods

execute() click to toggle source

Reads the tzdata source and generates the classes. Takes a long time to run. Currently outputs debugging information to standard out.

    # File lib/tzinfo/tzdataparser.rb, line 75
    def execute
      # Note that the backzone file is ignored. backzone contains alternative
      # definitions of certain zones, primarily for pre-1970 data. It is not
      # recommended for ordinary use and the tzdata Makefile does not
      # install its entries by default.

      files = Dir.entries(@input_dir).select do |file|
        file =~ /\A[^\.]+\z/ &&
          !%w(backzone checktab.awk leapseconds leapseconds.awk leap-seconds.list CONTRIBUTING Makefile NEWS README SOURCE Theory zoneinfo2tdf.pl).include?(file) &&
          File.file?(File.join(@input_dir, file))
      end

      files.each {|file| load_rules(file) }
      files.each {|file| load_zones(file) }
      files.each {|file| load_links(file) }

      load_countries

      if @generate_zones
        modules = []

        if @only_zones.nil? || @only_zones.empty?
          @zones.each_value {|zone|
            zone.write_module(@output_dir) unless @exclude_zones.include?(zone.name)
          }
        else
          @only_zones.each {|id|
            zone = @zones[id]
            zone.write_module(@output_dir)
          }
        end

        write_timezones_index
      end

      if @generate_countries
        write_countries_index
      end
    end

### Private Instance Methods

get_rules(ref) click to toggle source

Gets a rules object for the given reference. Might be a named rule set, a fixed offset or an empty ruleset.

    # File lib/tzinfo/tzdataparser.rb, line 198
    def get_rules(ref)
      if ref == '-'
        @no_rules
      elsif ref =~ /^[0-9]+:[0-9]+$/
        TZDataFixedOffsetRules.new(TZDataParser.parse_offset(ref))
      else
        rule_set = @rule_sets[ref]
        raise "Ruleset not found: #{ref}" if rule_set.nil?
        rule_set
      end
    end

load_countries() click to toggle source

Loads countries from iso3166.tab and zone.tab and stores the result in @countries.

    # File lib/tzinfo/tzdataparser.rb, line 263
    def load_countries
      puts 'load_countries'

      IO.foreach(@input_dir + File::SEPARATOR + 'iso3166.tab') {|line|
        if line =~ /^([A-Z]{2})\t(.*)$/
          code = $1
          name = $2
          @countries[code] = TZDataCountry.new(code, name)
        end
      }

      IO.foreach(@input_dir + File::SEPARATOR + 'zone.tab') {|line|
        line.chomp!

        if line =~ /^([A-Z]{2})\t([^\t]+)\t([^\t]+)(\t(.*))?$/
          code = $1
          location_str = $2
          zone_name = $3
          description = $5

          country = @countries[code]
          raise "Country not found: #{code}" if country.nil?

          location = TZDataLocation.new(location_str)

          zone = @zones[zone_name]
          raise "Zone not found: #{zone_name}" if zone.nil?

          description = nil if description == ''

          country.add_zone(TZDataCountryTimezone.new(zone, description, location))
        end
      }
    end

load_links(file) click to toggle source

Loads in the links and stores them in @zones.

    # File lib/tzinfo/tzdataparser.rb, line 244
    def load_links(file)
      puts 'load_links: ' + file

      IO.foreach(@input_dir + File::SEPARATOR + file) {|line|
        line = line.gsub(/#.*$/, '')
        line = line.gsub(/\s+$/, '')

        if line =~ /^Link\s+([^\s]+)\s+([^\s]+)/
          name = $2
          link_to = @zones[$1]
          raise "Link to zone not found (#{name}->#{link_to})" if link_to.nil?
          raise "Zone already defined: #{name}" if !@zones[name].nil?
          @zones[name] = TZDataLink.new(name, link_to)
        end
      }
    end

load_rules(file) click to toggle source

Loads all the Rule definitions from the tz data and stores them in @rule_sets.

    # File lib/tzinfo/tzdataparser.rb, line 176
    def load_rules(file)
      puts 'load_rules: ' + file

      IO.foreach(@input_dir + File::SEPARATOR + file) {|line|
        line = line.gsub(/#.*$/, '')
        line = line.gsub(/\s+$/, '')

        if line =~ /^Rule\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/

          name = $1

          if @rule_sets[name].nil?
            @rule_sets[name] = TZDataRuleSet.new(name)
          end

          @rule_sets[name].add_rule(TZDataRule.new($2, $3, $4, $5, $6, $7, $8, $9))
        end
      }
    end

load_zones(file) click to toggle source

Loads in the Zone definitions from the tz data and stores them in @zones.

    # File lib/tzinfo/tzdataparser.rb, line 211
    def load_zones(file)
      puts 'load_zones: ' + file

      in_zone = nil

      IO.foreach(@input_dir + File::SEPARATOR + file) {|line|
        line = line.gsub(/#.*$/, '')
        line = line.gsub(/\s+$/, '')

        if in_zone
          if line =~ /^\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)(\s+([0-9]+(\s+.*)?))?$/

            in_zone.add_observance(TZDataObservance.new($1, get_rules($2), $3, $5))

            in_zone = nil if $4.nil?
          end
        else
          if line =~ /^Zone\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)(\s+([0-9]+(\s+.*)?))?$/
            name = $1

            if @zones[name].nil?
              @zones[name] = TZDataZone.new(name)
            end

            @zones[name].add_observance(TZDataObservance.new($2, get_rules($3), $4, $6))

            in_zone = @zones[name] if !$5.nil?
          end
        end
      }
    end

write_countries_index() click to toggle source

Writes a country index file.

    # File lib/tzinfo/tzdataparser.rb, line 299
    def write_countries_index
      dir = @output_dir + File::SEPARATOR + 'indexes'
      FileUtils.mkdir_p(dir)

      File.open(dir + File::SEPARATOR + 'countries.rb', 'w') {|file|
        file.binmode

        file.puts('module TZInfo')
        file.puts('  module Indexes')
        file.puts('    module Countries')
        file.puts('      include CountryIndexDefinition')
        file.puts('')

        countries = @countries.values.sort {|c1,c2| c1.code <=> c2.code}
        countries.each {|country| country.write_index_record(file)}

        file.puts('    end') # end module Countries
        file.puts('  end') # end module Indexes
        file.puts('end') # end module TZInfo
      }
    end

write_timezones_index() click to toggle source

Writes a timezone index file.

    # File lib/tzinfo/tzdataparser.rb, line 322
    def write_timezones_index
      dir = File.join(@output_dir, 'indexes')
      FileUtils.mkdir_p(dir)

      File.open(File.join(dir, 'timezones.rb'), 'w') do |file|
        file.binmode

        file.puts('module TZInfo')
        file.puts('  module Indexes')
        file.puts('    module Timezones')
        file.puts('      include TimezoneIndexDefinition')
        file.puts('')

        zones = @zones.values.sort {|t1,t2| t1.name <=> t2.name}
        zones.each {|zone| zone.write_index_record(file)}

        file.puts('    end') # end module Timezones
        file.puts('  end') # end module Indexes
        file.puts('end') # end module TZInfo
      end

    end