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