tzinfo 1.2.11 → 2.0.0

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 (151) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +3 -0
  4. data/CHANGES.md +469 -431
  5. data/LICENSE +13 -13
  6. data/README.md +368 -114
  7. data/lib/tzinfo/country.rb +131 -129
  8. data/lib/tzinfo/country_timezone.rb +70 -112
  9. data/lib/tzinfo/data_source.rb +389 -144
  10. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  11. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  12. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  13. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  14. data/lib/tzinfo/data_sources/ruby_data_source.rb +141 -0
  15. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  16. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  17. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +573 -0
  18. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +284 -0
  19. data/lib/tzinfo/data_sources.rb +8 -0
  20. data/lib/tzinfo/data_timezone.rb +33 -47
  21. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  22. data/lib/tzinfo/format1/country_definer.rb +17 -0
  23. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  24. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  25. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  26. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  27. data/lib/tzinfo/format1.rb +10 -0
  28. data/lib/tzinfo/format2/country_definer.rb +68 -0
  29. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  30. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  31. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  32. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  33. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  34. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  35. data/lib/tzinfo/format2.rb +10 -0
  36. data/lib/tzinfo/info_timezone.rb +26 -21
  37. data/lib/tzinfo/linked_timezone.rb +33 -52
  38. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  39. data/lib/tzinfo/string_deduper.rb +118 -0
  40. data/lib/tzinfo/time_with_offset.rb +128 -0
  41. data/lib/tzinfo/timestamp.rb +548 -0
  42. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  43. data/lib/tzinfo/timezone.rb +979 -502
  44. data/lib/tzinfo/timezone_offset.rb +84 -74
  45. data/lib/tzinfo/timezone_period.rb +151 -217
  46. data/lib/tzinfo/timezone_proxy.rb +70 -79
  47. data/lib/tzinfo/timezone_transition.rb +77 -109
  48. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  49. data/lib/tzinfo/version.rb +7 -0
  50. data/lib/tzinfo/with_offset.rb +61 -0
  51. data/lib/tzinfo.rb +60 -40
  52. data.tar.gz.sig +0 -0
  53. metadata +51 -115
  54. metadata.gz.sig +2 -3
  55. data/Rakefile +0 -107
  56. data/lib/tzinfo/annual_rules.rb +0 -51
  57. data/lib/tzinfo/country_index_definition.rb +0 -31
  58. data/lib/tzinfo/country_info.rb +0 -42
  59. data/lib/tzinfo/data_timezone_info.rb +0 -55
  60. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  61. data/lib/tzinfo/offset_rationals.rb +0 -77
  62. data/lib/tzinfo/posix_time_zone_parser.rb +0 -136
  63. data/lib/tzinfo/ruby_core_support.rb +0 -176
  64. data/lib/tzinfo/ruby_country_info.rb +0 -74
  65. data/lib/tzinfo/ruby_data_source.rb +0 -136
  66. data/lib/tzinfo/time_or_datetime.rb +0 -351
  67. data/lib/tzinfo/timezone_definition.rb +0 -36
  68. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  69. data/lib/tzinfo/timezone_info.rb +0 -30
  70. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  71. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  72. data/lib/tzinfo/transition_rule.rb +0 -325
  73. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  74. data/lib/tzinfo/zoneinfo_data_source.rb +0 -504
  75. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -516
  76. data/test/assets/payload.rb +0 -1
  77. data/test/tc_annual_rules.rb +0 -95
  78. data/test/tc_country.rb +0 -240
  79. data/test/tc_country_index_definition.rb +0 -69
  80. data/test/tc_country_info.rb +0 -16
  81. data/test/tc_country_timezone.rb +0 -173
  82. data/test/tc_data_source.rb +0 -218
  83. data/test/tc_data_timezone.rb +0 -99
  84. data/test/tc_data_timezone_info.rb +0 -18
  85. data/test/tc_info_timezone.rb +0 -34
  86. data/test/tc_linked_timezone.rb +0 -155
  87. data/test/tc_linked_timezone_info.rb +0 -23
  88. data/test/tc_offset_rationals.rb +0 -23
  89. data/test/tc_posix_time_zone_parser.rb +0 -261
  90. data/test/tc_ruby_core_support.rb +0 -168
  91. data/test/tc_ruby_country_info.rb +0 -110
  92. data/test/tc_ruby_data_source.rb +0 -175
  93. data/test/tc_time_or_datetime.rb +0 -674
  94. data/test/tc_timezone.rb +0 -1361
  95. data/test/tc_timezone_definition.rb +0 -113
  96. data/test/tc_timezone_index_definition.rb +0 -73
  97. data/test/tc_timezone_info.rb +0 -11
  98. data/test/tc_timezone_london.rb +0 -143
  99. data/test/tc_timezone_melbourne.rb +0 -142
  100. data/test/tc_timezone_new_york.rb +0 -142
  101. data/test/tc_timezone_offset.rb +0 -126
  102. data/test/tc_timezone_period.rb +0 -555
  103. data/test/tc_timezone_proxy.rb +0 -136
  104. data/test/tc_timezone_transition.rb +0 -366
  105. data/test/tc_timezone_transition_definition.rb +0 -295
  106. data/test/tc_timezone_utc.rb +0 -27
  107. data/test/tc_transition_data_timezone_info.rb +0 -433
  108. data/test/tc_transition_rule.rb +0 -663
  109. data/test/tc_zoneinfo_country_info.rb +0 -78
  110. data/test/tc_zoneinfo_data_source.rb +0 -1226
  111. data/test/tc_zoneinfo_timezone_info.rb +0 -2149
  112. data/test/test_utils.rb +0 -214
  113. data/test/ts_all.rb +0 -7
  114. data/test/ts_all_ruby.rb +0 -5
  115. data/test/ts_all_zoneinfo.rb +0 -9
  116. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  117. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -327
  118. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -230
  119. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  120. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  121. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  122. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  123. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -273
  124. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -198
  125. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -333
  126. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -277
  127. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -235
  128. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  129. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -940
  130. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -609
  131. data/test/tzinfo-data/tzinfo/data/version.rb +0 -20
  132. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  133. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  134. data/test/zoneinfo/America/New_York +0 -0
  135. data/test/zoneinfo/Australia/Melbourne +0 -0
  136. data/test/zoneinfo/EST +0 -0
  137. data/test/zoneinfo/Etc/UTC +0 -0
  138. data/test/zoneinfo/Europe/Amsterdam +0 -0
  139. data/test/zoneinfo/Europe/Andorra +0 -0
  140. data/test/zoneinfo/Europe/London +0 -0
  141. data/test/zoneinfo/Europe/Paris +0 -0
  142. data/test/zoneinfo/Europe/Prague +0 -0
  143. data/test/zoneinfo/Factory +0 -0
  144. data/test/zoneinfo/iso3166.tab +0 -274
  145. data/test/zoneinfo/leapseconds +0 -78
  146. data/test/zoneinfo/posix/Europe/London +0 -0
  147. data/test/zoneinfo/posixrules +0 -0
  148. data/test/zoneinfo/right/Europe/London +0 -0
  149. data/test/zoneinfo/zone.tab +0 -452
  150. data/test/zoneinfo/zone1970.tab +0 -384
  151. data/tzinfo.gemspec +0 -21
@@ -1,504 +0,0 @@
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
- # File and directories in the top level zoneinfo directory that will be
83
- # excluded from the list of available time zones:
84
- #
85
- # - +VERSION is included on Mac OS X.
86
- # - leapseconds is a list of leap seconds.
87
- # - localtime is the current local timezone (may be a link).
88
- # - posix, posixrules and right are directories containing other versions
89
- # of the zoneinfo files.
90
- # - SECURITY is included in the Arch Linux tzdata package.
91
- # - src is a directory containing the tzdata source included on Solaris.
92
- # - timeconfig is a symlink included on Slackware.
93
- EXCLUDED_FILENAMES = [
94
- '+VERSION',
95
- 'leapseconds',
96
- 'localtime',
97
- 'posix',
98
- 'posixrules',
99
- 'right',
100
- 'SECURITY',
101
- 'src',
102
- 'timeconfig'
103
- ].freeze
104
-
105
- # Paths to be checked to find the system zoneinfo directory.
106
- @@search_path = DEFAULT_SEARCH_PATH.dup
107
-
108
- # Paths to possible alternate iso3166.tab files (used to locate the
109
- # system-wide iso3166.tab files on FreeBSD and OpenBSD).
110
- @@alternate_iso3166_tab_search_path = DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH.dup
111
-
112
- # An Array of directories that will be checked to find the system zoneinfo
113
- # directory.
114
- #
115
- # Directories are checked in the order they appear in the Array.
116
- #
117
- # The default value is ['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'].
118
- def self.search_path
119
- @@search_path
120
- end
121
-
122
- # Sets the directories to be checked when locating the system zoneinfo
123
- # directory.
124
- #
125
- # Can be set to an Array of directories or a String containing directories
126
- # separated with File::PATH_SEPARATOR.
127
- #
128
- # Directories are checked in the order they appear in the Array or String.
129
- #
130
- # Set to nil to revert to the default paths.
131
- def self.search_path=(search_path)
132
- @@search_path = process_search_path(search_path, DEFAULT_SEARCH_PATH)
133
- end
134
-
135
- # An Array of paths that will be checked to find an alternate iso3166.tab
136
- # file if one was not included in the zoneinfo directory (for example, on
137
- # FreeBSD and OpenBSD systems).
138
- #
139
- # Paths are checked in the order they appear in the array.
140
- #
141
- # The default value is ['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'].
142
- def self.alternate_iso3166_tab_search_path
143
- @@alternate_iso3166_tab_search_path
144
- end
145
-
146
- # Sets the paths to check to locate an alternate iso3166.tab file if one was
147
- # not included in the zoneinfo directory.
148
- #
149
- # Can be set to an Array of directories or a String containing directories
150
- # separated with File::PATH_SEPARATOR.
151
- #
152
- # Paths are checked in the order they appear in the array.
153
- #
154
- # Set to nil to revert to the default paths.
155
- def self.alternate_iso3166_tab_search_path=(alternate_iso3166_tab_search_path)
156
- @@alternate_iso3166_tab_search_path = process_search_path(alternate_iso3166_tab_search_path, DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH)
157
- end
158
-
159
- # The zoneinfo directory being used.
160
- attr_reader :zoneinfo_dir
161
-
162
- # Creates a new ZoneinfoDataSource.
163
- #
164
- # If zoneinfo_dir is specified, it will be checked and used as the source
165
- # of zoneinfo files.
166
- #
167
- # The directory must contain a file named iso3166.tab and a file named
168
- # either zone1970.tab or zone.tab. These may either be included in the root
169
- # of the directory or in a 'tab' sub-directory and named 'country.tab' and
170
- # 'zone_sun.tab' respectively (as is the case on Solaris.
171
- #
172
- # Additionally, the path to iso3166.tab can be overridden using the
173
- # alternate_iso3166_tab_path parameter.
174
- #
175
- # InvalidZoneinfoDirectory will be raised if the iso3166.tab and
176
- # zone1970.tab or zone.tab files cannot be found using the zoneinfo_dir and
177
- # alternate_iso3166_tab_path parameters.
178
- #
179
- # If zoneinfo_dir is not specified or nil, the paths referenced in
180
- # search_path are searched in order to find a valid zoneinfo directory
181
- # (one that contains zone1970.tab or zone.tab and iso3166.tab files as
182
- # above).
183
- #
184
- # The paths referenced in alternate_iso3166_tab_search_path are also
185
- # searched to find an iso3166.tab file if one of the searched zoneinfo
186
- # directories doesn't contain an iso3166.tab file.
187
- #
188
- # If no valid directory can be found by searching, ZoneinfoDirectoryNotFound
189
- # will be raised.
190
- def initialize(zoneinfo_dir = nil, alternate_iso3166_tab_path = nil)
191
- if zoneinfo_dir
192
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(zoneinfo_dir, alternate_iso3166_tab_path)
193
-
194
- unless iso3166_tab_path && zone_tab_path
195
- raise InvalidZoneinfoDirectory, "#{zoneinfo_dir} is not a directory or doesn't contain a iso3166.tab file and a zone1970.tab or zone.tab file."
196
- end
197
-
198
- @zoneinfo_dir = zoneinfo_dir
199
- else
200
- @zoneinfo_dir, iso3166_tab_path, zone_tab_path = find_zoneinfo_dir
201
-
202
- unless @zoneinfo_dir && iso3166_tab_path && zone_tab_path
203
- raise ZoneinfoDirectoryNotFound, "None of the paths included in TZInfo::ZoneinfoDataSource.search_path are valid zoneinfo directories."
204
- end
205
- end
206
-
207
- @zoneinfo_dir = File.expand_path(@zoneinfo_dir).freeze
208
- @timezone_index = load_timezone_index.freeze
209
- @country_index = load_country_index(iso3166_tab_path, zone_tab_path).freeze
210
- @posix_tz_parser = PosixTimeZoneParser.new
211
- end
212
-
213
- # Returns a TimezoneInfo instance for a given identifier.
214
- # Raises InvalidTimezoneIdentifier if the timezone is not found or the
215
- # identifier is invalid.
216
- def load_timezone_info(identifier)
217
- begin
218
- if @timezone_index.include?(identifier)
219
- path = File.join(@zoneinfo_dir, identifier)
220
-
221
- # Untaint path rather than identifier. We don't want to modify
222
- # identifier. identifier may also be frozen and therefore cannot be
223
- # untainted.
224
- RubyCoreSupport.untaint(path)
225
-
226
- begin
227
- ZoneinfoTimezoneInfo.new(identifier, path, @posix_tz_parser)
228
- rescue InvalidZoneinfoFile => e
229
- raise InvalidTimezoneIdentifier, e.message
230
- end
231
- else
232
- raise InvalidTimezoneIdentifier, 'Invalid identifier'
233
- end
234
- rescue Errno::ENOENT, Errno::ENAMETOOLONG, Errno::ENOTDIR
235
- raise InvalidTimezoneIdentifier, 'Invalid identifier'
236
- rescue Errno::EACCES => e
237
- raise InvalidTimezoneIdentifier, e.message
238
- end
239
- end
240
-
241
- # Returns an array of all the available timezone identifiers.
242
- def timezone_identifiers
243
- @timezone_index
244
- end
245
-
246
- # Returns an array of all the available timezone identifiers for
247
- # data timezones (i.e. those that actually contain definitions).
248
- #
249
- # For ZoneinfoDataSource, this will always be identical to
250
- # timezone_identifers.
251
- def data_timezone_identifiers
252
- @timezone_index
253
- end
254
-
255
- # Returns an array of all the available timezone identifiers that
256
- # are links to other timezones.
257
- #
258
- # For ZoneinfoDataSource, this will always be an empty array.
259
- def linked_timezone_identifiers
260
- [].freeze
261
- end
262
-
263
- # Returns a CountryInfo instance for the given ISO 3166-1 alpha-2
264
- # country code. Raises InvalidCountryCode if the country could not be found
265
- # or the code is invalid.
266
- def load_country_info(code)
267
- info = @country_index[code]
268
- raise InvalidCountryCode, 'Invalid country code' unless info
269
- info
270
- end
271
-
272
- # Returns an array of all the available ISO 3166-1 alpha-2
273
- # country codes.
274
- def country_codes
275
- @country_index.keys.freeze
276
- end
277
-
278
- # Returns the name and information about this DataSource.
279
- def to_s
280
- "Zoneinfo DataSource: #{@zoneinfo_dir}"
281
- end
282
-
283
- # Returns internal object state as a programmer-readable string.
284
- def inspect
285
- "#<#{self.class}: #{@zoneinfo_dir}>"
286
- end
287
-
288
- private
289
-
290
- # Processes a path for use as the search_path or
291
- # alternate_iso3166_tab_search_path.
292
- def self.process_search_path(path, default)
293
- if path
294
- if path.kind_of?(String)
295
- path.split(File::PATH_SEPARATOR)
296
- else
297
- path.collect {|p| p.to_s}
298
- end
299
- else
300
- default.dup
301
- end
302
- end
303
-
304
- # Validates a zoneinfo directory and returns the paths to the iso3166.tab
305
- # and zone1970.tab or zone.tab files if valid. If the directory is not
306
- # valid, returns nil.
307
- #
308
- # The path to the iso3166.tab file may be overriden by passing in a path.
309
- # This is treated as either absolute or relative to the current working
310
- # directory.
311
- def validate_zoneinfo_dir(path, iso3166_tab_path = nil)
312
- if File.directory?(path)
313
- if iso3166_tab_path
314
- return nil unless File.file?(iso3166_tab_path)
315
- else
316
- iso3166_tab_path = resolve_tab_path(path, ['iso3166.tab'], 'country.tab')
317
- return nil unless iso3166_tab_path
318
- end
319
-
320
- zone_tab_path = resolve_tab_path(path, ['zone1970.tab', 'zone.tab'], 'zone_sun.tab')
321
- return nil unless zone_tab_path
322
-
323
- [iso3166_tab_path, zone_tab_path]
324
- else
325
- nil
326
- end
327
- end
328
-
329
- # Attempts to resolve the path to a tab file given its standard names and
330
- # tab sub-directory name (as used on Solaris).
331
- def resolve_tab_path(zoneinfo_path, standard_names, tab_name)
332
- standard_names.each do |standard_name|
333
- path = File.join(zoneinfo_path, standard_name)
334
- return path if File.file?(path)
335
- end
336
-
337
- path = File.join(zoneinfo_path, 'tab', tab_name)
338
- return path if File.file?(path)
339
-
340
- nil
341
- end
342
-
343
- # Finds a zoneinfo directory using search_path and
344
- # alternate_iso3166_tab_search_path. Returns the paths to the directory,
345
- # the iso3166.tab file and the zone.tab file or nil if not found.
346
- def find_zoneinfo_dir
347
- alternate_iso3166_tab_path = self.class.alternate_iso3166_tab_search_path.detect do |path|
348
- File.file?(path)
349
- end
350
-
351
- self.class.search_path.each do |path|
352
- # Try without the alternate_iso3166_tab_path first.
353
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path)
354
- return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
355
-
356
- if alternate_iso3166_tab_path
357
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path, alternate_iso3166_tab_path)
358
- return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
359
- end
360
- end
361
-
362
- # Not found.
363
- nil
364
- end
365
-
366
- # Scans @zoneinfo_dir and returns an Array of available timezone
367
- # identifiers.
368
- def load_timezone_index
369
- index = []
370
-
371
- enum_timezones(nil, EXCLUDED_FILENAMES) do |identifier|
372
- index << identifier
373
- end
374
-
375
- index.sort
376
- end
377
-
378
- # Recursively scans a directory of timezones, calling the passed in block
379
- # for each identifier found.
380
- def enum_timezones(dir, exclude = [], &block)
381
- Dir.foreach(dir ? File.join(@zoneinfo_dir, dir) : @zoneinfo_dir) do |entry|
382
- unless entry =~ /\./ || exclude.include?(entry)
383
- RubyCoreSupport.untaint(entry)
384
- path = dir ? File.join(dir, entry) : entry
385
- full_path = File.join(@zoneinfo_dir, path)
386
-
387
- if File.directory?(full_path)
388
- enum_timezones(path, [], &block)
389
- elsif File.file?(full_path)
390
- yield path
391
- end
392
- end
393
- end
394
- end
395
-
396
- # Uses the iso3166.tab and zone1970.tab or zone.tab files to build an index
397
- # of the available countries and their timezones.
398
- def load_country_index(iso3166_tab_path, zone_tab_path)
399
-
400
- # Handle standard 3 to 4 column zone.tab files as well as the 4 to 5
401
- # column format used by Solaris.
402
- #
403
- # On Solaris, an extra column before the comment gives an optional
404
- # linked/alternate timezone identifier (or '-' if not set).
405
- #
406
- # Additionally, there is a section at the end of the file for timezones
407
- # covering regions. These are given lower-case "country" codes. The timezone
408
- # identifier column refers to a continent instead of an identifier. These
409
- # lines will be ignored by TZInfo.
410
- #
411
- # Since the last column is optional in both formats, testing for the
412
- # Solaris format is done in two passes. The first pass identifies if there
413
- # are any lines using 5 columns.
414
-
415
-
416
- # The first column is allowed to be a comma separated list of country
417
- # codes, as used in zone1970.tab (introduced in tzdata 2014f).
418
- #
419
- # The first country code in the comma-separated list is the country that
420
- # contains the city the zone identifer is based on. The first country
421
- # code on each line is considered to be primary with the others
422
- # secondary.
423
- #
424
- # The zones for each country are ordered primary first, then secondary.
425
- # Within the primary and secondary groups, the zones are ordered by their
426
- # order in the file.
427
-
428
- file_is_5_column = false
429
- zone_tab = []
430
-
431
- RubyCoreSupport.open_file(zone_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
432
- file.each_line do |line|
433
- line.chomp!
434
-
435
- 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/
436
- codes = $1
437
-
438
- if $2
439
- latitude = dms_to_rational($2, $3, $4)
440
- longitude = dms_to_rational($5, $6, $7)
441
- else
442
- latitude = dms_to_rational($8, $9, $10, $11)
443
- longitude = dms_to_rational($12, $13, $14, $15)
444
- end
445
-
446
- zone_identifier = $16
447
- column4 = $17
448
- column5 = $18
449
-
450
- file_is_5_column = true if column5
451
-
452
- zone_tab << [codes.split(','.freeze), zone_identifier, latitude, longitude, column4, column5]
453
- end
454
- end
455
- end
456
-
457
- primary_zones = {}
458
- secondary_zones = {}
459
-
460
- zone_tab.each do |codes, zone_identifier, latitude, longitude, column4, column5|
461
- description = file_is_5_column ? column5 : column4
462
- country_timezone = CountryTimezone.new(zone_identifier, latitude, longitude, description)
463
-
464
- # codes will always have at least one element
465
-
466
- (primary_zones[codes.first] ||= []) << country_timezone
467
-
468
- codes[1..-1].each do |code|
469
- (secondary_zones[code] ||= []) << country_timezone
470
- end
471
- end
472
-
473
- countries = {}
474
-
475
- RubyCoreSupport.open_file(iso3166_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
476
- file.each_line do |line|
477
- line.chomp!
478
-
479
- # Handle both the two column alpha-2 and name format used in the tz
480
- # database as well as the 4 column alpha-2, alpha-3, numeric-3 and
481
- # name format used by FreeBSD and OpenBSD.
482
-
483
- if line =~ /\A([A-Z]{2})(?:\t[A-Z]{3}\t[0-9]{3})?\t(.+)\z/
484
- code = $1
485
- name = $2
486
- zones = (primary_zones[code] || []) + (secondary_zones[code] || [])
487
-
488
- countries[code] = ZoneinfoCountryInfo.new(code, name, zones)
489
- end
490
- end
491
- end
492
-
493
- countries
494
- end
495
-
496
- # Converts degrees, minutes and seconds to a Rational.
497
- def dms_to_rational(sign, degrees, minutes, seconds = nil)
498
- result = degrees.to_i + Rational(minutes.to_i, 60)
499
- result += Rational(seconds.to_i, 3600) if seconds
500
- result = -result if sign == '-'.freeze
501
- result
502
- end
503
- end
504
- end