tzinfo 1.2.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of tzinfo might be problematic. Click here for more details.

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