tzinfo 1.2.1 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +3 -0
  4. data/CHANGES.md +693 -315
  5. data/LICENSE +13 -13
  6. data/README.md +368 -113
  7. data/lib/tzinfo/annual_rules.rb +71 -0
  8. data/lib/tzinfo/country.rb +148 -120
  9. data/lib/tzinfo/country_timezone.rb +71 -101
  10. data/lib/tzinfo/data_source.rb +400 -144
  11. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  12. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  13. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  14. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  15. data/lib/tzinfo/data_sources/posix_time_zone_parser.rb +181 -0
  16. data/lib/tzinfo/data_sources/ruby_data_source.rb +145 -0
  17. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  18. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  19. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +596 -0
  20. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +486 -0
  21. data/lib/tzinfo/data_sources.rb +8 -0
  22. data/lib/tzinfo/data_timezone.rb +33 -47
  23. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  24. data/lib/tzinfo/format1/country_definer.rb +17 -0
  25. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  26. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  27. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  28. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  29. data/lib/tzinfo/format1.rb +10 -0
  30. data/lib/tzinfo/format2/country_definer.rb +68 -0
  31. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  32. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  33. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  34. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  35. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  36. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  37. data/lib/tzinfo/format2.rb +10 -0
  38. data/lib/tzinfo/info_timezone.rb +26 -21
  39. data/lib/tzinfo/linked_timezone.rb +33 -52
  40. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  41. data/lib/tzinfo/string_deduper.rb +118 -0
  42. data/lib/tzinfo/time_with_offset.rb +154 -0
  43. data/lib/tzinfo/timestamp.rb +552 -0
  44. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  45. data/lib/tzinfo/timezone.rb +997 -473
  46. data/lib/tzinfo/timezone_offset.rb +91 -54
  47. data/lib/tzinfo/timezone_period.rb +163 -188
  48. data/lib/tzinfo/timezone_proxy.rb +75 -49
  49. data/lib/tzinfo/timezone_transition.rb +77 -99
  50. data/lib/tzinfo/transition_rule.rb +455 -0
  51. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  52. data/lib/tzinfo/untaint_ext.rb +18 -0
  53. data/lib/tzinfo/version.rb +7 -0
  54. data/lib/tzinfo/with_offset.rb +61 -0
  55. data/lib/tzinfo.rb +74 -29
  56. data.tar.gz.sig +0 -0
  57. metadata +72 -120
  58. metadata.gz.sig +0 -0
  59. data/Rakefile +0 -104
  60. data/lib/tzinfo/country_index_definition.rb +0 -31
  61. data/lib/tzinfo/country_info.rb +0 -42
  62. data/lib/tzinfo/data_timezone_info.rb +0 -55
  63. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  64. data/lib/tzinfo/offset_rationals.rb +0 -77
  65. data/lib/tzinfo/ruby_core_support.rb +0 -146
  66. data/lib/tzinfo/ruby_country_info.rb +0 -70
  67. data/lib/tzinfo/ruby_data_source.rb +0 -136
  68. data/lib/tzinfo/time_or_datetime.rb +0 -333
  69. data/lib/tzinfo/timezone_definition.rb +0 -36
  70. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  71. data/lib/tzinfo/timezone_info.rb +0 -30
  72. data/lib/tzinfo/timezone_transition_definition.rb +0 -101
  73. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  74. data/lib/tzinfo/zoneinfo_country_info.rb +0 -35
  75. data/lib/tzinfo/zoneinfo_data_source.rb +0 -460
  76. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -245
  77. data/test/tc_country.rb +0 -236
  78. data/test/tc_country_index_definition.rb +0 -69
  79. data/test/tc_country_info.rb +0 -16
  80. data/test/tc_country_timezone.rb +0 -161
  81. data/test/tc_data_source.rb +0 -192
  82. data/test/tc_data_timezone.rb +0 -99
  83. data/test/tc_data_timezone_info.rb +0 -18
  84. data/test/tc_info_timezone.rb +0 -34
  85. data/test/tc_linked_timezone.rb +0 -155
  86. data/test/tc_linked_timezone_info.rb +0 -23
  87. data/test/tc_offset_rationals.rb +0 -23
  88. data/test/tc_ruby_core_support.rb +0 -168
  89. data/test/tc_ruby_country_info.rb +0 -80
  90. data/test/tc_ruby_data_source.rb +0 -143
  91. data/test/tc_time_or_datetime.rb +0 -639
  92. data/test/tc_timezone.rb +0 -1334
  93. data/test/tc_timezone_definition.rb +0 -113
  94. data/test/tc_timezone_index_definition.rb +0 -73
  95. data/test/tc_timezone_info.rb +0 -11
  96. data/test/tc_timezone_london.rb +0 -143
  97. data/test/tc_timezone_melbourne.rb +0 -142
  98. data/test/tc_timezone_new_york.rb +0 -142
  99. data/test/tc_timezone_offset.rb +0 -126
  100. data/test/tc_timezone_period.rb +0 -548
  101. data/test/tc_timezone_proxy.rb +0 -113
  102. data/test/tc_timezone_transition.rb +0 -352
  103. data/test/tc_timezone_transition_definition.rb +0 -284
  104. data/test/tc_timezone_utc.rb +0 -27
  105. data/test/tc_transition_data_timezone_info.rb +0 -423
  106. data/test/tc_zoneinfo_country_info.rb +0 -64
  107. data/test/tc_zoneinfo_data_source.rb +0 -985
  108. data/test/tc_zoneinfo_timezone_info.rb +0 -814
  109. data/test/test_utils.rb +0 -132
  110. data/test/ts_all.rb +0 -7
  111. data/test/ts_all_ruby.rb +0 -5
  112. data/test/ts_all_zoneinfo.rb +0 -7
  113. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  114. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -315
  115. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -218
  116. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  117. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  118. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  119. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  120. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -261
  121. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -186
  122. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -321
  123. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -265
  124. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -220
  125. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  126. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -927
  127. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -594
  128. data/test/tzinfo-data/tzinfo/data/version.rb +0 -14
  129. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  130. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  131. data/test/zoneinfo/America/New_York +0 -0
  132. data/test/zoneinfo/Australia/Melbourne +0 -0
  133. data/test/zoneinfo/EST +0 -0
  134. data/test/zoneinfo/Etc/UTC +0 -0
  135. data/test/zoneinfo/Europe/Amsterdam +0 -0
  136. data/test/zoneinfo/Europe/Andorra +0 -0
  137. data/test/zoneinfo/Europe/London +0 -0
  138. data/test/zoneinfo/Europe/Paris +0 -0
  139. data/test/zoneinfo/Europe/Prague +0 -0
  140. data/test/zoneinfo/Factory +0 -0
  141. data/test/zoneinfo/iso3166.tab +0 -275
  142. data/test/zoneinfo/posix/Europe/London +0 -0
  143. data/test/zoneinfo/posixrules +0 -0
  144. data/test/zoneinfo/right/Europe/London +0 -0
  145. data/test/zoneinfo/zone.tab +0 -452
  146. data/tzinfo.gemspec +0 -21
@@ -1,460 +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 index files named iso3166.tab and zone.tab as well as other
13
- # timezone files.
14
- class ZoneinfoDirectoryNotFound < StandardError
15
- end
16
-
17
- # A DataSource that loads data from a 'zoneinfo' directory containing
18
- # compiled "TZif" version 2 (or earlier) files in addition to zones.tab
19
- # and iso3166.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 iso3166.tab and zone.tab files. These may
145
- # either be included in the root of the directory or in a 'tab'
146
- # sub-directory named 'country.tab' and 'zone_sun.tab' respectively (as is
147
- # 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 zone.tab
153
- # files cannot be found using the zoneinfo_dir and alternate_iso3166_tab_path
154
- # 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 zone.tab and iso3166.tab files as above).
159
- #
160
- # The paths referenced in alternate_iso3166_tab_search_path are also
161
- # searched to find an iso3166.tab file if one of the searched zoneinfo
162
- # directories doesn't contain an iso3166.tab file.
163
- #
164
- # If no valid directory can be found by searching, ZoneinfoDirectoryNotFound
165
- # will be raised.
166
- def initialize(zoneinfo_dir = nil, alternate_iso3166_tab_path = nil)
167
- if zoneinfo_dir
168
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(zoneinfo_dir, alternate_iso3166_tab_path)
169
-
170
- unless iso3166_tab_path && zone_tab_path
171
- raise InvalidZoneinfoDirectory, "#{zoneinfo_dir} is not a directory or doesn't contain iso3166.tab and zone.tab files."
172
- end
173
-
174
- @zoneinfo_dir = zoneinfo_dir
175
- else
176
- @zoneinfo_dir, iso3166_tab_path, zone_tab_path = find_zoneinfo_dir
177
-
178
- unless @zoneinfo_dir && iso3166_tab_path && zone_tab_path
179
- raise ZoneinfoDirectoryNotFound, "None of the paths included in TZInfo::ZoneinfoDataSource.search_path are valid zoneinfo directories."
180
- end
181
- end
182
-
183
- @zoneinfo_dir = File.expand_path(@zoneinfo_dir).freeze
184
- @timezone_index = load_timezone_index.freeze
185
- @country_index = load_country_index(iso3166_tab_path, zone_tab_path).freeze
186
- end
187
-
188
- # Returns a TimezoneInfo instance for a given identifier.
189
- # Raises InvalidTimezoneIdentifier if the timezone is not found or the
190
- # identifier is invalid.
191
- def load_timezone_info(identifier)
192
- begin
193
- if @timezone_index.include?(identifier)
194
- path = File.join(@zoneinfo_dir, identifier)
195
-
196
- # Untaint path rather than identifier. We don't want to modify
197
- # identifier. identifier may also be frozen and therefore cannot be
198
- # untainted.
199
- path.untaint
200
-
201
- begin
202
- ZoneinfoTimezoneInfo.new(identifier, path)
203
- rescue InvalidZoneinfoFile => e
204
- raise InvalidTimezoneIdentifier, e.message
205
- end
206
- else
207
- raise InvalidTimezoneIdentifier, 'Invalid identifier'
208
- end
209
- rescue Errno::ENOENT, Errno::ENAMETOOLONG, Errno::ENOTDIR
210
- raise InvalidTimezoneIdentifier, 'Invalid identifier'
211
- rescue Errno::EACCES => e
212
- raise InvalidTimezoneIdentifier, e.message
213
- end
214
- end
215
-
216
- # Returns an array of all the available timezone identifiers.
217
- def timezone_identifiers
218
- @timezone_index
219
- end
220
-
221
- # Returns an array of all the available timezone identifiers for
222
- # data timezones (i.e. those that actually contain definitions).
223
- #
224
- # For ZoneinfoDataSource, this will always be identical to
225
- # timezone_identifers.
226
- def data_timezone_identifiers
227
- @timezone_index
228
- end
229
-
230
- # Returns an array of all the available timezone identifiers that
231
- # are links to other timezones.
232
- #
233
- # For ZoneinfoDataSource, this will always be an empty array.
234
- def linked_timezone_identifiers
235
- [].freeze
236
- end
237
-
238
- # Returns a CountryInfo instance for the given ISO 3166-1 alpha-2
239
- # country code. Raises InvalidCountryCode if the country could not be found
240
- # or the code is invalid.
241
- def load_country_info(code)
242
- info = @country_index[code]
243
- raise InvalidCountryCode, 'Invalid country code' unless info
244
- info
245
- end
246
-
247
- # Returns an array of all the available ISO 3166-1 alpha-2
248
- # country codes.
249
- def country_codes
250
- @country_index.keys.freeze
251
- end
252
-
253
- # Returns the name and information about this DataSource.
254
- def to_s
255
- "Zoneinfo DataSource: #{@zoneinfo_dir}"
256
- end
257
-
258
- # Returns internal object state as a programmer-readable string.
259
- def inspect
260
- "#<#{self.class}: #{@zoneinfo_dir}>"
261
- end
262
-
263
- private
264
-
265
- # Processes a path for use as the search_path or
266
- # alternate_iso3166_tab_search_path.
267
- def self.process_search_path(path, default)
268
- if path
269
- if path.kind_of?(String)
270
- path.split(File::PATH_SEPARATOR)
271
- else
272
- path.collect {|p| p.to_s}
273
- end
274
- else
275
- default.dup
276
- end
277
- end
278
-
279
- # Validates a zoneinfo directory and returns the paths to the iso3166.tab
280
- # and zone.tab files if valid. If the directory is not valid, returns nil.
281
- #
282
- # The path to the iso3166.tab file may be overriden by passing in a path.
283
- # This is treated as either absolute or relative to the current working
284
- # directory.
285
- def validate_zoneinfo_dir(path, iso3166_tab_path = nil)
286
- if File.directory?(path)
287
- if iso3166_tab_path
288
- return nil unless File.file?(iso3166_tab_path)
289
- else
290
- iso3166_tab_path = resolve_tab_path(path, 'iso3166.tab', 'country.tab')
291
- return nil unless iso3166_tab_path
292
- end
293
-
294
- zone_tab_path = resolve_tab_path(path, 'zone.tab', 'zone_sun.tab')
295
- return nil unless zone_tab_path
296
-
297
- [iso3166_tab_path, zone_tab_path]
298
- else
299
- nil
300
- end
301
- end
302
-
303
- # Attempts to resolve the path to a tab file given its standard name and
304
- # tab sub-directory name (as used on Solaris).
305
- def resolve_tab_path(zoneinfo_path, standard_name, tab_name)
306
- path = File.join(zoneinfo_path, standard_name)
307
- return path if File.file?(path)
308
-
309
- path = File.join(zoneinfo_path, 'tab', tab_name)
310
- return path if File.file?(path)
311
-
312
- nil
313
- end
314
-
315
- # Finds a zoneinfo directory using search_path and
316
- # alternate_iso3166_tab_search_path. Returns the paths to the directory,
317
- # the iso3166.tab file and the zone.tab file or nil if not found.
318
- def find_zoneinfo_dir
319
- alternate_iso3166_tab_path = self.class.alternate_iso3166_tab_search_path.detect do |path|
320
- File.file?(path)
321
- end
322
-
323
- self.class.search_path.each do |path|
324
- # Try without the alternate_iso3166_tab_path first.
325
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path)
326
- return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
327
-
328
- if alternate_iso3166_tab_path
329
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path, alternate_iso3166_tab_path)
330
- return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
331
- end
332
- end
333
-
334
- # Not found.
335
- nil
336
- end
337
-
338
- # Scans @zoneinfo_dir and returns an Array of available timezone
339
- # identifiers.
340
- def load_timezone_index
341
- index = []
342
-
343
- # Ignoring particular files:
344
- # +VERSION is included on Mac OS X.
345
- # localtime current local timezone (may be a link).
346
- # posix, posixrules and right are directories containing other versions of the zoneinfo files.
347
- # src is a directory containing the tzdata source included on Solaris.
348
- # Factory is the compiled in default timezone.
349
-
350
- enum_timezones(nil, ['+VERSION', 'localtime', 'posix', 'posixrules', 'right', 'src', 'Factory']) do |identifier|
351
- index << identifier
352
- end
353
-
354
- index.sort
355
- end
356
-
357
- # Recursively scans a directory of timezones, calling the passed in block
358
- # for each identifier found.
359
- def enum_timezones(dir, exclude = [], &block)
360
- Dir.foreach(dir ? File.join(@zoneinfo_dir, dir) : @zoneinfo_dir) do |entry|
361
- unless entry =~ /\./ || exclude.include?(entry)
362
- entry.untaint
363
- path = dir ? File.join(dir, entry) : entry
364
- full_path = File.join(@zoneinfo_dir, path)
365
-
366
- if File.directory?(full_path)
367
- enum_timezones(path, [], &block)
368
- elsif File.file?(full_path)
369
- yield path
370
- end
371
- end
372
- end
373
- end
374
-
375
- # Uses the iso3166.tab and zone.tab files to build an index of the
376
- # available countries and their timezones.
377
- def load_country_index(iso3166_tab_path, zone_tab_path)
378
-
379
- # Handle standard 3 to 4 column zone.tab files as well as the 4 to 5
380
- # column format used by Solaris.
381
- #
382
- # On Solaris, an extra column before the comment gives an optional
383
- # linked/alternate timezone identifier (or '-' if not set).
384
- #
385
- # Additionally, there is a section at the end of the file for timezones
386
- # covering regions. These are given lower-case "country" codes. The timezone
387
- # identifier column refers to a continent instead of an identifier. These
388
- # lines will be ignored by TZInfo.
389
- #
390
- # Since the last column is optional in both formats, testing for the
391
- # Solaris format is done in two passes. The first pass identifies if there
392
- # are any lines using 5 columns.
393
-
394
- file_is_5_column = false
395
- zone_tab = []
396
-
397
- RubyCoreSupport.open_file(zone_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
398
- file.each_line do |line|
399
- line.chomp!
400
-
401
- if line =~ /\A([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/
402
- code = $1
403
-
404
- if $2
405
- latitude = dms_to_rational($2, $3, $4)
406
- longitude = dms_to_rational($5, $6, $7)
407
- else
408
- latitude = dms_to_rational($8, $9, $10, $11)
409
- longitude = dms_to_rational($12, $13, $14, $15)
410
- end
411
-
412
- zone_identifier = $16
413
- column4 = $17
414
- column5 = $18
415
-
416
- file_is_5_column = true if column5
417
-
418
- zone_tab << [code, zone_identifier, latitude, longitude, column4, column5]
419
- end
420
- end
421
- end
422
-
423
- zones = {}
424
-
425
- zone_tab.each do |code, zone_identifier, latitude, longitude, column4, column5|
426
- description = file_is_5_column ? column5 : column4
427
-
428
- (zones[code] ||= []) << CountryTimezone.new(zone_identifier, latitude, longitude, description)
429
- end
430
-
431
- countries = {}
432
-
433
- RubyCoreSupport.open_file(iso3166_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
434
- file.each_line do |line|
435
- line.chomp!
436
-
437
- # Handle both the two column alpha-2 and name format used in the tz
438
- # database as well as the 4 column alpha-2, alpha-3, numeric-3 and
439
- # name format used by FreeBSD and OpenBSD.
440
-
441
- if line =~ /\A([A-Z]{2})(?:\t[A-Z]{3}\t[0-9]{3})?\t(.+)\z/
442
- code = $1
443
- name = $2
444
- countries[code] = ZoneinfoCountryInfo.new(code, name, zones[code] || [])
445
- end
446
- end
447
- end
448
-
449
- countries
450
- end
451
-
452
- # Converts degrees, minutes and seconds to a Rational.
453
- def dms_to_rational(sign, degrees, minutes, seconds = nil)
454
- result = degrees.to_i + Rational(minutes.to_i, 60)
455
- result += Rational(seconds.to_i, 3600) if seconds
456
- result = -result if sign == '-'
457
- result
458
- end
459
- end
460
- end
@@ -1,245 +0,0 @@
1
- module TZInfo
2
- # An InvalidZoneinfoFile exception is raised if an attempt is made to load an
3
- # invalid zoneinfo file.
4
- class InvalidZoneinfoFile < StandardError
5
- end
6
-
7
- # Represents a timezone defined by a compiled zoneinfo TZif (\0, 2 or 3) file.
8
- #
9
- # @private
10
- class ZoneinfoTimezoneInfo < TransitionDataTimezoneInfo #:nodoc:
11
-
12
- # Minimum supported timestamp (inclusive).
13
- #
14
- # Time.utc(1700, 1, 1).to_i
15
- MIN_TIMESTAMP = -8520336000
16
-
17
- # Maximum supported timestamp (exclusive).
18
- #
19
- # Time.utc(2500, 1, 1).to_i
20
- MAX_TIMESTAMP = 16725225600
21
-
22
- # Constructs the new ZoneinfoTimezoneInfo with an identifier and path
23
- # to the file.
24
- def initialize(identifier, file_path)
25
- super(identifier)
26
-
27
- File.open(file_path, 'rb') do |file|
28
- parse(file)
29
- end
30
- end
31
-
32
- private
33
- # Unpack will return unsigned 32-bit integers. Translate to
34
- # signed 32-bit.
35
- def make_signed_int32(long)
36
- long >= 0x80000000 ? long - 0x100000000 : long
37
- end
38
-
39
- # Unpack will return a 64-bit integer as two unsigned 32-bit integers
40
- # (most significant first). Translate to signed 64-bit
41
- def make_signed_int64(high, low)
42
- unsigned = (high << 32) | low
43
- unsigned >= 0x8000000000000000 ? unsigned - 0x10000000000000000 : unsigned
44
- end
45
-
46
- # Read bytes from file and check that the correct number of bytes could
47
- # be read. Raises InvalidZoneinfoFile if the number of bytes didn't match
48
- # the number requested.
49
- def check_read(file, bytes)
50
- result = file.read(bytes)
51
-
52
- unless result && result.length == bytes
53
- raise InvalidZoneinfoFile, "Expected #{bytes} bytes reading '#{file.path}', but got #{result ? result.length : 0} bytes"
54
- end
55
-
56
- result
57
- end
58
-
59
- # Zoneinfo doesn't include the offset from standard time (std_offset).
60
- # Derive the missing offsets by looking at changes in the total UTC
61
- # offset.
62
- #
63
- # This will be run through forwards and then backwards by the parse
64
- # method.
65
- def derive_offsets(transitions, offsets)
66
- previous_offset = nil
67
-
68
- transitions.each do |t|
69
- offset = offsets[t[:offset]]
70
-
71
- if !offset[:std_offset] && offset[:is_dst] && previous_offset
72
- difference = offset[:utc_total_offset] - previous_offset[:utc_total_offset]
73
-
74
- if previous_offset[:is_dst]
75
- if previous_offset[:std_offset]
76
- std_offset = previous_offset[:std_offset] + difference
77
- else
78
- std_offset = nil
79
- end
80
- else
81
- std_offset = difference
82
- end
83
-
84
- if std_offset && std_offset > 0
85
- offset[:std_offset] = std_offset
86
- offset[:utc_offset] = offset[:utc_total_offset] - std_offset
87
- end
88
- end
89
-
90
- previous_offset = offset
91
- end
92
- end
93
-
94
- # Parses a zoneinfo file and intializes the DataTimezoneInfo structures.
95
- def parse(file)
96
- magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
97
- check_read(file, 44).unpack('a4 a x15 NNNNNN')
98
-
99
- if magic != 'TZif'
100
- raise InvalidZoneinfoFile, "The file '#{file.path}' does not start with the expected header."
101
- end
102
-
103
- if (version == '2' || version == '3') && RubyCoreSupport.time_supports_64bit
104
- # Skip the first 32-bit section and read the header of the second 64-bit section
105
- file.seek(timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisgmtcnt + ttisstdcnt, IO::SEEK_CUR)
106
-
107
- prev_version = version
108
-
109
- magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
110
- check_read(file, 44).unpack('a4 a x15 NNNNNN')
111
-
112
- unless magic == 'TZif' && (version == prev_version)
113
- raise InvalidZoneinfoFile, "The file '#{file.path}' contains an invalid 64-bit section header."
114
- end
115
-
116
- using_64bit = true
117
- elsif version != '3' && version != '2' && version != "\0"
118
- raise InvalidZoneinfoFile, "The file '#{file.path}' contains a version of the zoneinfo format that is not currently supported."
119
- else
120
- using_64bit = false
121
- end
122
-
123
- unless leapcnt == 0
124
- raise InvalidZoneinfoFile, "The zoneinfo file '#{file.path}' contains leap second data. TZInfo requires zoneinfo files that omit leap seconds."
125
- end
126
-
127
- transitions = []
128
-
129
- if using_64bit
130
- (0...timecnt).each do |i|
131
- high, low = check_read(file, 8).unpack('NN')
132
- transition_time = make_signed_int64(high, low)
133
- transitions << {:at => transition_time}
134
- end
135
- else
136
- (0...timecnt).each do |i|
137
- transition_time = make_signed_int32(check_read(file, 4).unpack('N')[0])
138
- transitions << {:at => transition_time}
139
- end
140
- end
141
-
142
- (0...timecnt).each do |i|
143
- localtime_type = check_read(file, 1).unpack('C')[0]
144
- transitions[i][:offset] = localtime_type
145
- end
146
-
147
- offsets = []
148
-
149
- (0...typecnt).each do |i|
150
- gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC')
151
- gmtoff = make_signed_int32(gmtoff)
152
- isdst = isdst == 1
153
- offset = {:utc_total_offset => gmtoff, :is_dst => isdst, :abbr_index => abbrind}
154
-
155
- unless isdst
156
- offset[:utc_offset] = gmtoff
157
- offset[:std_offset] = 0
158
- end
159
-
160
- offsets << offset
161
- end
162
-
163
- abbrev = check_read(file, charcnt)
164
-
165
- offsets.each do |o|
166
- abbrev_start = o[:abbr_index]
167
- raise InvalidZoneinfoFile, "Abbreviation index is out of range in file '#{file.path}'" unless abbrev_start < abbrev.length
168
-
169
- abbrev_end = abbrev.index("\0", abbrev_start)
170
- raise InvalidZoneinfoFile, "Missing abbreviation null terminator in file '#{file.path}'" unless abbrev_end
171
-
172
- o[:abbr] = RubyCoreSupport.force_encoding(abbrev[abbrev_start...abbrev_end], 'UTF-8')
173
- end
174
-
175
- transitions.each do |t|
176
- if t[:offset] < 0 || t[:offset] >= offsets.length
177
- raise InvalidZoneinfoFile, "Invalid offset referenced by transition in file '#{file.path}'."
178
- end
179
- end
180
-
181
- # Derive the offsets from standard time (std_offset).
182
- derive_offsets(transitions, offsets)
183
- derive_offsets(transitions.reverse, offsets)
184
-
185
- # Assign anything left a standard offset of one hour
186
- offsets.each do |o|
187
- if !o[:std_offset] && o[:is_dst]
188
- o[:std_offset] = 3600
189
- o[:utc_offset] = o[:utc_total_offset] - 3600
190
- end
191
- end
192
-
193
- # Find the first non-dst offset. This is used as the offset for the time
194
- # before the first transition.
195
- first = nil
196
- offsets.each_with_index do |o, i|
197
- if !o[:is_dst]
198
- first = i
199
- break
200
- end
201
- end
202
-
203
- if first
204
- offset first, offsets[first][:utc_offset], offsets[first][:std_offset], offsets[first][:abbr].untaint.to_sym
205
- end
206
-
207
- offsets.each_with_index do |o, i|
208
- offset i, o[:utc_offset], o[:std_offset], o[:abbr].untaint.to_sym unless i == first
209
- end
210
-
211
- if !using_64bit && !RubyCoreSupport.time_supports_negative
212
- # Filter out transitions that are not supported by Time on this
213
- # platform.
214
-
215
- # Move the last transition before the epoch up to the epoch. This
216
- # allows for accurate conversions for all supported timestamps on the
217
- # platform.
218
-
219
- before_epoch, after_epoch = transitions.partition {|t| t[:at] < 0}
220
-
221
- if before_epoch.length > 0 && after_epoch.length > 0 && after_epoch.first[:at] != 0
222
- last_before = before_epoch.last
223
- last_before[:at] = 0
224
- transitions = [last_before] + after_epoch
225
- else
226
- transitions = after_epoch
227
- end
228
- end
229
-
230
- # Ignore transitions that occur outside of a defined window. The
231
- # transition index cannot handle a large range of transition times.
232
- #
233
- # This is primarily intended to ignore the far in the past transition
234
- # added in zic 2014c (at timestamp -2**63 in zic 2014c and at the
235
- # approximate time of the big bang from zic 2014d).
236
- transitions.each do |t|
237
- at = t[:at]
238
- if at >= MIN_TIMESTAMP && at < MAX_TIMESTAMP
239
- time = Time.at(at).utc
240
- transition time.year, time.mon, t[:offset], at
241
- end
242
- end
243
- end
244
- end
245
- end