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,37 @@
1
+ module TZInfo
2
+ # Represents information about a country returned by ZoneinfoDataSource.
3
+ #
4
+ # @private
5
+ class ZoneinfoCountryInfo < CountryInfo #:nodoc:
6
+ # Constructs a new CountryInfo with an ISO 3166 country code, name and
7
+ # an array of CountryTimezones.
8
+ def initialize(code, name, zones)
9
+ super(code, name)
10
+ @zones = zones.dup.freeze
11
+ @zone_identifiers = nil
12
+ end
13
+
14
+ # Returns a frozen array of all the zone identifiers for the country ordered
15
+ # geographically, most populous first.
16
+ def zone_identifiers
17
+ # Thread-safety: It is possible that the value of @zone_identifiers may be
18
+ # calculated multiple times in concurrently executing threads. It is not
19
+ # worth the overhead of locking to ensure that @zone_identifiers is only
20
+ # calculated once.
21
+
22
+ unless @zone_identifiers
23
+ result = zones.collect {|zone| zone.identifier}.freeze
24
+ return result if frozen?
25
+ @zone_identifiers = result
26
+ end
27
+
28
+ @zone_identifiers
29
+ end
30
+
31
+ # Returns a frozen array of all the timezones for the for the country
32
+ # ordered geographically, most populous first.
33
+ def zones
34
+ @zones
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,488 @@
1
+ module TZInfo
2
+ # An InvalidZoneinfoDirectory exception is raised if the DataSource is
3
+ # set to a specific zoneinfo path, which is not a valid zoneinfo directory
4
+ # (i.e. a directory containing index files named iso3166.tab and zone.tab
5
+ # as well as other timezone files).
6
+ class InvalidZoneinfoDirectory < StandardError
7
+ end
8
+
9
+ # A ZoneinfoDirectoryNotFound exception is raised if no valid zoneinfo
10
+ # directory could be found when checking the paths listed in
11
+ # ZoneinfoDataSource.search_path. A valid zoneinfo directory is one that
12
+ # contains timezone files, a country code index file named iso3166.tab and a
13
+ # timezone index file named zone1970.tab or zone.tab.
14
+ class ZoneinfoDirectoryNotFound < StandardError
15
+ end
16
+
17
+ # A DataSource that loads data from a 'zoneinfo' directory containing
18
+ # compiled "TZif" version 3 (or earlier) files in addition to iso3166.tab and
19
+ # zone1970.tab or zone.tab index files.
20
+ #
21
+ # To have TZInfo load the system zoneinfo files, call TZInfo::DataSource.set
22
+ # as follows:
23
+ #
24
+ # TZInfo::DataSource.set(:zoneinfo)
25
+ #
26
+ # To load zoneinfo files from a particular directory, pass the directory to
27
+ # TZInfo::DataSource.set:
28
+ #
29
+ # TZInfo::DataSource.set(:zoneinfo, directory)
30
+ #
31
+ # Note that the platform used at runtime may limit the range of available
32
+ # transition data that can be loaded from zoneinfo files. There are two
33
+ # factors to consider:
34
+ #
35
+ # First of all, the zoneinfo support in TZInfo makes use of Ruby's Time class.
36
+ # On 32-bit builds of Ruby 1.8, the Time class only supports 32-bit
37
+ # timestamps. This means that only Times between 1901-12-13 20:45:52 and
38
+ # 2038-01-19 03:14:07 can be represented. Furthermore, certain platforms only
39
+ # allow for positive 32-bit timestamps (notably Windows), making the earliest
40
+ # representable time 1970-01-01 00:00:00.
41
+ #
42
+ # 64-bit builds of Ruby 1.8 and all builds of Ruby 1.9 support 64-bit
43
+ # timestamps. This means that there is no practical restriction on the range
44
+ # of the Time class on these platforms.
45
+ #
46
+ # TZInfo will only load transitions that fall within the supported range of
47
+ # the Time class. Any queries performed on times outside of this range may
48
+ # give inaccurate results.
49
+ #
50
+ # The second factor concerns the zoneinfo files. Versions of the 'zic' tool
51
+ # (used to build zoneinfo files) that were released prior to February 2006
52
+ # created zoneinfo files that used 32-bit integers for transition timestamps.
53
+ # Later versions of zic produce zoneinfo files that use 64-bit integers. If
54
+ # you have 32-bit zoneinfo files on your system, then any queries falling
55
+ # outside of the range 1901-12-13 20:45:52 to 2038-01-19 03:14:07 may be
56
+ # inaccurate.
57
+ #
58
+ # Most modern platforms include 64-bit zoneinfo files. However, Mac OS X (up
59
+ # to at least 10.8.4) still uses 32-bit zoneinfo files.
60
+ #
61
+ # To check whether your zoneinfo files contain 32-bit or 64-bit transition
62
+ # data, you can run the following code (substituting the identifier of the
63
+ # zone you want to test for zone_identifier):
64
+ #
65
+ # TZInfo::DataSource.set(:zoneinfo)
66
+ # dir = TZInfo::DataSource.get.zoneinfo_dir
67
+ # File.open(File.join(dir, zone_identifier), 'r') {|f| f.read(5) }
68
+ #
69
+ # If the last line returns "TZif\\x00", then you have a 32-bit zoneinfo file.
70
+ # If it returns "TZif2" or "TZif3" then you have a 64-bit zoneinfo file.
71
+ #
72
+ # If you require support for 64-bit transitions, but are restricted to 32-bit
73
+ # zoneinfo support, then you may want to consider using TZInfo::RubyDataSource
74
+ # instead.
75
+ class ZoneinfoDataSource < DataSource
76
+ # The default value of ZoneinfoDataSource.search_path.
77
+ DEFAULT_SEARCH_PATH = ['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'].freeze
78
+
79
+ # The default value of ZoneinfoDataSource.alternate_iso3166_tab_search_path.
80
+ DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH = ['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'].freeze
81
+
82
+ # Paths to be checked to find the system zoneinfo directory.
83
+ @@search_path = DEFAULT_SEARCH_PATH.dup
84
+
85
+ # Paths to possible alternate iso3166.tab files (used to locate the
86
+ # system-wide iso3166.tab files on FreeBSD and OpenBSD).
87
+ @@alternate_iso3166_tab_search_path = DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH.dup
88
+
89
+ # An Array of directories that will be checked to find the system zoneinfo
90
+ # directory.
91
+ #
92
+ # Directories are checked in the order they appear in the Array.
93
+ #
94
+ # The default value is ['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'].
95
+ def self.search_path
96
+ @@search_path
97
+ end
98
+
99
+ # Sets the directories to be checked when locating the system zoneinfo
100
+ # directory.
101
+ #
102
+ # Can be set to an Array of directories or a String containing directories
103
+ # separated with File::PATH_SEPARATOR.
104
+ #
105
+ # Directories are checked in the order they appear in the Array or String.
106
+ #
107
+ # Set to nil to revert to the default paths.
108
+ def self.search_path=(search_path)
109
+ @@search_path = process_search_path(search_path, DEFAULT_SEARCH_PATH)
110
+ end
111
+
112
+ # An Array of paths that will be checked to find an alternate iso3166.tab
113
+ # file if one was not included in the zoneinfo directory (for example, on
114
+ # FreeBSD and OpenBSD systems).
115
+ #
116
+ # Paths are checked in the order they appear in the array.
117
+ #
118
+ # The default value is ['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'].
119
+ def self.alternate_iso3166_tab_search_path
120
+ @@alternate_iso3166_tab_search_path
121
+ end
122
+
123
+ # Sets the paths to check to locate an alternate iso3166.tab file if one was
124
+ # not included in the zoneinfo directory.
125
+ #
126
+ # Can be set to an Array of directories or a String containing directories
127
+ # separated with File::PATH_SEPARATOR.
128
+ #
129
+ # Paths are checked in the order they appear in the array.
130
+ #
131
+ # Set to nil to revert to the default paths.
132
+ def self.alternate_iso3166_tab_search_path=(alternate_iso3166_tab_search_path)
133
+ @@alternate_iso3166_tab_search_path = process_search_path(alternate_iso3166_tab_search_path, DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH)
134
+ end
135
+
136
+ # The zoneinfo directory being used.
137
+ attr_reader :zoneinfo_dir
138
+
139
+ # Creates a new ZoneinfoDataSource.
140
+ #
141
+ # If zoneinfo_dir is specified, it will be checked and used as the source
142
+ # of zoneinfo files.
143
+ #
144
+ # The directory must contain a file named iso3166.tab and a file named
145
+ # either zone1970.tab or zone.tab. These may either be included in the root
146
+ # of the directory or in a 'tab' sub-directory and named 'country.tab' and
147
+ # 'zone_sun.tab' respectively (as is the case on Solaris.
148
+ #
149
+ # Additionally, the path to iso3166.tab can be overridden using the
150
+ # alternate_iso3166_tab_path parameter.
151
+ #
152
+ # InvalidZoneinfoDirectory will be raised if the iso3166.tab and
153
+ # zone1970.tab or zone.tab files cannot be found using the zoneinfo_dir and
154
+ # alternate_iso3166_tab_path parameters.
155
+ #
156
+ # If zoneinfo_dir is not specified or nil, the paths referenced in
157
+ # search_path are searched in order to find a valid zoneinfo directory
158
+ # (one that contains zone1970.tab or zone.tab and iso3166.tab files as
159
+ # above).
160
+ #
161
+ # The paths referenced in alternate_iso3166_tab_search_path are also
162
+ # searched to find an iso3166.tab file if one of the searched zoneinfo
163
+ # directories doesn't contain an iso3166.tab file.
164
+ #
165
+ # If no valid directory can be found by searching, ZoneinfoDirectoryNotFound
166
+ # will be raised.
167
+ def initialize(zoneinfo_dir = nil, alternate_iso3166_tab_path = nil)
168
+ if zoneinfo_dir
169
+ iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(zoneinfo_dir, alternate_iso3166_tab_path)
170
+
171
+ unless iso3166_tab_path && zone_tab_path
172
+ raise InvalidZoneinfoDirectory, "#{zoneinfo_dir} is not a directory or doesn't contain a iso3166.tab file and a zone1970.tab or zone.tab file."
173
+ end
174
+
175
+ @zoneinfo_dir = zoneinfo_dir
176
+ else
177
+ @zoneinfo_dir, iso3166_tab_path, zone_tab_path = find_zoneinfo_dir
178
+
179
+ unless @zoneinfo_dir && iso3166_tab_path && zone_tab_path
180
+ raise ZoneinfoDirectoryNotFound, "None of the paths included in TZInfo::ZoneinfoDataSource.search_path are valid zoneinfo directories."
181
+ end
182
+ end
183
+
184
+ @zoneinfo_dir = File.expand_path(@zoneinfo_dir).freeze
185
+ @timezone_index = load_timezone_index.freeze
186
+ @country_index = load_country_index(iso3166_tab_path, zone_tab_path).freeze
187
+ end
188
+
189
+ # Returns a TimezoneInfo instance for a given identifier.
190
+ # Raises InvalidTimezoneIdentifier if the timezone is not found or the
191
+ # identifier is invalid.
192
+ def load_timezone_info(identifier)
193
+ begin
194
+ if @timezone_index.include?(identifier)
195
+ path = File.join(@zoneinfo_dir, identifier)
196
+
197
+ # Untaint path rather than identifier. We don't want to modify
198
+ # identifier. identifier may also be frozen and therefore cannot be
199
+ # untainted.
200
+ path.untaint
201
+
202
+ begin
203
+ ZoneinfoTimezoneInfo.new(identifier, path)
204
+ rescue InvalidZoneinfoFile => e
205
+ raise InvalidTimezoneIdentifier, e.message
206
+ end
207
+ else
208
+ raise InvalidTimezoneIdentifier, 'Invalid identifier'
209
+ end
210
+ rescue Errno::ENOENT, Errno::ENAMETOOLONG, Errno::ENOTDIR
211
+ raise InvalidTimezoneIdentifier, 'Invalid identifier'
212
+ rescue Errno::EACCES => e
213
+ raise InvalidTimezoneIdentifier, e.message
214
+ end
215
+ end
216
+
217
+ # Returns an array of all the available timezone identifiers.
218
+ def timezone_identifiers
219
+ @timezone_index
220
+ end
221
+
222
+ # Returns an array of all the available timezone identifiers for
223
+ # data timezones (i.e. those that actually contain definitions).
224
+ #
225
+ # For ZoneinfoDataSource, this will always be identical to
226
+ # timezone_identifers.
227
+ def data_timezone_identifiers
228
+ @timezone_index
229
+ end
230
+
231
+ # Returns an array of all the available timezone identifiers that
232
+ # are links to other timezones.
233
+ #
234
+ # For ZoneinfoDataSource, this will always be an empty array.
235
+ def linked_timezone_identifiers
236
+ [].freeze
237
+ end
238
+
239
+ # Returns a CountryInfo instance for the given ISO 3166-1 alpha-2
240
+ # country code. Raises InvalidCountryCode if the country could not be found
241
+ # or the code is invalid.
242
+ def load_country_info(code)
243
+ info = @country_index[code]
244
+ raise InvalidCountryCode, 'Invalid country code' unless info
245
+ info
246
+ end
247
+
248
+ # Returns an array of all the available ISO 3166-1 alpha-2
249
+ # country codes.
250
+ def country_codes
251
+ @country_index.keys.freeze
252
+ end
253
+
254
+ # Returns the name and information about this DataSource.
255
+ def to_s
256
+ "Zoneinfo DataSource: #{@zoneinfo_dir}"
257
+ end
258
+
259
+ # Returns internal object state as a programmer-readable string.
260
+ def inspect
261
+ "#<#{self.class}: #{@zoneinfo_dir}>"
262
+ end
263
+
264
+ private
265
+
266
+ # Processes a path for use as the search_path or
267
+ # alternate_iso3166_tab_search_path.
268
+ def self.process_search_path(path, default)
269
+ if path
270
+ if path.kind_of?(String)
271
+ path.split(File::PATH_SEPARATOR)
272
+ else
273
+ path.collect {|p| p.to_s}
274
+ end
275
+ else
276
+ default.dup
277
+ end
278
+ end
279
+
280
+ # Validates a zoneinfo directory and returns the paths to the iso3166.tab
281
+ # and zone1970.tab or zone.tab files if valid. If the directory is not
282
+ # valid, returns nil.
283
+ #
284
+ # The path to the iso3166.tab file may be overriden by passing in a path.
285
+ # This is treated as either absolute or relative to the current working
286
+ # directory.
287
+ def validate_zoneinfo_dir(path, iso3166_tab_path = nil)
288
+ if File.directory?(path)
289
+ if iso3166_tab_path
290
+ return nil unless File.file?(iso3166_tab_path)
291
+ else
292
+ iso3166_tab_path = resolve_tab_path(path, ['iso3166.tab'], 'country.tab')
293
+ return nil unless iso3166_tab_path
294
+ end
295
+
296
+ zone_tab_path = resolve_tab_path(path, ['zone1970.tab', 'zone.tab'], 'zone_sun.tab')
297
+ return nil unless zone_tab_path
298
+
299
+ [iso3166_tab_path, zone_tab_path]
300
+ else
301
+ nil
302
+ end
303
+ end
304
+
305
+ # Attempts to resolve the path to a tab file given its standard names and
306
+ # tab sub-directory name (as used on Solaris).
307
+ def resolve_tab_path(zoneinfo_path, standard_names, tab_name)
308
+ standard_names.each do |standard_name|
309
+ path = File.join(zoneinfo_path, standard_name)
310
+ return path if File.file?(path)
311
+ end
312
+
313
+ path = File.join(zoneinfo_path, 'tab', tab_name)
314
+ return path if File.file?(path)
315
+
316
+ nil
317
+ end
318
+
319
+ # Finds a zoneinfo directory using search_path and
320
+ # alternate_iso3166_tab_search_path. Returns the paths to the directory,
321
+ # the iso3166.tab file and the zone.tab file or nil if not found.
322
+ def find_zoneinfo_dir
323
+ alternate_iso3166_tab_path = self.class.alternate_iso3166_tab_search_path.detect do |path|
324
+ File.file?(path)
325
+ end
326
+
327
+ self.class.search_path.each do |path|
328
+ # Try without the alternate_iso3166_tab_path first.
329
+ iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path)
330
+ return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
331
+
332
+ if alternate_iso3166_tab_path
333
+ iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path, alternate_iso3166_tab_path)
334
+ return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
335
+ end
336
+ end
337
+
338
+ # Not found.
339
+ nil
340
+ end
341
+
342
+ # Scans @zoneinfo_dir and returns an Array of available timezone
343
+ # identifiers.
344
+ def load_timezone_index
345
+ index = []
346
+
347
+ # Ignoring particular files:
348
+ # +VERSION is included on Mac OS X.
349
+ # leapseconds is a list of leap seconds.
350
+ # localtime is the current local timezone (may be a link).
351
+ # posix, posixrules and right are directories containing other versions of the zoneinfo files.
352
+ # src is a directory containing the tzdata source included on Solaris.
353
+ # timeconfig is a symlink included on Slackware.
354
+
355
+ enum_timezones(nil, ['+VERSION', 'leapseconds', 'localtime', 'posix', 'posixrules', 'right', 'src', 'timeconfig']) do |identifier|
356
+ index << identifier
357
+ end
358
+
359
+ index.sort
360
+ end
361
+
362
+ # Recursively scans a directory of timezones, calling the passed in block
363
+ # for each identifier found.
364
+ def enum_timezones(dir, exclude = [], &block)
365
+ Dir.foreach(dir ? File.join(@zoneinfo_dir, dir) : @zoneinfo_dir) do |entry|
366
+ unless entry =~ /\./ || exclude.include?(entry)
367
+ entry.untaint
368
+ path = dir ? File.join(dir, entry) : entry
369
+ full_path = File.join(@zoneinfo_dir, path)
370
+
371
+ if File.directory?(full_path)
372
+ enum_timezones(path, [], &block)
373
+ elsif File.file?(full_path)
374
+ yield path
375
+ end
376
+ end
377
+ end
378
+ end
379
+
380
+ # Uses the iso3166.tab and zone1970.tab or zone.tab files to build an index
381
+ # of the available countries and their timezones.
382
+ def load_country_index(iso3166_tab_path, zone_tab_path)
383
+
384
+ # Handle standard 3 to 4 column zone.tab files as well as the 4 to 5
385
+ # column format used by Solaris.
386
+ #
387
+ # On Solaris, an extra column before the comment gives an optional
388
+ # linked/alternate timezone identifier (or '-' if not set).
389
+ #
390
+ # Additionally, there is a section at the end of the file for timezones
391
+ # covering regions. These are given lower-case "country" codes. The timezone
392
+ # identifier column refers to a continent instead of an identifier. These
393
+ # lines will be ignored by TZInfo.
394
+ #
395
+ # Since the last column is optional in both formats, testing for the
396
+ # Solaris format is done in two passes. The first pass identifies if there
397
+ # are any lines using 5 columns.
398
+
399
+
400
+ # The first column is allowed to be a comma separated list of country
401
+ # codes, as used in zone1970.tab (introduced in tzdata 2014f).
402
+ #
403
+ # The first country code in the comma-separated list is the country that
404
+ # contains the city the zone identifer is based on. The first country
405
+ # code on each line is considered to be primary with the others
406
+ # secondary.
407
+ #
408
+ # The zones for each country are ordered primary first, then secondary.
409
+ # Within the primary and secondary groups, the zones are ordered by their
410
+ # order in the file.
411
+
412
+ file_is_5_column = false
413
+ zone_tab = []
414
+
415
+ RubyCoreSupport.open_file(zone_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
416
+ file.each_line do |line|
417
+ line.chomp!
418
+
419
+ if line =~ /\A([A-Z]{2}(?:,[A-Z]{2})*)\t(?:([+\-])(\d{2})(\d{2})([+\-])(\d{3})(\d{2})|([+\-])(\d{2})(\d{2})(\d{2})([+\-])(\d{3})(\d{2})(\d{2}))\t([^\t]+)(?:\t([^\t]+))?(?:\t([^\t]+))?\z/
420
+ codes = $1
421
+
422
+ if $2
423
+ latitude = dms_to_rational($2, $3, $4)
424
+ longitude = dms_to_rational($5, $6, $7)
425
+ else
426
+ latitude = dms_to_rational($8, $9, $10, $11)
427
+ longitude = dms_to_rational($12, $13, $14, $15)
428
+ end
429
+
430
+ zone_identifier = $16
431
+ column4 = $17
432
+ column5 = $18
433
+
434
+ file_is_5_column = true if column5
435
+
436
+ zone_tab << [codes.split(','.freeze), zone_identifier, latitude, longitude, column4, column5]
437
+ end
438
+ end
439
+ end
440
+
441
+ primary_zones = {}
442
+ secondary_zones = {}
443
+
444
+ zone_tab.each do |codes, zone_identifier, latitude, longitude, column4, column5|
445
+ description = file_is_5_column ? column5 : column4
446
+ country_timezone = CountryTimezone.new(zone_identifier, latitude, longitude, description)
447
+
448
+ # codes will always have at least one element
449
+
450
+ (primary_zones[codes.first] ||= []) << country_timezone
451
+
452
+ codes[1..-1].each do |code|
453
+ (secondary_zones[code] ||= []) << country_timezone
454
+ end
455
+ end
456
+
457
+ countries = {}
458
+
459
+ RubyCoreSupport.open_file(iso3166_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
460
+ file.each_line do |line|
461
+ line.chomp!
462
+
463
+ # Handle both the two column alpha-2 and name format used in the tz
464
+ # database as well as the 4 column alpha-2, alpha-3, numeric-3 and
465
+ # name format used by FreeBSD and OpenBSD.
466
+
467
+ if line =~ /\A([A-Z]{2})(?:\t[A-Z]{3}\t[0-9]{3})?\t(.+)\z/
468
+ code = $1
469
+ name = $2
470
+ zones = (primary_zones[code] || []) + (secondary_zones[code] || [])
471
+
472
+ countries[code] = ZoneinfoCountryInfo.new(code, name, zones)
473
+ end
474
+ end
475
+ end
476
+
477
+ countries
478
+ end
479
+
480
+ # Converts degrees, minutes and seconds to a Rational.
481
+ def dms_to_rational(sign, degrees, minutes, seconds = nil)
482
+ result = degrees.to_i + Rational(minutes.to_i, 60)
483
+ result += Rational(seconds.to_i, 3600) if seconds
484
+ result = -result if sign == '-'.freeze
485
+ result
486
+ end
487
+ end
488
+ end