tzinfo 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +2 -0
  3. data.tar.gz.sig +1 -0
  4. data/.yardopts +6 -0
  5. data/CHANGES.md +797 -0
  6. data/LICENSE +19 -0
  7. data/README.md +152 -0
  8. data/Rakefile +107 -0
  9. data/lib/tzinfo.rb +40 -0
  10. data/lib/tzinfo/country.rb +196 -0
  11. data/lib/tzinfo/country_index_definition.rb +31 -0
  12. data/lib/tzinfo/country_info.rb +42 -0
  13. data/lib/tzinfo/country_timezone.rb +135 -0
  14. data/lib/tzinfo/data_source.rb +190 -0
  15. data/lib/tzinfo/data_timezone.rb +58 -0
  16. data/lib/tzinfo/data_timezone_info.rb +55 -0
  17. data/lib/tzinfo/info_timezone.rb +30 -0
  18. data/lib/tzinfo/linked_timezone.rb +63 -0
  19. data/lib/tzinfo/linked_timezone_info.rb +26 -0
  20. data/lib/tzinfo/offset_rationals.rb +77 -0
  21. data/lib/tzinfo/ruby_core_support.rb +176 -0
  22. data/lib/tzinfo/ruby_country_info.rb +74 -0
  23. data/lib/tzinfo/ruby_data_source.rb +138 -0
  24. data/lib/tzinfo/time_or_datetime.rb +340 -0
  25. data/lib/tzinfo/timezone.rb +673 -0
  26. data/lib/tzinfo/timezone_definition.rb +36 -0
  27. data/lib/tzinfo/timezone_index_definition.rb +54 -0
  28. data/lib/tzinfo/timezone_info.rb +30 -0
  29. data/lib/tzinfo/timezone_offset.rb +101 -0
  30. data/lib/tzinfo/timezone_period.rb +245 -0
  31. data/lib/tzinfo/timezone_proxy.rb +105 -0
  32. data/lib/tzinfo/timezone_transition.rb +130 -0
  33. data/lib/tzinfo/timezone_transition_definition.rb +104 -0
  34. data/lib/tzinfo/transition_data_timezone_info.rb +274 -0
  35. data/lib/tzinfo/zoneinfo_country_info.rb +37 -0
  36. data/lib/tzinfo/zoneinfo_data_source.rb +496 -0
  37. data/lib/tzinfo/zoneinfo_timezone_info.rb +298 -0
  38. data/test/tc_country.rb +236 -0
  39. data/test/tc_country_index_definition.rb +69 -0
  40. data/test/tc_country_info.rb +16 -0
  41. data/test/tc_country_timezone.rb +173 -0
  42. data/test/tc_data_source.rb +218 -0
  43. data/test/tc_data_timezone.rb +99 -0
  44. data/test/tc_data_timezone_info.rb +18 -0
  45. data/test/tc_info_timezone.rb +34 -0
  46. data/test/tc_linked_timezone.rb +155 -0
  47. data/test/tc_linked_timezone_info.rb +23 -0
  48. data/test/tc_offset_rationals.rb +23 -0
  49. data/test/tc_ruby_core_support.rb +168 -0
  50. data/test/tc_ruby_country_info.rb +110 -0
  51. data/test/tc_ruby_data_source.rb +165 -0
  52. data/test/tc_time_or_datetime.rb +660 -0
  53. data/test/tc_timezone.rb +1359 -0
  54. data/test/tc_timezone_definition.rb +113 -0
  55. data/test/tc_timezone_index_definition.rb +73 -0
  56. data/test/tc_timezone_info.rb +11 -0
  57. data/test/tc_timezone_london.rb +143 -0
  58. data/test/tc_timezone_melbourne.rb +142 -0
  59. data/test/tc_timezone_new_york.rb +142 -0
  60. data/test/tc_timezone_offset.rb +126 -0
  61. data/test/tc_timezone_period.rb +555 -0
  62. data/test/tc_timezone_proxy.rb +136 -0
  63. data/test/tc_timezone_transition.rb +366 -0
  64. data/test/tc_timezone_transition_definition.rb +295 -0
  65. data/test/tc_timezone_utc.rb +27 -0
  66. data/test/tc_transition_data_timezone_info.rb +433 -0
  67. data/test/tc_zoneinfo_country_info.rb +78 -0
  68. data/test/tc_zoneinfo_data_source.rb +1204 -0
  69. data/test/tc_zoneinfo_timezone_info.rb +1234 -0
  70. data/test/test_utils.rb +188 -0
  71. data/test/ts_all.rb +7 -0
  72. data/test/ts_all_ruby.rb +5 -0
  73. data/test/ts_all_zoneinfo.rb +9 -0
  74. data/test/tzinfo-data/tzinfo/data.rb +8 -0
  75. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +89 -0
  76. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +315 -0
  77. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +218 -0
  78. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +19 -0
  79. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +21 -0
  80. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +21 -0
  81. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +21 -0
  82. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +261 -0
  83. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +186 -0
  84. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +321 -0
  85. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +265 -0
  86. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +220 -0
  87. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +16 -0
  88. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +927 -0
  89. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +596 -0
  90. data/test/tzinfo-data/tzinfo/data/version.rb +14 -0
  91. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  92. data/test/zoneinfo/America/New_York +0 -0
  93. data/test/zoneinfo/Australia/Melbourne +0 -0
  94. data/test/zoneinfo/EST +0 -0
  95. data/test/zoneinfo/Etc/UTC +0 -0
  96. data/test/zoneinfo/Europe/Amsterdam +0 -0
  97. data/test/zoneinfo/Europe/Andorra +0 -0
  98. data/test/zoneinfo/Europe/London +0 -0
  99. data/test/zoneinfo/Europe/Paris +0 -0
  100. data/test/zoneinfo/Europe/Prague +0 -0
  101. data/test/zoneinfo/Factory +0 -0
  102. data/test/zoneinfo/iso3166.tab +275 -0
  103. data/test/zoneinfo/leapseconds +61 -0
  104. data/test/zoneinfo/posix/Europe/London +0 -0
  105. data/test/zoneinfo/posixrules +0 -0
  106. data/test/zoneinfo/right/Europe/London +0 -0
  107. data/test/zoneinfo/zone.tab +439 -0
  108. data/test/zoneinfo/zone1970.tab +369 -0
  109. data/tzinfo.gemspec +21 -0
  110. metadata +191 -0
  111. metadata.gz.sig +0 -0
@@ -0,0 +1,78 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'test_utils')
2
+
3
+ include TZInfo
4
+
5
+ class TCZoneinfoCountryInfo < Minitest::Test
6
+
7
+ def test_code
8
+ ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', []) {|c| }
9
+ assert_equal('ZZ', ci.code)
10
+ end
11
+
12
+ def test_name
13
+ ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', []) {|c| }
14
+ assert_equal('Zzz', ci.name)
15
+ end
16
+
17
+ def test_zone_identifiers_empty
18
+ ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', []) {|c| }
19
+ assert(ci.zone_identifiers.empty?)
20
+ assert(ci.zone_identifiers.frozen?)
21
+ end
22
+
23
+ def test_zone_identifiers
24
+ zones = [
25
+ CountryTimezone.new('ZZ/TimezoneB', Rational(1, 2), Rational(1, 2), 'Timezone B'),
26
+ CountryTimezone.new('ZZ/TimezoneA', Rational(1, 4), Rational(1, 4), 'Timezone A'),
27
+ CountryTimezone.new('ZZ/TimezoneC', Rational(-10, 3), Rational(-20, 7), 'C'),
28
+ CountryTimezone.new('ZZ/TimezoneD', Rational(-10, 3), Rational(-20, 7))
29
+ ]
30
+
31
+ ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', zones)
32
+
33
+ assert_equal(['ZZ/TimezoneB', 'ZZ/TimezoneA', 'ZZ/TimezoneC', 'ZZ/TimezoneD'], ci.zone_identifiers)
34
+ assert(ci.zone_identifiers.frozen?)
35
+ assert(!ci.zones.equal?(zones))
36
+ assert(!zones.frozen?)
37
+ end
38
+
39
+ def test_zone_identifiers_after_freeze
40
+ zones = [
41
+ CountryTimezone.new('ZZ/TimezoneB', Rational(1, 2), Rational(1, 2), 'Timezone B'),
42
+ CountryTimezone.new('ZZ/TimezoneA', Rational(1, 4), Rational(1, 4), 'Timezone A'),
43
+ CountryTimezone.new('ZZ/TimezoneC', Rational(-10, 3), Rational(-20, 7), 'C'),
44
+ CountryTimezone.new('ZZ/TimezoneD', Rational(-10, 3), Rational(-20, 7))
45
+ ]
46
+
47
+ ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', zones)
48
+ ci.freeze
49
+
50
+ assert_equal(['ZZ/TimezoneB', 'ZZ/TimezoneA', 'ZZ/TimezoneC', 'ZZ/TimezoneD'], ci.zone_identifiers)
51
+ end
52
+
53
+ def test_zones_empty
54
+ ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', [])
55
+ assert(ci.zones.empty?)
56
+ assert(ci.zones.frozen?)
57
+ end
58
+
59
+ def test_zones
60
+ zones = [
61
+ CountryTimezone.new('ZZ/TimezoneB', Rational(1, 2), Rational(1, 2), 'Timezone B'),
62
+ CountryTimezone.new('ZZ/TimezoneA', Rational(1, 4), Rational(1, 4), 'Timezone A'),
63
+ CountryTimezone.new('ZZ/TimezoneC', Rational(-10, 3), Rational(-20, 7), 'C'),
64
+ CountryTimezone.new('ZZ/TimezoneD', Rational(-10, 3), Rational(-20, 7))
65
+ ]
66
+
67
+ ci = ZoneinfoCountryInfo.new('ZZ', 'Zzz', zones)
68
+
69
+ assert_equal([CountryTimezone.new('ZZ/TimezoneB', Rational(1, 2), Rational(1, 2), 'Timezone B'),
70
+ CountryTimezone.new('ZZ/TimezoneA', Rational(1, 4), Rational(1, 4), 'Timezone A'),
71
+ CountryTimezone.new('ZZ/TimezoneC', Rational(-10, 3), Rational(-20, 7), 'C'),
72
+ CountryTimezone.new('ZZ/TimezoneD', Rational(-10, 3), Rational(-20, 7))],
73
+ ci.zones)
74
+ assert(ci.zones.frozen?)
75
+ assert(!ci.zones.equal?(zones))
76
+ assert(!zones.frozen?)
77
+ end
78
+ end
@@ -0,0 +1,1204 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'test_utils')
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'tmpdir'
7
+
8
+ include TZInfo
9
+
10
+ # Use send as a workaround for an issue on JRuby 9.2.9.0 where using the
11
+ # refinement causes calls to RubyCoreSupport.file_open to fail to pass the block
12
+ # parameter.
13
+ #
14
+ # https://travis-ci.org/tzinfo/tzinfo/jobs/628812051#L1931
15
+ # https://github.com/jruby/jruby/issues/6009
16
+ send(:using, TZInfo::RubyCoreSupport::UntaintExt) if TZInfo::RubyCoreSupport.const_defined?(:UntaintExt)
17
+ send(:using, TaintExt) if Module.const_defined?(:TaintExt)
18
+
19
+ class TCZoneinfoDataSource < Minitest::Test
20
+ ZONEINFO_DIR = File.join(File.expand_path(File.dirname(__FILE__)), 'zoneinfo').untaint
21
+
22
+ def setup
23
+ @orig_search_path = ZoneinfoDataSource.search_path.clone
24
+ @orig_alternate_iso3166_tab_search_path = ZoneinfoDataSource.alternate_iso3166_tab_search_path.clone
25
+ @orig_pwd = FileUtils.pwd
26
+
27
+ # A zoneinfo directory containing files needed by the tests.
28
+ # The symlinks in this directory are set up in test_utils.rb.
29
+ @data_source = ZoneinfoDataSource.new(ZONEINFO_DIR)
30
+ end
31
+
32
+ def teardown
33
+ ZoneinfoDataSource.search_path = @orig_search_path
34
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = @orig_alternate_iso3166_tab_search_path
35
+ FileUtils.chdir(@orig_pwd)
36
+ end
37
+
38
+ def test_default_search_path
39
+ assert_equal(['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'], ZoneinfoDataSource.search_path)
40
+ assert_equal(false, ZoneinfoDataSource.search_path.frozen?)
41
+ end
42
+
43
+ def test_set_search_path_default
44
+ ZoneinfoDataSource.search_path = ['/tmp/zoneinfo1', '/tmp/zoneinfo2']
45
+ assert_equal(['/tmp/zoneinfo1', '/tmp/zoneinfo2'], ZoneinfoDataSource.search_path)
46
+
47
+ ZoneinfoDataSource.search_path = nil
48
+ assert_equal(['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'], ZoneinfoDataSource.search_path)
49
+ assert_equal(false, ZoneinfoDataSource.search_path.frozen?)
50
+ end
51
+
52
+ def test_set_search_path_array
53
+ path = ['/tmp/zoneinfo1', '/tmp/zoneinfo2']
54
+ ZoneinfoDataSource.search_path = path
55
+ assert_equal(['/tmp/zoneinfo1', '/tmp/zoneinfo2'], ZoneinfoDataSource.search_path)
56
+ refute_same(path, ZoneinfoDataSource.search_path)
57
+ end
58
+
59
+ def test_set_search_path_array_to_s
60
+ ZoneinfoDataSource.search_path = [Pathname.new('/tmp/zoneinfo3')]
61
+ assert_equal(['/tmp/zoneinfo3'], ZoneinfoDataSource.search_path)
62
+ end
63
+
64
+ def test_set_search_path_string
65
+ ZoneinfoDataSource.search_path = ['/tmp/zoneinfo4', '/tmp/zoneinfo5'].join(File::PATH_SEPARATOR)
66
+ assert_equal(['/tmp/zoneinfo4', '/tmp/zoneinfo5'], ZoneinfoDataSource.search_path)
67
+ end
68
+
69
+ def test_default_alternate_iso3166_tab_search_path
70
+ assert_equal(['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'], ZoneinfoDataSource.alternate_iso3166_tab_search_path)
71
+ assert_equal(false, ZoneinfoDataSource.alternate_iso3166_tab_search_path.frozen?)
72
+ end
73
+
74
+ def test_set_alternate_iso3166_tab_search_path_default
75
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = ['/tmp/iso3166.tab', '/tmp/iso3166']
76
+ assert_equal(['/tmp/iso3166.tab', '/tmp/iso3166'], ZoneinfoDataSource.alternate_iso3166_tab_search_path)
77
+
78
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = nil
79
+ assert_equal(['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'], ZoneinfoDataSource.alternate_iso3166_tab_search_path)
80
+ assert_equal(false, ZoneinfoDataSource.alternate_iso3166_tab_search_path.frozen?)
81
+ end
82
+
83
+ def test_set_alternate_iso3166_tab_search_path_array
84
+ path = ['/tmp/iso3166.tab', '/tmp/iso3166']
85
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = path
86
+ assert_equal(['/tmp/iso3166.tab', '/tmp/iso3166'], ZoneinfoDataSource.alternate_iso3166_tab_search_path)
87
+ refute_same(path, ZoneinfoDataSource.alternate_iso3166_tab_search_path)
88
+ end
89
+
90
+ def test_set_alternate_iso3166_tab_search_path_array_to_s
91
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = [Pathname.new('/tmp/iso3166.tab')]
92
+ assert_equal(['/tmp/iso3166.tab'], ZoneinfoDataSource.alternate_iso3166_tab_search_path)
93
+ end
94
+
95
+ def test_set_alternate_iso3166_tab_search_path_string
96
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = ['/tmp/iso3166.tab', '/tmp/iso3166'].join(File::PATH_SEPARATOR)
97
+ assert_equal(['/tmp/iso3166.tab', '/tmp/iso3166'], ZoneinfoDataSource.alternate_iso3166_tab_search_path)
98
+ end
99
+
100
+ def test_new_search
101
+ Dir.mktmpdir('tzinfo_test_dir1') do |dir1|
102
+ Dir.mktmpdir('tzinfo_test_dir2') do |dir2|
103
+ Dir.mktmpdir('tzinfo_test_dir3') do |dir3|
104
+ Dir.mktmpdir('tzinfo_test_dir4') do |dir4|
105
+ file = File.join(dir1, 'file')
106
+ FileUtils.touch(File.join(dir2, 'zone.tab'))
107
+ FileUtils.touch(File.join(dir3, 'iso3166.tab'))
108
+ FileUtils.touch(File.join(dir4, 'zone.tab'))
109
+ FileUtils.touch(File.join(dir4, 'iso3166.tab'))
110
+
111
+ ZoneinfoDataSource.search_path = [file, dir2, dir3, dir4]
112
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = []
113
+
114
+ data_source = ZoneinfoDataSource.new
115
+ assert_equal(dir4, data_source.zoneinfo_dir)
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ def test_new_search_zone1970
123
+ Dir.mktmpdir('tzinfo_test_dir1') do |dir1|
124
+ Dir.mktmpdir('tzinfo_test_dir2') do |dir2|
125
+ Dir.mktmpdir('tzinfo_test_dir3') do |dir3|
126
+ Dir.mktmpdir('tzinfo_test_dir4') do |dir4|
127
+ file = File.join(dir1, 'file')
128
+ FileUtils.touch(File.join(dir2, 'zone1970.tab'))
129
+ FileUtils.touch(File.join(dir3, 'iso3166.tab'))
130
+ FileUtils.touch(File.join(dir4, 'zone1970.tab'))
131
+ FileUtils.touch(File.join(dir4, 'iso3166.tab'))
132
+
133
+ ZoneinfoDataSource.search_path = [file, dir2, dir3, dir4]
134
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = []
135
+
136
+ data_source = ZoneinfoDataSource.new
137
+ assert_equal(dir4, data_source.zoneinfo_dir)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def test_new_search_solaris_tab_files
145
+ # Solaris names the tab files 'tab/country.tab' (iso3166.tab) and
146
+ # 'tab/zone_sun.tab' (zone.tab).
147
+
148
+ Dir.mktmpdir('tzinfo_test_dir') do |dir|
149
+ tab = File.join(dir, 'tab')
150
+ FileUtils.mkdir(tab)
151
+ FileUtils.touch(File.join(tab, 'country.tab'))
152
+ FileUtils.touch(File.join(tab, 'zone_sun.tab'))
153
+
154
+ ZoneinfoDataSource.search_path = [dir]
155
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = []
156
+
157
+ data_source = ZoneinfoDataSource.new
158
+ assert_equal(dir, data_source.zoneinfo_dir)
159
+ end
160
+ end
161
+
162
+ def test_new_search_alternate_iso3166_path
163
+ Dir.mktmpdir('tzinfo_test_dir_zoneinfo') do |zoneinfo_dir|
164
+ Dir.mktmpdir('tzinfo_test_dir_tab') do |tab_dir|
165
+ FileUtils.touch(File.join(zoneinfo_dir, 'zone.tab'))
166
+
167
+ tab_file = File.join(tab_dir, 'iso3166')
168
+
169
+ ZoneinfoDataSource.search_path = [zoneinfo_dir]
170
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = [tab_file]
171
+
172
+ assert_raises(ZoneinfoDirectoryNotFound) do
173
+ ZoneinfoDataSource.new
174
+ end
175
+
176
+ FileUtils.touch(tab_file)
177
+
178
+ data_source = ZoneinfoDataSource.new
179
+ assert_equal(zoneinfo_dir, data_source.zoneinfo_dir)
180
+ end
181
+ end
182
+ end
183
+
184
+ def test_new_search_not_found
185
+ Dir.mktmpdir('tzinfo_test_dir1') do |dir1|
186
+ Dir.mktmpdir('tzinfo_test_dir2') do |dir2|
187
+ Dir.mktmpdir('tzinfo_test_dir3') do |dir3|
188
+ Dir.mktmpdir('tzinfo_test_dir4') do |dir4|
189
+ Dir.mktmpdir('tzinfo_test_dir5') do |dir5|
190
+ file = File.join(dir1, 'file')
191
+ FileUtils.touch(file)
192
+ FileUtils.touch(File.join(dir2, 'zone.tab'))
193
+ FileUtils.touch(File.join(dir3, 'zone1970.tab'))
194
+ FileUtils.touch(File.join(dir4, 'iso3166.tab'))
195
+
196
+ ZoneinfoDataSource.search_path = [file, dir2, dir3, dir4, dir5]
197
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = []
198
+
199
+ assert_raises(ZoneinfoDirectoryNotFound) do
200
+ ZoneinfoDataSource.new
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
208
+
209
+ def test_new_search_relative
210
+ Dir.mktmpdir('tzinfo_test') do |dir|
211
+ FileUtils.touch(File.join(dir, 'zone.tab'))
212
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
213
+
214
+ FileUtils.chdir(dir)
215
+
216
+ ZoneinfoDataSource.search_path = ['.']
217
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = []
218
+ data_source = ZoneinfoDataSource.new
219
+ assert_equal(Pathname.new(dir).realpath.to_s, data_source.zoneinfo_dir)
220
+
221
+ # Change out of the directory to allow it to be deleted on Windows.
222
+ FileUtils.chdir(@orig_pwd)
223
+ end
224
+ end
225
+
226
+ def test_new_dir
227
+ Dir.mktmpdir('tzinfo_test') do |dir|
228
+ FileUtils.touch(File.join(dir, 'zone.tab'))
229
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
230
+
231
+ data_source = ZoneinfoDataSource.new(dir)
232
+ assert_equal(dir, data_source.zoneinfo_dir)
233
+ end
234
+ end
235
+
236
+ def test_new_dir_zone1970
237
+ Dir.mktmpdir('tzinfo_test') do |dir|
238
+ FileUtils.touch(File.join(dir, 'zone1970.tab'))
239
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
240
+
241
+ data_source = ZoneinfoDataSource.new(dir)
242
+ assert_equal(dir, data_source.zoneinfo_dir)
243
+ end
244
+ end
245
+
246
+ def test_new_dir_solaris_tab_files
247
+ # Solaris names the tab files 'tab/country.tab' (iso3166.tab) and
248
+ # 'tab/zone_sun.tab' (zone.tab).
249
+
250
+ Dir.mktmpdir('tzinfo_test') do |dir|
251
+ tab = File.join(dir, 'tab')
252
+ FileUtils.mkdir(tab)
253
+ FileUtils.touch(File.join(tab, 'country.tab'))
254
+ FileUtils.touch(File.join(tab, 'zone_sun.tab'))
255
+
256
+ data_source = ZoneinfoDataSource.new(dir)
257
+ assert_equal(dir, data_source.zoneinfo_dir)
258
+ end
259
+ end
260
+
261
+ def test_new_dir_alternate_iso3166_path
262
+ Dir.mktmpdir('tzinfo_test_dir_zoneinfo') do |zoneinfo_dir|
263
+ Dir.mktmpdir('tzinfo_test_dir_tab') do |tab_dir|
264
+ FileUtils.touch(File.join(zoneinfo_dir, 'zone.tab'))
265
+
266
+ tab_file = File.join(tab_dir, 'iso3166')
267
+ FileUtils.touch(tab_file)
268
+
269
+ ZoneinfoDataSource.alternate_iso3166_tab_search_path = [tab_file]
270
+
271
+ assert_raises(InvalidZoneinfoDirectory) do
272
+ # The alternate_iso3166_tab_search_path should not be used. This should raise
273
+ # an exception.
274
+ ZoneinfoDataSource.new(zoneinfo_dir)
275
+ end
276
+
277
+ data_source = ZoneinfoDataSource.new(zoneinfo_dir, tab_file)
278
+ assert_equal(zoneinfo_dir, data_source.zoneinfo_dir)
279
+ end
280
+ end
281
+ end
282
+
283
+ def test_new_dir_invalid
284
+ Dir.mktmpdir('tzinfo_test') do |dir|
285
+ assert_raises(InvalidZoneinfoDirectory) do
286
+ ZoneinfoDataSource.new(dir)
287
+ end
288
+ end
289
+ end
290
+
291
+ def test_new_dir_invalid_alternate_iso3166_path
292
+ Dir.mktmpdir('tzinfo_test_dir_zoneinfo') do |zoneinfo_dir|
293
+ Dir.mktmpdir('tzinfo_test_dir_tab') do |tab_dir|
294
+ FileUtils.touch(File.join(zoneinfo_dir, 'zone.tab'))
295
+
296
+ assert_raises(InvalidZoneinfoDirectory) do
297
+ ZoneinfoDataSource.new(zoneinfo_dir, File.join(tab_dir, 'iso3166'))
298
+ end
299
+ end
300
+ end
301
+ end
302
+
303
+ def test_new_dir_invalid_alternate_iso3166_path_overrides_valid
304
+ Dir.mktmpdir('tzinfo_test_dir_zoneinfo') do |zoneinfo_dir|
305
+ Dir.mktmpdir('tzinfo_test_dir_tab') do |tab_dir|
306
+ FileUtils.touch(File.join(zoneinfo_dir, 'iso3166.tab'))
307
+ FileUtils.touch(File.join(zoneinfo_dir, 'zone.tab'))
308
+
309
+ assert_raises(InvalidZoneinfoDirectory) do
310
+ ZoneinfoDataSource.new(zoneinfo_dir, File.join(tab_dir, 'iso3166'))
311
+ end
312
+ end
313
+ end
314
+ end
315
+
316
+ def test_new_file
317
+ Dir.mktmpdir('tzinfo_test') do |dir|
318
+ file = File.join(dir, 'file')
319
+ FileUtils.touch(file)
320
+
321
+ assert_raises(InvalidZoneinfoDirectory) do
322
+ ZoneinfoDataSource.new(file)
323
+ end
324
+ end
325
+ end
326
+
327
+ def test_new_dir_relative
328
+ Dir.mktmpdir('tzinfo_test') do |dir|
329
+ FileUtils.touch(File.join(dir, 'zone.tab'))
330
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
331
+
332
+ FileUtils.chdir(dir)
333
+
334
+ data_source = ZoneinfoDataSource.new('.')
335
+ assert_equal(Pathname.new(dir).realpath.to_s, data_source.zoneinfo_dir)
336
+
337
+ # Change out of the directory to allow it to be deleted on Windows.
338
+ FileUtils.chdir(@orig_pwd)
339
+ end
340
+ end
341
+
342
+ def test_zoneinfo_dir
343
+ Dir.mktmpdir('tzinfo_test') do |dir|
344
+ FileUtils.touch(File.join(dir, 'zone.tab'))
345
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
346
+
347
+ data_source = ZoneinfoDataSource.new(dir)
348
+ assert_equal(dir, data_source.zoneinfo_dir)
349
+ assert_equal(true, data_source.zoneinfo_dir.frozen?)
350
+ end
351
+ end
352
+
353
+ def test_load_timezone_info_data
354
+ info = @data_source.load_timezone_info('Europe/London')
355
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
356
+ assert_equal('Europe/London', info.identifier)
357
+ end
358
+
359
+ def test_load_timezone_info_linked
360
+ info = @data_source.load_timezone_info('UTC')
361
+
362
+ # On platforms that don't support symlinks, 'UTC' will be created as a copy.
363
+ # Either way, a ZoneinfoTimezoneInfo should be returned.
364
+
365
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
366
+ assert_equal('UTC', info.identifier)
367
+ end
368
+
369
+ def test_load_timezone_info_does_not_exist
370
+ assert_raises(InvalidTimezoneIdentifier) do
371
+ @data_source.load_timezone_info('Nowhere/Special')
372
+ end
373
+ end
374
+
375
+ def test_load_timezone_info_invalid
376
+ assert_raises(InvalidTimezoneIdentifier) do
377
+ @data_source.load_timezone_info('../Definitions/Europe/London')
378
+ end
379
+ end
380
+
381
+ %w(leapseconds localtime).each do |file_name|
382
+ define_method("test_load_timezone_info_ignored_#{file_name}_file") do
383
+ assert_raises(InvalidTimezoneIdentifier) do
384
+ @data_source.load_timezone_info(file_name)
385
+ end
386
+ end
387
+ end
388
+
389
+ def test_load_timezone_info_ignored_plus_version_file
390
+ # Mac OS X includes a file named +VERSION containing the tzdata version.
391
+
392
+ Dir.mktmpdir('tzinfo_test') do |dir|
393
+ FileUtils.touch(File.join(dir, 'zone.tab'))
394
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
395
+
396
+ File.open(File.join(dir, '+VERSION'), 'w') do |f|
397
+ f.binmode
398
+ f.write("2013a\n")
399
+ end
400
+
401
+ data_source = ZoneinfoDataSource.new(dir)
402
+
403
+ assert_raises(InvalidTimezoneIdentifier) do
404
+ data_source.load_timezone_info('+VERSION')
405
+ end
406
+ end
407
+ end
408
+
409
+ def test_load_timezone_info_ignored_timeconfig_symlink
410
+ # Slackware includes a symlink named timeconfig that points at /usr/sbin/timeconfig.
411
+
412
+ Dir.mktmpdir('tzinfo_test_target') do |target_dir|
413
+ target_path = File.join(target_dir, 'timeconfig')
414
+
415
+ File.open(target_path, 'w') do |f|
416
+ f.write("#!/bin/sh\n")
417
+ f.write("#\n")
418
+ f.write('# timeconfig Slackware Linux timezone configuration utility.\n')
419
+ end
420
+
421
+ Dir.mktmpdir('tzinfo_test') do |dir|
422
+ FileUtils.touch(File.join(dir, 'zone.tab'))
423
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
424
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(dir, 'EST'))
425
+
426
+ symlink_path = File.join(dir, 'timeconfig')
427
+ begin
428
+ FileUtils.ln_s(target_path, symlink_path)
429
+ rescue NotImplementedError, Errno::EACCES
430
+ # Symlinks not supported on this platform, or permission denied
431
+ # (administrative rights are required on Windows). Copy instead.
432
+ FileUtils.cp(target_path, symlink_path)
433
+ end
434
+
435
+ data_source = ZoneinfoDataSource.new(dir)
436
+
437
+ assert_raises(InvalidTimezoneIdentifier) do
438
+ data_source.load_timezone_info('timeconfig')
439
+ end
440
+ end
441
+ end
442
+ end
443
+
444
+ def test_load_timezone_info_nil
445
+ assert_raises(InvalidTimezoneIdentifier) do
446
+ @data_source.load_timezone_info(nil)
447
+ end
448
+ end
449
+
450
+ def test_load_timezone_info_case
451
+ assert_raises(InvalidTimezoneIdentifier) do
452
+ @data_source.load_timezone_info('europe/london')
453
+ end
454
+ end
455
+
456
+ def test_load_timezone_info_permission_denied
457
+ Dir.mktmpdir('tzinfo_test') do |dir|
458
+ FileUtils.touch(File.join(dir, 'zone.tab'))
459
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
460
+
461
+ file = File.join(dir, 'UTC')
462
+ FileUtils.touch(file)
463
+ FileUtils.chmod(0200, file)
464
+
465
+ data_source = ZoneinfoDataSource.new(dir)
466
+
467
+ assert_raises(InvalidTimezoneIdentifier) do
468
+ data_source.load_timezone_info('UTC')
469
+ end
470
+ end
471
+ end
472
+
473
+ def test_load_timezone_info_directory
474
+ Dir.mktmpdir('tzinfo_test') do |dir|
475
+ FileUtils.touch(File.join(dir, 'zone.tab'))
476
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
477
+
478
+ subdir = File.join(dir, 'Subdir')
479
+ FileUtils.mkdir(subdir)
480
+
481
+ data_source = ZoneinfoDataSource.new(dir)
482
+
483
+ assert_raises(InvalidTimezoneIdentifier) do
484
+ data_source.load_timezone_info('Subdir')
485
+ end
486
+ end
487
+ end
488
+
489
+ def test_load_timezone_info_linked_absolute_outside
490
+ Dir.mktmpdir('tzinfo_test') do |dir|
491
+ Dir.mktmpdir('tzinfo_test') do |outside|
492
+ outside_file = File.join(outside, 'EST')
493
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), outside_file)
494
+
495
+ FileUtils.touch(File.join(dir, 'zone.tab'))
496
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
497
+
498
+ file = File.join(dir, 'EST')
499
+
500
+ begin
501
+ FileUtils.ln_s(outside_file, file)
502
+ rescue NotImplementedError, Errno::EACCES
503
+ # Symlinks not supported on this platform, or permission denied
504
+ # (administrative rights are required on Windows). Skip test.
505
+ return
506
+ end
507
+
508
+ data_source = ZoneinfoDataSource.new(dir)
509
+
510
+ info = data_source.load_timezone_info('EST')
511
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
512
+ assert_equal('EST', info.identifier)
513
+ end
514
+ end
515
+ end
516
+
517
+ def test_load_timezone_info_linked_absolute_inside
518
+ Dir.mktmpdir('tzinfo_test') do |dir|
519
+ FileUtils.touch(File.join(dir, 'zone.tab'))
520
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
521
+
522
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(dir, 'EST'))
523
+
524
+ link = File.join(dir, 'Link')
525
+
526
+ begin
527
+ FileUtils.ln_s(File.join(File.expand_path(dir), 'EST'), link)
528
+ rescue NotImplementedError, Errno::EACCES
529
+ # Symlinks not supported on this platform, or permission denied
530
+ # (administrative rights are required on Windows). Skip test.
531
+ return
532
+ end
533
+
534
+ data_source = ZoneinfoDataSource.new(dir)
535
+
536
+ info = data_source.load_timezone_info('Link')
537
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
538
+ assert_equal('Link', info.identifier)
539
+ end
540
+ end
541
+
542
+ def test_load_timezone_info_linked_relative_outside
543
+ Dir.mktmpdir('tzinfo_test') do |root|
544
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(root, 'outside'))
545
+
546
+ dir = File.join(root, 'zoneinfo')
547
+ FileUtils.mkdir(dir)
548
+
549
+ FileUtils.touch(File.join(dir, 'zone.tab'))
550
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
551
+
552
+ link = File.join(dir, 'Link')
553
+
554
+ begin
555
+ FileUtils.ln_s('../outside', link)
556
+ rescue NotImplementedError, Errno::EACCES
557
+ # Symlinks not supported on this platform, or permission denied
558
+ # (administrative rights are required on Windows). Skip test.
559
+ return
560
+ end
561
+
562
+ subdir = File.join(dir, 'Subdir')
563
+ subdir_link = File.join(subdir, 'Link')
564
+ FileUtils.mkdir(subdir)
565
+ FileUtils.ln_s('../../outside', subdir_link)
566
+
567
+ data_source = ZoneinfoDataSource.new(dir)
568
+
569
+ info = data_source.load_timezone_info('Link')
570
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
571
+ assert_equal('Link', info.identifier)
572
+
573
+ info = data_source.load_timezone_info('Subdir/Link')
574
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
575
+ assert_equal('Subdir/Link', info.identifier)
576
+ end
577
+ end
578
+
579
+ def test_load_timezone_info_linked_relative_parent_inside
580
+ Dir.mktmpdir('tzinfo_test') do |dir|
581
+ FileUtils.touch(File.join(dir, 'zone.tab'))
582
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
583
+
584
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(dir, 'EST'))
585
+
586
+ subdir = File.join(dir, 'Subdir')
587
+ FileUtils.mkdir(subdir)
588
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(subdir, 'EST'))
589
+
590
+ subdir_link = File.join(subdir, 'Link')
591
+ begin
592
+ FileUtils.ln_s('../Subdir/EST', subdir_link)
593
+ rescue NotImplementedError, Errno::EACCES
594
+ # Symlinks not supported on this platform, or permission denied
595
+ # (administrative rights are required on Windows). Skip test.
596
+ return
597
+ end
598
+
599
+ subdir_link2 = File.join(subdir, 'Link2')
600
+ FileUtils.ln_s('../EST', subdir_link2)
601
+
602
+ subdir2 = File.join(dir, 'Subdir2')
603
+ FileUtils.mkdir(subdir2)
604
+ subdir2_link = File.join(subdir2, 'Link')
605
+ FileUtils.ln_s('../Subdir/EST', subdir2_link)
606
+
607
+ data_source = ZoneinfoDataSource.new(dir)
608
+
609
+ info = data_source.load_timezone_info('Subdir/Link')
610
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
611
+ assert_equal('Subdir/Link', info.identifier)
612
+
613
+ info = data_source.load_timezone_info('Subdir/Link2')
614
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
615
+ assert_equal('Subdir/Link2', info.identifier)
616
+
617
+ info = data_source.load_timezone_info('Subdir2/Link')
618
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
619
+ assert_equal('Subdir2/Link', info.identifier)
620
+ end
621
+ end
622
+
623
+ def test_load_timezone_info_invalid_file
624
+ Dir.mktmpdir('tzinfo_test') do |dir|
625
+ FileUtils.touch(File.join(dir, 'zone.tab'))
626
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
627
+
628
+ File.open(File.join(dir, 'Zone'), 'wb') do |file|
629
+ file.write('NotAValidTZifFile')
630
+ end
631
+
632
+ data_source = ZoneinfoDataSource.new(dir)
633
+
634
+ assert_raises(InvalidTimezoneIdentifier) do
635
+ data_source.load_timezone_info('Zone')
636
+ end
637
+ end
638
+ end
639
+
640
+ def test_load_timezone_info_invalid_file_2
641
+ Dir.mktmpdir('tzinfo_test') do |dir|
642
+ FileUtils.touch(File.join(dir, 'zone.tab'))
643
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
644
+
645
+ zone = File.join(dir, 'Zone')
646
+
647
+ File.open(File.join(@data_source.zoneinfo_dir, 'EST')) do |src|
648
+ # Change header to TZif1 (which is not a valid header).
649
+ File.open(zone, 'wb') do |dest|
650
+ dest.write('TZif1')
651
+ src.pos = 5
652
+ FileUtils.copy_stream(src, dest)
653
+ end
654
+ end
655
+
656
+ data_source = ZoneinfoDataSource.new(dir)
657
+
658
+ assert_raises(InvalidTimezoneIdentifier) do
659
+ data_source.load_timezone_info('Zone')
660
+ end
661
+ end
662
+ end
663
+
664
+ def test_load_timezone_info_tainted
665
+ safe_test(:unavailable => :skip) do
666
+ identifier = 'Europe/Amsterdam'.dup.taint
667
+ assert(identifier.tainted?)
668
+ info = @data_source.load_timezone_info(identifier)
669
+ assert_equal('Europe/Amsterdam', info.identifier)
670
+ assert(identifier.tainted?)
671
+ end
672
+ end
673
+
674
+ def test_load_timezone_info_tainted_and_frozen
675
+ safe_test do
676
+ info = @data_source.load_timezone_info('Europe/Amsterdam'.dup.taint.freeze)
677
+ assert_equal('Europe/Amsterdam', info.identifier)
678
+ end
679
+ end
680
+
681
+ def test_load_timezone_info_tainted_zoneinfo_dir_safe_mode
682
+ safe_test(:unavailable => :skip) do
683
+ assert_raises(SecurityError) do
684
+ ZoneinfoDataSource.new(@data_source.zoneinfo_dir.dup.taint)
685
+ end
686
+ end
687
+ end
688
+
689
+ def test_load_timezone_info_tainted_zoneinfo_dir
690
+ data_source = ZoneinfoDataSource.new(@data_source.zoneinfo_dir.dup.taint)
691
+ info = data_source.load_timezone_info('Europe/London')
692
+ assert_kind_of(ZoneinfoTimezoneInfo, info)
693
+ assert_equal('Europe/London', info.identifier)
694
+ end
695
+
696
+ def get_timezone_filenames(directory)
697
+ entries = Dir.glob(File.join(directory, '**', '*'))
698
+
699
+ entries = entries.select do |file|
700
+ file.untaint
701
+ File.file?(file)
702
+ end
703
+
704
+ entries = entries.collect {|file| file[directory.length + File::SEPARATOR.length, file.length - directory.length - File::SEPARATOR.length]}
705
+
706
+ # Exclude right (with leapseconds) and posix (copy) directories; .tab files; leapseconds, localtime and posixrules files.
707
+ entries = entries.select do |file|
708
+ file !~ /\A(posix|right)\// &&
709
+ file !~ /\.tab\z/ &&
710
+ !%w(leapseconds localtime posixrules).include?(file)
711
+ end
712
+
713
+ entries.sort
714
+ end
715
+
716
+ def test_timezone_identifiers
717
+ expected = get_timezone_filenames(@data_source.zoneinfo_dir)
718
+ all = @data_source.timezone_identifiers
719
+ assert_kind_of(Array, all)
720
+ assert_array_same_items(expected, all)
721
+ assert_equal(true, all.frozen?)
722
+ end
723
+
724
+ def test_data_timezone_identifiers
725
+ expected = get_timezone_filenames(@data_source.zoneinfo_dir)
726
+ all_data = @data_source.data_timezone_identifiers
727
+ assert_kind_of(Array, all_data)
728
+ assert_array_same_items(expected, all_data)
729
+ assert_equal(true, all_data.frozen?)
730
+ end
731
+
732
+ def test_linked_timezone_identifiers
733
+ all_linked = @data_source.linked_timezone_identifiers
734
+ assert_kind_of(Array, all_linked)
735
+ assert_equal(true, all_linked.empty?)
736
+ assert_equal(true, all_linked.frozen?)
737
+ end
738
+
739
+ def test_timezone_identifiers_safe_mode
740
+ safe_test do
741
+ expected = get_timezone_filenames(@data_source.zoneinfo_dir)
742
+ all = @data_source.timezone_identifiers
743
+ assert_kind_of(Array, all)
744
+ assert_array_same_items(expected, all)
745
+ assert_equal(true, all.frozen?)
746
+ end
747
+ end
748
+
749
+ def test_timezone_identifiers_ignored_plus_version_file
750
+ # Mac OS X includes a file named +VERSION containing the tzdata version.
751
+
752
+ Dir.mktmpdir('tzinfo_test') do |dir|
753
+ FileUtils.touch(File.join(dir, 'zone.tab'))
754
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
755
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(dir, 'EST'))
756
+
757
+ File.open(File.join(dir, '+VERSION'), 'w') do |f|
758
+ f.binmode
759
+ f.write("2013a\n")
760
+ end
761
+
762
+ data_source = ZoneinfoDataSource.new(dir)
763
+ assert_array_same_items(['EST'], data_source.timezone_identifiers)
764
+ end
765
+ end
766
+
767
+ def test_timezone_identifiers_ignored_timeconfig_symlink
768
+ # Slackware includes a symlink named timeconfig that points at /usr/sbin/timeconfig.
769
+
770
+ Dir.mktmpdir('tzinfo_test_target') do |target_dir|
771
+ target_path = File.join(target_dir, 'timeconfig')
772
+
773
+ File.open(target_path, 'w') do |f|
774
+ f.write("#!/bin/sh\n")
775
+ f.write("#\n")
776
+ f.write('# timeconfig Slackware Linux timezone configuration utility.\n')
777
+ end
778
+
779
+ Dir.mktmpdir('tzinfo_test') do |dir|
780
+ FileUtils.touch(File.join(dir, 'zone.tab'))
781
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
782
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(dir, 'EST'))
783
+
784
+ symlink_path = File.join(dir, 'timeconfig')
785
+ begin
786
+ FileUtils.ln_s(target_path, symlink_path)
787
+ rescue NotImplementedError, Errno::EACCES
788
+ # Symlinks not supported on this platform, or permission denied
789
+ # (administrative rights are required on Windows). Copy instead.
790
+ FileUtils.cp(target_path, symlink_path)
791
+ end
792
+
793
+ data_source = ZoneinfoDataSource.new(dir)
794
+ assert_array_same_items(['EST'], data_source.timezone_identifiers)
795
+ end
796
+ end
797
+ end
798
+
799
+ def test_timezone_identifiers_ignored_src_directory
800
+ # Solaris includes a src directory containing the source timezone data files
801
+ # from the tzdata distribution. These should be ignored.
802
+
803
+ Dir.mktmpdir('tzinfo_test') do |dir|
804
+ FileUtils.touch(File.join(dir, 'zone.tab'))
805
+ FileUtils.touch(File.join(dir, 'iso3166.tab'))
806
+ FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), File.join(dir, 'EST'))
807
+
808
+ src_dir = File.join(dir, 'src')
809
+ FileUtils.mkdir(src_dir)
810
+
811
+ File.open(File.join(src_dir, 'europe'), 'w') do |f|
812
+ f.binmode
813
+ f.write("Zone\tEurope/London\t0:00\tEU\tGMT/BST\n")
814
+ end
815
+
816
+ data_source = ZoneinfoDataSource.new(dir)
817
+ assert_array_same_items(['EST'], data_source.timezone_identifiers)
818
+ end
819
+ end
820
+
821
+ def test_load_country_info
822
+ info = @data_source.load_country_info('GB')
823
+ assert_equal('GB', info.code)
824
+ assert_equal('Britain (UK)', info.name)
825
+ end
826
+
827
+ def test_load_country_info_not_exist
828
+ assert_raises(InvalidCountryCode) do
829
+ @data_source.load_country_info('ZZ')
830
+ end
831
+ end
832
+
833
+ def test_load_country_info_invalid
834
+ assert_raises(InvalidCountryCode) do
835
+ @data_source.load_country_info('../Countries/GB')
836
+ end
837
+ end
838
+
839
+ def test_load_country_info_nil
840
+ assert_raises(InvalidCountryCode) do
841
+ @data_source.load_country_info(nil)
842
+ end
843
+ end
844
+
845
+ def test_load_country_info_case
846
+ assert_raises(InvalidCountryCode) do
847
+ @data_source.load_country_info('gb')
848
+ end
849
+ end
850
+
851
+ def test_load_country_info_tainted
852
+ safe_test(:unavailable => :skip) do
853
+ code = 'NL'.dup.taint
854
+ assert(code.tainted?)
855
+ info = @data_source.load_country_info(code)
856
+ assert_equal('NL', info.code)
857
+ assert(code.tainted?)
858
+ end
859
+ end
860
+
861
+ def test_load_country_info_tainted_and_frozen
862
+ safe_test do
863
+ info = @data_source.load_country_info('NL'.dup.taint.freeze)
864
+ assert_equal('NL', info.code)
865
+ end
866
+ end
867
+
868
+ def test_load_country_info_check_zones
869
+ Dir.mktmpdir('tzinfo_test') do |dir|
870
+ RubyCoreSupport.open_file(File.join(dir, 'iso3166.tab'), 'w', :external_encoding => 'UTF-8') do |iso3166|
871
+ iso3166.puts('# iso3166.tab')
872
+ iso3166.puts('')
873
+ iso3166.puts("FC\tFake Country")
874
+ iso3166.puts("OC\tOther Country")
875
+ end
876
+
877
+ RubyCoreSupport.open_file(File.join(dir, 'zone.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
878
+ zone.puts('# zone.tab')
879
+ zone.puts('')
880
+ zone.puts("FC\t+513030-0000731\tFake/One\tDescription of one")
881
+ zone.puts("FC\t+353916+1394441\tFake/Two\tAnother description")
882
+ zone.puts("FC\t-2332-04637\tFake/Three\tThis is Three")
883
+ zone.puts("OC\t+5005+01426\tOther/One")
884
+ end
885
+
886
+ data_source = ZoneinfoDataSource.new(dir)
887
+
888
+ info = data_source.load_country_info('FC')
889
+ assert_equal('FC', info.code)
890
+ assert_equal('Fake Country', info.name)
891
+ assert_equal(['Fake/One', 'Fake/Two', 'Fake/Three'], info.zone_identifiers)
892
+ assert_equal(true, info.zone_identifiers.frozen?)
893
+ assert_equal([
894
+ CountryTimezone.new('Fake/One', Rational(6181, 120), Rational(-451, 3600), 'Description of one'),
895
+ CountryTimezone.new('Fake/Two', Rational(32089, 900), Rational(503081, 3600), 'Another description'),
896
+ CountryTimezone.new('Fake/Three', Rational(-353, 15), Rational(-2797, 60), 'This is Three')], info.zones)
897
+ assert_equal(true, info.zones.frozen?)
898
+
899
+ info = data_source.load_country_info('OC')
900
+ assert_equal('OC', info.code)
901
+ assert_equal('Other Country', info.name)
902
+ assert_equal(['Other/One'], info.zone_identifiers)
903
+ assert_equal(true, info.zone_identifiers.frozen?)
904
+ assert_equal([CountryTimezone.new('Other/One', Rational(601, 12), Rational(433, 30))], info.zones)
905
+ assert_equal(true, info.zones.frozen?)
906
+ end
907
+ end
908
+
909
+ def test_load_country_info_check_zones_zone1970
910
+ Dir.mktmpdir('tzinfo_test') do |dir|
911
+ RubyCoreSupport.open_file(File.join(dir, 'iso3166.tab'), 'w', :external_encoding => 'UTF-8') do |iso3166|
912
+ iso3166.puts('# iso3166.tab')
913
+ iso3166.puts('')
914
+ iso3166.puts("AC\tAnother Country")
915
+ iso3166.puts("FC\tFake Country")
916
+ iso3166.puts("OC\tOther Country")
917
+ end
918
+
919
+ # zone.tab will be ignored.
920
+ RubyCoreSupport.open_file(File.join(dir, 'zone.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
921
+ zone.puts('# zone.tab')
922
+ zone.puts('')
923
+ zone.puts("FC\t+513030-0000731\tFake/One\tDescription of one")
924
+ zone.puts("FC\t+353916+1394441\tFake/Two\tAnother description")
925
+ zone.puts("FC\t-2332-04637\tFake/Three\tThis is Three")
926
+ zone.puts("OC\t+5005+01426\tOther/One")
927
+ end
928
+
929
+ # zone1970.tab will be used.
930
+ RubyCoreSupport.open_file(File.join(dir, 'zone1970.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
931
+ zone.puts('# zone1970.tab')
932
+ zone.puts('')
933
+ zone.puts("AC,OC\t+0000+00000\tMiddle/Another/One\tAnother's One")
934
+ zone.puts("FC\t+513030-0000731\tFake/One\tDescription of one")
935
+ zone.puts("FC,OC\t+353916+1394441\tFake/Two\tAnother description")
936
+ zone.puts("FC,OC\t-2332-04637\tFake/Three\tThis is Three")
937
+ zone.puts("OC\t+5005+01426\tOther/One")
938
+ zone.puts("OC\t+5015+11426\tOther/Two")
939
+ end
940
+
941
+ data_source = ZoneinfoDataSource.new(dir)
942
+
943
+ info = data_source.load_country_info('AC')
944
+ assert_equal('AC', info.code)
945
+ assert_equal('Another Country', info.name)
946
+ assert_equal(['Middle/Another/One'], info.zone_identifiers)
947
+ assert_equal(true, info.zone_identifiers.frozen?)
948
+ assert_equal([CountryTimezone.new('Middle/Another/One', Rational(0, 1), Rational(0, 1), "Another's One")], info.zones)
949
+ assert_equal(true, info.zones.frozen?)
950
+
951
+ info = data_source.load_country_info('FC')
952
+ assert_equal('FC', info.code)
953
+ assert_equal('Fake Country', info.name)
954
+ assert_equal(['Fake/One', 'Fake/Two', 'Fake/Three'], info.zone_identifiers)
955
+ assert_equal(true, info.zone_identifiers.frozen?)
956
+ assert_equal([
957
+ CountryTimezone.new('Fake/One', Rational(6181, 120), Rational(-451, 3600), 'Description of one'),
958
+ CountryTimezone.new('Fake/Two', Rational(32089, 900), Rational(503081, 3600), 'Another description'),
959
+ CountryTimezone.new('Fake/Three', Rational(-353, 15), Rational(-2797, 60), 'This is Three')], info.zones)
960
+ assert_equal(true, info.zones.frozen?)
961
+
962
+ # Testing the ordering of zones. A zone can either be primary (country
963
+ # code is the first in the first column), or secondary (country code is
964
+ # not the first). Should return all the primaries first in the order they
965
+ # appeared in the file, followed by all the secondaries in the order they
966
+ # appeared in file.
967
+
968
+ info = data_source.load_country_info('OC')
969
+ assert_equal('OC', info.code)
970
+ assert_equal('Other Country', info.name)
971
+ assert_equal(['Other/One', 'Other/Two', 'Middle/Another/One', 'Fake/Two', 'Fake/Three'], info.zone_identifiers)
972
+ assert_equal(true, info.zone_identifiers.frozen?)
973
+ assert_equal([
974
+ CountryTimezone.new('Other/One', Rational(601, 12), Rational( 433, 30)),
975
+ CountryTimezone.new('Other/Two', Rational(201, 4), Rational(3433, 30)),
976
+ CountryTimezone.new('Middle/Another/One', Rational(0, 1), Rational(0, 1), "Another's One"),
977
+ CountryTimezone.new('Fake/Two', Rational(32089, 900), Rational(503081, 3600), 'Another description'),
978
+ CountryTimezone.new('Fake/Three', Rational(-353, 15), Rational(-2797, 60), 'This is Three')], info.zones)
979
+ assert_equal(true, info.zones.frozen?)
980
+ end
981
+ end
982
+
983
+ def test_load_country_info_check_zones_solaris_tab_files
984
+ # Solaris uses 5 columns instead of the usual 4 in zone_sun.tab.
985
+ # An extra column before the comment gives an optional linked/alternate
986
+ # timezone identifier (or '-' if not set).
987
+ #
988
+ # Additionally, there is a section at the end of the file for timezones
989
+ # covering regions. These are given lower-case "country" codes. The timezone
990
+ # identifier column refers to a continent instead of an identifier. These
991
+ # lines will be ignored by TZInfo.
992
+
993
+ Dir.mktmpdir('tzinfo_test') do |dir|
994
+ tab_dir = File.join(dir, 'tab')
995
+ FileUtils.mkdir(tab_dir)
996
+
997
+ RubyCoreSupport.open_file(File.join(tab_dir, 'country.tab'), 'w', :external_encoding => 'UTF-8') do |country|
998
+ country.puts('# country.tab')
999
+ country.puts('# Solaris')
1000
+ country.puts("FC\tFake Country")
1001
+ country.puts("OC\tOther Country")
1002
+ end
1003
+
1004
+ RubyCoreSupport.open_file(File.join(tab_dir, 'zone_sun.tab'), 'w', :external_encoding => 'UTF-8') do |zone_sun|
1005
+ zone_sun.puts('# zone_sun.tab')
1006
+ zone_sun.puts('# Solaris')
1007
+ zone_sun.puts('# Countries')
1008
+ zone_sun.puts("FC\t+513030-0000731\tFake/One\t-\tDescription of one")
1009
+ zone_sun.puts("FC\t+353916+1394441\tFake/Two\tFake/Alias/Two\tAnother description")
1010
+ zone_sun.puts("FC\t-2332-04637\tFake/Three\tFake/Alias/Three\tThis is Three")
1011
+ zone_sun.puts("OC\t+5005+01426\tOther/One\tOther/Linked/One")
1012
+ zone_sun.puts("OC\t+5015+01436\tOther/Two\t-")
1013
+ zone_sun.puts('# Regions')
1014
+ zone_sun.puts("ee\t+0000+00000\tEurope/\tEET")
1015
+ zone_sun.puts("me\t+0000+00000\tEurope/\tMET")
1016
+ zone_sun.puts("we\t+0000+00000\tEurope/\tWET")
1017
+ end
1018
+
1019
+ data_source = ZoneinfoDataSource.new(dir)
1020
+
1021
+ info = data_source.load_country_info('FC')
1022
+ assert_equal('FC', info.code)
1023
+ assert_equal('Fake Country', info.name)
1024
+ assert_equal(['Fake/One', 'Fake/Two', 'Fake/Three'], info.zone_identifiers)
1025
+ assert_equal(true, info.zone_identifiers.frozen?)
1026
+ assert_equal([
1027
+ CountryTimezone.new('Fake/One', Rational(6181, 120), Rational(-451, 3600), 'Description of one'),
1028
+ CountryTimezone.new('Fake/Two', Rational(32089, 900), Rational(503081, 3600), 'Another description'),
1029
+ CountryTimezone.new('Fake/Three', Rational(-353, 15), Rational(-2797, 60), 'This is Three')], info.zones)
1030
+ assert_equal(true, info.zones.frozen?)
1031
+
1032
+ info = data_source.load_country_info('OC')
1033
+ assert_equal('OC', info.code)
1034
+ assert_equal('Other Country', info.name)
1035
+ assert_equal(['Other/One', 'Other/Two'], info.zone_identifiers)
1036
+ assert_equal(true, info.zone_identifiers.frozen?)
1037
+ assert_equal([
1038
+ CountryTimezone.new('Other/One', Rational(601, 12), Rational(433, 30)),
1039
+ CountryTimezone.new('Other/Two', Rational(201, 4), Rational(73, 5))], info.zones)
1040
+ assert_equal(true, info.zones.frozen?)
1041
+ end
1042
+ end
1043
+
1044
+ def test_load_country_info_check_zones_alternate_iso3166_file
1045
+ Dir.mktmpdir('tzinfo_test') do |dir|
1046
+ zoneinfo_dir = File.join(dir, 'zoneinfo')
1047
+ tab_dir = File.join(dir, 'tab')
1048
+ FileUtils.mkdir(zoneinfo_dir)
1049
+ FileUtils.mkdir(tab_dir)
1050
+
1051
+ tab_file = File.join(tab_dir, 'iso3166')
1052
+ RubyCoreSupport.open_file(tab_file, 'w', :external_encoding => 'UTF-8') do |iso3166|
1053
+ # Use the BSD 4 column format (alternate iso3166 is used on BSD).
1054
+ iso3166.puts("FC\tFCC\t001\tFake Country")
1055
+ iso3166.puts("OC\tOCC\t002\tOther Country")
1056
+ end
1057
+
1058
+ RubyCoreSupport.open_file(File.join(zoneinfo_dir, 'zone.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
1059
+ zone.puts("FC\t+513030-0000731\tFake/One\tDescription of one")
1060
+ zone.puts("FC\t+353916+1394441\tFake/Two\tAnother description")
1061
+ zone.puts("FC\t-2332-04637\tFake/Three\tThis is Three")
1062
+ zone.puts("OC\t+5005+01426\tOther/One")
1063
+ end
1064
+
1065
+ data_source = ZoneinfoDataSource.new(zoneinfo_dir, tab_file)
1066
+
1067
+ info = data_source.load_country_info('FC')
1068
+ assert_equal('FC', info.code)
1069
+ assert_equal('Fake Country', info.name)
1070
+ assert_equal(['Fake/One', 'Fake/Two', 'Fake/Three'], info.zone_identifiers)
1071
+ assert_equal(true, info.zone_identifiers.frozen?)
1072
+ assert_equal([
1073
+ CountryTimezone.new('Fake/One', Rational(6181, 120), Rational(-451, 3600), 'Description of one'),
1074
+ CountryTimezone.new('Fake/Two', Rational(32089, 900), Rational(503081, 3600), 'Another description'),
1075
+ CountryTimezone.new('Fake/Three', Rational(-353, 15), Rational(-2797, 60), 'This is Three')], info.zones)
1076
+ assert_equal(true, info.zones.frozen?)
1077
+
1078
+ info = data_source.load_country_info('OC')
1079
+ assert_equal('OC', info.code)
1080
+ assert_equal('Other Country', info.name)
1081
+ assert_equal(['Other/One'], info.zone_identifiers)
1082
+ assert_equal(true, info.zone_identifiers.frozen?)
1083
+ assert_equal([CountryTimezone.new('Other/One', Rational(601, 12), Rational(433, 30))], info.zones)
1084
+ assert_equal(true, info.zones.frozen?)
1085
+ end
1086
+ end
1087
+
1088
+ def test_load_country_info_four_column_iso31611
1089
+ # OpenBSD and FreeBSD use a 4 column iso3166.tab file that includes
1090
+ # alpha-3 and numeric-3 codes in addition to the alpha-2 and name in the
1091
+ # tz database version.
1092
+
1093
+ Dir.mktmpdir('tzinfo_test') do |dir|
1094
+ RubyCoreSupport.open_file(File.join(dir, 'iso3166.tab'), 'w', :external_encoding => 'UTF-8') do |iso3166|
1095
+ iso3166.puts("FC\tFCC\t001\tFake Country")
1096
+ iso3166.puts("OC\tOCC\t002\tOther Country")
1097
+ end
1098
+
1099
+ RubyCoreSupport.open_file(File.join(dir, 'zone.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
1100
+ zone.puts("FC\t+513030-0000731\tFake/One\tDescription of one")
1101
+ zone.puts("OC\t+5005+01426\tOther/One")
1102
+ end
1103
+
1104
+ data_source = ZoneinfoDataSource.new(dir)
1105
+
1106
+ info = data_source.load_country_info('FC')
1107
+ assert_equal('FC', info.code)
1108
+ assert_equal('Fake Country', info.name)
1109
+
1110
+ info = data_source.load_country_info('OC')
1111
+ assert_equal('OC', info.code)
1112
+ assert_equal('Other Country', info.name)
1113
+ end
1114
+ end
1115
+
1116
+ def test_load_country_info_utf8
1117
+ # iso3166.tab is currently in ASCII (as of tzdata 2014f), but will be
1118
+ # changed to UTF-8 in the future.
1119
+
1120
+ # zone.tab is in ASCII, with no plans to change. Since ASCII is a subset of
1121
+ # UTF-8, test that this is loaded in UTF-8 anyway.
1122
+
1123
+ Dir.mktmpdir('tzinfo_test') do |dir|
1124
+ RubyCoreSupport.open_file(File.join(dir, 'iso3166.tab'), 'w', :external_encoding => 'UTF-8') do |iso3166|
1125
+ iso3166.puts("UT\tUnicode Test ✓")
1126
+ end
1127
+
1128
+ RubyCoreSupport.open_file(File.join(dir, 'zone.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
1129
+ zone.puts("UT\t+513030-0000731\tUnicode✓/One\tUnicode Description ✓")
1130
+ end
1131
+
1132
+ data_source = ZoneinfoDataSource.new(dir)
1133
+
1134
+ info = data_source.load_country_info('UT')
1135
+ assert_equal('UT', info.code)
1136
+ assert_equal('Unicode Test ✓', info.name)
1137
+ assert_equal(['Unicode✓/One'], info.zone_identifiers)
1138
+ assert_equal([CountryTimezone.new('Unicode✓/One', Rational(6181, 120), Rational(-451, 3600), 'Unicode Description ✓')], info.zones)
1139
+ end
1140
+ end
1141
+
1142
+ def test_load_country_info_utf8_zone1970
1143
+ # iso3166.tab is currently in ASCII (as of tzdata 2014f), but will be
1144
+ # changed to UTF-8 in the future.
1145
+
1146
+ # zone1970.tab is in UTF-8.
1147
+
1148
+ Dir.mktmpdir('tzinfo_test') do |dir|
1149
+ RubyCoreSupport.open_file(File.join(dir, 'iso3166.tab'), 'w', :external_encoding => 'UTF-8') do |iso3166|
1150
+ iso3166.puts("UT\tUnicode Test ✓")
1151
+ end
1152
+
1153
+ RubyCoreSupport.open_file(File.join(dir, 'zone1970.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
1154
+ zone.puts("UT\t+513030-0000731\tUnicode✓/One\tUnicode Description ✓")
1155
+ end
1156
+
1157
+ data_source = ZoneinfoDataSource.new(dir)
1158
+
1159
+ info = data_source.load_country_info('UT')
1160
+ assert_equal('UT', info.code)
1161
+ assert_equal('Unicode Test ✓', info.name)
1162
+ assert_equal(['Unicode✓/One'], info.zone_identifiers)
1163
+ assert_equal([CountryTimezone.new('Unicode✓/One', Rational(6181, 120), Rational(-451, 3600), 'Unicode Description ✓')], info.zones)
1164
+ end
1165
+ end
1166
+
1167
+ def test_country_codes
1168
+ file_codes = []
1169
+
1170
+ RubyCoreSupport.open_file(File.join(@data_source.zoneinfo_dir, 'iso3166.tab'), 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
1171
+ file.each_line do |line|
1172
+ line.chomp!
1173
+ file_codes << $1 if line =~ /\A([A-Z]{2})\t/
1174
+ end
1175
+ end
1176
+
1177
+ codes = @data_source.country_codes
1178
+ assert_array_same_items(file_codes, codes)
1179
+ assert_equal(true, codes.frozen?)
1180
+ end
1181
+
1182
+ def test_country_codes_four_column_iso3166
1183
+ # OpenBSD and FreeBSD use a 4 column iso3166.tab file that includes
1184
+ # alpha-3 and numeric-3 codes in addition to the alpha-2 and name in the
1185
+ # tz database version.
1186
+
1187
+ Dir.mktmpdir('tzinfo_test') do |dir|
1188
+ RubyCoreSupport.open_file(File.join(dir, 'iso3166.tab'), 'w', :external_encoding => 'UTF-8') do |iso3166|
1189
+ iso3166.puts("FC\tFCC\t001\tFake Country")
1190
+ iso3166.puts("OC\tOCC\t002\tOther Country")
1191
+ end
1192
+
1193
+ RubyCoreSupport.open_file(File.join(dir, 'zone.tab'), 'w', :external_encoding => 'UTF-8') do |zone|
1194
+ zone.puts("FC\t+513030-0000731\tFake/One\tDescription of one")
1195
+ zone.puts("OC\t+5005+01426\tOther/One")
1196
+ end
1197
+
1198
+ data_source = ZoneinfoDataSource.new(dir)
1199
+
1200
+ codes = data_source.country_codes
1201
+ assert_array_same_items(%w(FC OC), codes)
1202
+ end
1203
+ end
1204
+ end