Skip to content

Commit

Permalink
Default to tzinfo-data if it is available, otherwise search for a zon…
Browse files Browse the repository at this point in the history
…einfo directory.

Rename MissingDataSourceError to InvalidDataSource to better reflect what it is being used for).
  • Loading branch information
philr committed Oct 20, 2012
1 parent a0fd4c3 commit d5df522
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 38 deletions.
4 changes: 3 additions & 1 deletion tzinfo/CHANGES
Expand Up @@ -3,10 +3,12 @@
* Allow TZInfo to be used with different data sources instead of just the
built-in Ruby module data files.
* Include a data source that allows TZInfo to load data from the binary
zoneinfo files produced by zic and included with many Linux/Unix
zoneinfo files produced by zic and included with many Linux and Unix
distributions.
* Remove the definition and index Ruby modules from TZInfo and move them into
a separate TZInfo Data library (available as the tzinfo-data gem).
* Default to using the TZInfo Data library as the data source if it is
installed, otherwise use zoneinfo files instead.
* Put the full path to the TZInfo lib directory on the search path when
requiring 'tzinfo' instead of a relative path.

Expand Down
12 changes: 10 additions & 2 deletions tzinfo/README
Expand Up @@ -15,8 +15,16 @@ TZInfo can load data from:
TZInfo Data library.
2. A zoneinfo directory, typically packaged with your operating system.

By default the Ruby modules will be used. Please refer to the documentation of
TZInfo::DataSource.set for information regarding changing the data source.
By default the TZInfo Data Ruby modules will be used. If TZInfo Data is not
available (i.e. if require 'tzinfo/data' fails), then TZInfo will search for
a zoneinfo directory instead (using the paths specified in
TZInfo::ZoneinfoDataSource::DEFAULT_SEARCH_PATHS).

If no data source can be found, a TZInfo::DataSourceNotFound exception will be
raised when TZInfo is used.

The default data source selection can be overridden using
TZInfo::DataSource.set.


== Example usage
Expand Down
39 changes: 30 additions & 9 deletions tzinfo/lib/tzinfo/data_source.rb
Expand Up @@ -21,8 +21,15 @@
#++

module TZInfo
# Exception raised when no data source has been found.
class MissingDataSourceError < StandardError
# Exception raised if the DataSource is used doesn't implement one of the
# required methods.
class InvalidDataSource < StandardError
end

# Exception raised if no data source could be found (i.e. 'tzinfo/data'
# cannot be found on the load path and no valid zoneinfo directory can be
# found on the system).
class DataSourceNotFound < StandardError
end

# The base class for data sources of timezone and country data.
Expand Down Expand Up @@ -98,37 +105,37 @@ def self.set(data_source_or_type, *args)
# Raises InvalidTimezoneIdentifier if the timezone is not found or the
# identifier is invalid.
def load_timezone_info(identifier)
raise MissingDataSourceError
raise InvalidDataSource, 'load_timezone_info not defined'
end

# Returns an array of all the available timezone identifiers.
def timezone_identifiers
raise MissingDataSourceError
raise InvalidDataSource, 'timezone_identifiers not defined'
end

# Returns an array of all the available timezone identifiers for
# data timezones (i.e. those that actually contain definitions).
def data_timezone_identifiers
raise MissingDataSourceError
raise InvalidDataSource, 'data_timezone_identifiers not defined'
end

# Returns an array of all the available timezone identifiers that
# are links to other timezones.
def linked_timezone_identifiers
raise MissingDataSourceError
raise InvalidDataSource, 'linked_timezone_identifiers not defined'
end

# Returns a CountryInfo instance for the given ISO 3166-1 alpha-2
# country code. Raises InvalidCountryCode if the country could not be found
# or the code is invalid.
def load_country_info(code)
raise MissingDataSourceError
raise InvalidDataSource, 'load_country_info not defined'
end

# Returns an array of all the available ISO 3166-1 alpha-2
# country codes.
def country_codes
raise MissingDataSourceError
raise InvalidDataSource, 'country_codes not defined'
end

# Returns the name of this DataSource.
Expand All @@ -146,7 +153,21 @@ def inspect
# Creates a DataSource instance for use as the default. Used if
# no preference has been specified manually.
def self.create_default_data_source
RubyDataSource.new
has_tzinfo_data = false

begin
require 'tzinfo/data'
has_tzinfo_data = true
rescue LoadError
end

return RubyDataSource.new if has_tzinfo_data

begin
return ZoneinfoDataSource.new
rescue ZoneinfoDirectoryNotFound
raise DataSourceNotFound, 'No data source could be found. Either install tzinfo-data or specify the location to your zoneinfo files with TZInfo::ZoneinfoDataSource.search_paths='
end
end
end
end
8 changes: 4 additions & 4 deletions tzinfo/test/tc_country.rb
Expand Up @@ -163,31 +163,31 @@ def test_reload
def test_get_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Country.get('GB')
end
end

def test_new_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Country.new('GB')
end
end

def test_all_codes_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Country.all_codes
end
end

def test_all_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Country.all
end
end
Expand Down
77 changes: 73 additions & 4 deletions tzinfo/test/tc_data_source.rb
Expand Up @@ -26,11 +26,80 @@ def test_current
assert_kind_of(InitDataSource, data_source)
end

def test_current_default
DataSource.send :class_variable_set, :@@current, nil
def test_current_default_ruby_only
code = <<-EOF
require 'tmpdir'
begin
Dir.mktmpdir('tzinfo_test_dir') do |dir|
TZInfo::ZoneinfoDataSource.search_paths = [dir]
puts TZInfo::DataSource.current.class
end
rescue Exception => e
puts "Unexpected exception: \#{e}"
end
EOF

data_source = DataSource.current
assert_kind_of(RubyDataSource, data_source)
assert_sub_process_returns(['TZInfo::RubyDataSource'], code, [TZINFO_TEST_DATA_DIR])
end

def test_current_default_zoneinfo_only
code = <<-EOF
require 'tmpdir'
begin
Dir.mktmpdir('tzinfo_test_dir') do |dir|
TZInfo::ZoneinfoDataSource.search_paths = [dir, '#{TZINFO_TEST_ZONEINFO_DIR}']
puts TZInfo::DataSource.current.class
puts TZInfo::DataSource.current.zoneinfo_dir
end
rescue Exception => e
puts "Unexpected exception: \#{e}"
end
EOF

assert_sub_process_returns(
['TZInfo::ZoneinfoDataSource', TZINFO_TEST_ZONEINFO_DIR],
code)
end

def test_current_default_ruby_and_zoneinfo
code = <<-EOF
begin
TZInfo::ZoneinfoDataSource.search_paths = ['#{TZINFO_TEST_ZONEINFO_DIR}']
puts TZInfo::DataSource.current.class
rescue Exception => e
puts "Unexpected exception: \#{e}"
end
EOF

assert_sub_process_returns(['TZInfo::RubyDataSource'], code, [TZINFO_TEST_DATA_DIR])
end

def test_current_default_no_data
code = <<-EOF
require 'tmpdir'
begin
Dir.mktmpdir('tzinfo_test_dir') do |dir|
TZInfo::ZoneinfoDataSource.search_paths = [dir]
begin
data_source = TZInfo::DataSource.current
puts "No exception raised, returned \#{data_source} instead"
rescue Exception => e
puts e.class
end
end
rescue Exception => e
puts "Unexpected exception: \#{e}"
end
EOF

assert_sub_process_returns(['TZInfo::DataSourceNotFound'], code)
end

def test_set_instance
Expand Down
16 changes: 8 additions & 8 deletions tzinfo/test/tc_timezone.rb
Expand Up @@ -978,63 +978,63 @@ def test_strftime_int
def test_get_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.get('Europe/London')
end
end

def test_new_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.new('Europe/London')
end
end

def test_all_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.all
end
end

def test_all_identifiers_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.all_identifiers
end
end

def test_all_data_zones_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.all_data_zones
end
end

def test_all_data_zone_identifiers_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.all_data_zone_identifiers
end
end

def test_all_linked_zones_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.all_linked_zones
end
end

def test_all_linked_zone_identifiers_missing_data_source
DataSource.set(DataSource.new)

assert_raises(MissingDataSourceError) do
assert_raises(InvalidDataSource) do
Timezone.all_linked_zone_identifiers
end
end
Expand Down
40 changes: 30 additions & 10 deletions tzinfo/test/test_utils.rb
@@ -1,14 +1,16 @@
TESTS_DIR = File.expand_path(File.dirname(__FILE__)).untaint
TZINFO_LIB_DIR = File.expand_path(File.join(TESTS_DIR, '..', 'lib'))
TZINFO_TEST_DATA_DIR = File.join(TESTS_DIR, 'tzinfo-data')
TZINFO_TEST_ZONEINFO_DIR = File.join(TESTS_DIR, 'zoneinfo')

require 'test/unit'
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'tzinfo'))
require File.join(TZINFO_LIB_DIR, 'tzinfo')
require 'fileutils'
require 'rbconfig'

# tzinfo-data contains a cut down copy of tzinfo-data for use in the tests.
# Add it to the load path.
unless $:.include?(File.join(File.expand_path(File.dirname(__FILE__)), 'tzinfo-data'))
$:.unshift(File.join(File.expand_path(File.dirname(__FILE__)), 'tzinfo-data').untaint)

puts $:.inspect
end
$:.unshift(TZINFO_TEST_DATA_DIR) unless $:.include?(TZINFO_TEST_DATA_DIR)

module TestUtils
ZONEINFO_SYMLINKS = [
Expand All @@ -17,17 +19,15 @@ module TestUtils


def self.prepare_test_zoneinfo_dir
dir = File.expand_path(File.join(File.dirname(__FILE__), 'zoneinfo'))

ZONEINFO_SYMLINKS.each do |file, target|
path = File.join(dir, file)
path = File.join(TZINFO_TEST_ZONEINFO_DIR, file)

File.delete(path) if File.exists?(path)

begin
FileUtils.ln_s(target, path)
rescue NotImplementedError
target_path = File.join(dir, target)
target_path = File.join(TZINFO_TEST_ZONEINFO_DIR, target)
FileUtils.cp(target_path, path)
end
end
Expand Down Expand Up @@ -66,4 +66,24 @@ def assert_array_same_items(expected, actual, message = nil)
(expected.size == actual.size) && (expected - actual == [])
end
end

def assert_sub_process_returns(expected_lines, code, extra_load_path = [], required = ['tzinfo'])
ruby = File.join(RbConfig::CONFIG['bindir'],
RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT'])

load_path = [TZINFO_LIB_DIR] + extra_load_path
load_path = load_path.collect {|p| "-I \"#{p}\""}.join(' ')

required = required.collect {|r| "-r \"#{r}\""}.join(' ')

IO.popen("\"#{ruby}\" #{load_path} #{required}", 'r+') do |process|
process.puts(code)
process.flush
process.close_write

actual_lines = process.readlines
actual_lines = actual_lines.collect {|l| l.chomp}
assert_equal(expected_lines, actual_lines)
end
end
end

0 comments on commit d5df522

Please sign in to comment.