TimezoneParser 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,107 +13,5 @@ module TimezoneParser
13
13
  DataDir = RootDir + 'data'
14
14
  # Path to Vendor directory
15
15
  VendorDir = RootDir + 'vendor'
16
-
17
- attr_reader :Offsets
18
- attr_reader :Timezones
19
- attr_reader :Types
20
- attr_reader :Metazones
21
- def initialize
22
- @Offsets = SortedSet.new
23
- @Timezones = SortedSet.new
24
- @Types = SortedSet.new
25
- @Metazones = SortedSet.new
26
- end
27
-
28
- def processEntry(entry, toTime, fromTime, regions = [])
29
- @Timezones += entry['Timezones'] if entry['Timezones']
30
- @Offsets << entry['Offset'] if entry['Offset']
31
- @Types += entry['Types'].map(&:to_sym) if entry['Types']
32
- if entry.has_key?('Metazones')
33
- entry['Metazones'].each do |zone|
34
- @Metazones << zone
35
- @Timezones += Storage.getTimezones(zone, toTime, fromTime, regions)
36
- end
37
- end
38
- self
39
- end
40
-
41
- def findOffsets(toTime, fromTime, regions = nil, types = nil)
42
- types = @Types.to_a unless types
43
- types = [:daylight, :standard] if types.empty?
44
- @Timezones.each do |timezone|
45
- if regions and not regions.empty?
46
- timezoneRegions = Data::Storage.TimezoneCountries[timezone]
47
- next if timezoneRegions and (timezoneRegions & regions).empty?
48
- end
49
- begin
50
- tz = TZInfo::Timezone.get(timezone)
51
- rescue TZInfo::InvalidTimezoneIdentifier
52
- tz = nil
53
- end
54
- next unless tz
55
- offsets = []
56
- self.class.addOffset(offsets, tz.period_for_utc(fromTime).offset, types)
57
- tz.transitions_up_to(toTime, fromTime).each do |transition|
58
- self.class.addOffset(offsets, transition.offset, types)
59
- end
60
- @Offsets += offsets
61
- end
62
- @Offsets
63
- end
64
-
65
- # Load data entries which match specified time
66
- # @param data [Array<Hash>] array of entries
67
- # @param toTime [DateTime] entries before this date, exclusive
68
- # @param fromTime [DateTime] entries after/at this date, inclusive
69
- # @return [Array<Hash>] resulting array containing filtered entries
70
- def self.loadEntries(data, toTime, fromTime, offsets = false)
71
- result = []
72
- data.each do |entry|
73
- result << entry if (entry['From'] && entry['To'] and toTime > entry['From'] and fromTime < entry['To']) or
74
- (entry['From'] && !entry['To'] and toTime > entry['From']) or
75
- (!entry['From'] && entry['To'] and fromTime < entry['To']) or
76
- (!entry['From'] && !entry['To'])
77
- end
78
- result.each do |entry|
79
- return result if not offsets or (offsets and entry['Offset'])
80
- end
81
- data.each_index do |i|
82
- entry = data[i]
83
- nextentry = offsets ? getNextEntry(data, i) : data[i+1]
84
- result << entry if ( entry['From'] && entry['To'] and toTime > entry['From'] and fromTime < entry['To'] ) or
85
- ( entry['To'] and ( (nextentry.nil? and fromTime >= entry['To']) or
86
- (nextentry and nextentry['From'] and fromTime >= entry['To'] and toTime <= nextentry['From']) or
87
- (!entry['From'] and fromTime < entry['To']) ) ) or
88
- ( entry['From'] and ( (i.zero? and toTime <= entry['From']) or (!entry['To'] and toTime > entry['From']) ) )
89
- end
90
- result
91
- end
92
-
93
- def self.filterData(data, toTime, fromTime, type, regions)
94
- entries = []
95
- data.each do |entry|
96
- next if type and entry['Types'] and not entry['Types'].include?(type)
97
- next if not regions.empty? and entry['Countries'] and (entry['Countries'] & regions).empty?
98
- entries << entry
99
- end
100
- loadEntries(entries, toTime, fromTime, true)
101
- end
102
-
103
- protected
104
-
105
- def self.getNextEntry(data, i)
106
- j = 1
107
- begin
108
- entry = data[i+j]
109
- j += 1
110
- end until entry.nil? or (entry and entry['Offset'])
111
- entry
112
- end
113
-
114
- def self.addOffset(offsets, offset, types)
115
- offsets << offset.utc_total_offset if (offset.dst? and types.include?(:daylight)) or (not offset.dst? and types.include?(:standard))
116
- end
117
-
118
16
  end
119
17
  end
@@ -0,0 +1,242 @@
1
+ require 'yaml'
2
+ require 'sqlite3'
3
+ require_relative 'tzinfo'
4
+ require_relative '../zone_info'
5
+
6
+ module TimezoneParser
7
+ class Data
8
+ # Timezone data Exporter class
9
+ class Exporter
10
+ Database = 'timezones.db'
11
+
12
+ public
13
+
14
+ def initialize(location)
15
+ path = location + Database
16
+ File.delete(path) if File.exist?(path)
17
+ @Database = SQLite3::Database.new(path.to_s)
18
+ @DataDir = Data::DataDir
19
+ end
20
+
21
+ def exportDatabase
22
+ configure
23
+ loadSchema
24
+ loadLocales
25
+ loadTerritories
26
+ loadTimezones
27
+ loadTimezoneTerritories
28
+ loadMetazones
29
+ loadTimezoneNames
30
+ loadAbbreviations
31
+ loadRailsTimezones
32
+ loadRailsI18N
33
+ loadWindowsZones
34
+ loadWindowsZoneTimezones
35
+ loadWindowsZoneNames
36
+ finalize
37
+ end
38
+
39
+ private
40
+
41
+ @Database = nil
42
+ @LocaleIds = {}
43
+ @TerritoryIds = {}
44
+ @TimezoneIds = {}
45
+ @MetazoneIds = {}
46
+ @RailsTimezoneIds = {}
47
+ @WindowsZoneIds = {}
48
+
49
+ def configure
50
+ @Database.application_id = 'TZPR'.unpack('l>').first.to_s
51
+ @Database.user_version = TimezoneParser::VERSION.split('.').map(&:to_i).pack('CS>C').unpack('l>').first.to_s
52
+ @Database.foreign_keys = true
53
+ @Database.journal_mode = 'off'
54
+ @Database.temp_store = 'memory'
55
+ @Database.locking_mode = 'exclusive'
56
+ @Database.synchronous = 'off'
57
+ end
58
+
59
+ def finalize
60
+ @Database.execute('ANALYZE')
61
+ @Database.execute('VACUUM')
62
+ end
63
+
64
+ def normalizeLocale(locale)
65
+ locale = locale.gsub('_', '-')
66
+ case locale
67
+ when 'zh-CN'
68
+ locale = 'zh-Hans-CN'
69
+ when 'zh-TW'
70
+ locale = 'zh-Hant-TW'
71
+ when 'ha-Latn-NG'
72
+ locale = 'ha-NG'
73
+ end
74
+ locale
75
+ end
76
+
77
+ def getLocaleId(locale)
78
+ @LocaleIds[normalizeLocale(locale)]
79
+ end
80
+
81
+ def loadSchema
82
+ schema = File.read(@DataDir + 'schema.sql')
83
+ @Database.execute_batch(schema)
84
+ end
85
+
86
+ def loadLocales
87
+ @LocaleIds = {}
88
+ locales = YAML.load_file(@DataDir + 'locales.yaml')
89
+ locales.each do |locale|
90
+ locale = normalizeLocale(locale)
91
+ @Database.execute('INSERT INTO `Locales` (`Name`) VALUES (?)', locale)
92
+ @LocaleIds[locale] = @Database.last_insert_row_id
93
+ end
94
+ locales.each do |locale|
95
+ locale = normalizeLocale(locale)
96
+ if locale.include?('-')
97
+ parent = locale.split('-')[0..-2].join('-')
98
+ @Database.execute('UPDATE `Locales` SET `Parent` = ? WHERE `ID` = ?', getLocaleId(parent), getLocaleId(locale))
99
+ end
100
+ end
101
+ end
102
+
103
+ def loadTerritories
104
+ @TerritoryIds = {}
105
+ territories = YAML.load_file(@DataDir + 'territories.yaml')
106
+ (territories.to_a.flatten + ['ZZ']).sort.uniq.each do |territory|
107
+ @Database.execute('INSERT INTO `Territories` (`Territory`) VALUES (?)', territory)
108
+ @TerritoryIds[territory] = @Database.last_insert_row_id
109
+ end
110
+ territories.each do |parent, entries|
111
+ entries.each do |territory|
112
+ @Database.execute('INSERT INTO `TerritoryContainment` (`Parent`, `Territory`) VALUES (?, ?)', @TerritoryIds[parent], @TerritoryIds[territory])
113
+ end
114
+ end
115
+ end
116
+
117
+ def loadTimezones
118
+ @TimezoneIds = {}
119
+ TimezoneParser::TZInfo.getTimezones.each do |timezone|
120
+ @Database.execute('INSERT INTO `Timezones` (`Name`) VALUES (?)', timezone)
121
+ @TimezoneIds[timezone] = @Database.last_insert_row_id
122
+ end
123
+ end
124
+
125
+ def loadTimezoneTerritories
126
+ YAML.load_file(@DataDir + 'countries.yaml').each do |timezone, countries|
127
+ countries.each do |country|
128
+ @Database.execute('INSERT INTO TimezoneTerritories (Timezone, Territory) VALUES (?, ?)', [@TimezoneIds[timezone], @TerritoryIds[country]])
129
+ end
130
+ end
131
+ end
132
+
133
+ def loadMetazones
134
+ @MetazoneIds = {}
135
+ YAML.load_file(@DataDir + 'metazones.yaml').each do |metazone, entries|
136
+ @Database.execute('INSERT INTO `Metazones` (`Name`) VALUES (?)', [metazone])
137
+ @MetazoneIds[metazone] = @Database.last_insert_row_id
138
+ entries.each do |entry|
139
+ @Database.execute('INSERT INTO `MetazonePeriods` (`Metazone`, `From`, `To`) VALUES (?, ?, ?)', [@MetazoneIds[metazone], entry['From'], entry['To']])
140
+ period = @Database.last_insert_row_id
141
+ entry['Timezones'].each do |timezone|
142
+ @Database.execute('INSERT INTO `MetazonePeriod_Timezones` (`MetazonePeriod`, `Timezone`) VALUES (?, ?)', [period, @TimezoneIds[timezone]])
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def getTypes(data)
149
+ types = nil
150
+ if data['Types']
151
+ types = types.to_i | TimezoneParser::ZoneInfo::TIMEZONE_TYPE_STANDARD if data['Types'].include?('standard')
152
+ types = types.to_i | TimezoneParser::ZoneInfo::TIMEZONE_TYPE_DAYLIGHT if data['Types'].include?('daylight')
153
+ end
154
+ types
155
+ end
156
+
157
+ def loadTimezoneNames
158
+ YAML.load_file(@DataDir + 'timezones.yaml').each do |locale, localeData|
159
+ localeData.each do |name, data|
160
+ @Database.execute('INSERT INTO TimezoneNames (`Locale`, `Name`, `NameLowercase`, Types) VALUES (?, ?, ?, ?)', [getLocaleId(locale), name, name.downcase, getTypes(data)])
161
+ nameId = @Database.last_insert_row_id
162
+ data['Timezones'].to_a.each do |timezone|
163
+ @Database.execute('INSERT INTO TimezoneName_Timezones (`Name`, `Timezone`) VALUES (?, ?)', [nameId, @TimezoneIds[timezone]])
164
+ end
165
+ data['Metazones'].to_a.each do |metazone|
166
+ @Database.execute('INSERT INTO TimezoneName_Metazones (`Name`, `Metazone`) VALUES (?, ?)', [nameId, @MetazoneIds[metazone]])
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ def loadAbbreviations
173
+ YAML.load_file(@DataDir + 'abbreviations.yaml').each do |abbreviation, entries|
174
+ @Database.execute('INSERT INTO `Abbreviations` (`Name`, `NameLowercase`) VALUES (?, ?)', [abbreviation, abbreviation.downcase])
175
+ abbreviationId = @Database.last_insert_row_id
176
+ entries.each do |entry|
177
+ @Database.execute('INSERT INTO `AbbreviationOffsets` (`Abbreviation`, `Offset`, `Types`, `From`, `To`) VALUES (?, ?, ?, ?, ?)', [abbreviationId, entry['Offset'], getTypes(entry), entry['From'], entry['To']])
178
+ offset = @Database.last_insert_row_id
179
+ entry['Timezones'].to_a.each do |timezone|
180
+ @Database.execute('INSERT INTO `AbbreviationOffset_Timezones` (`Offset`, `Timezone`) VALUES (?, ?)', [offset, @TimezoneIds[timezone]])
181
+ end
182
+ entry['Metazones'].to_a.each do |timezone|
183
+ @Database.execute('INSERT INTO `AbbreviationOffset_Metazones` (`Offset`, `Metazone`) VALUES (?, ?)', [offset, @MetazoneIds[timezone]])
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ def loadRailsTimezones
190
+ @RailsTimezoneIds = {}
191
+ YAML.load_file(@DataDir + 'rails.yaml').each do |name, timezone|
192
+ @Database.execute('INSERT INTO `RailsTimezones` (`Name`, `Timezone`) VALUES (?, ?)', [name, @TimezoneIds[timezone]])
193
+ @RailsTimezoneIds[name] = @Database.last_insert_row_id
194
+ @Database.execute('INSERT INTO `RailsI18N` (`Locale`, `Name`, `NameLowercase`, `Zone`) VALUES (?, ?, ?, ?)', [getLocaleId('en'), name, name.downcase, @RailsTimezoneIds[name]])
195
+ end
196
+ end
197
+
198
+ def loadRailsI18N
199
+ YAML.load_file(@DataDir + 'rails_i18n.yaml').each do |locale, data|
200
+ data.each do |name, zone|
201
+ @Database.execute('INSERT INTO `RailsI18N` (`Locale`, `Name`, `NameLowercase`, `Zone`) VALUES (?, ?, ?, ?)', [getLocaleId(locale), name, name.downcase, @RailsTimezoneIds[zone]])
202
+ end
203
+ end
204
+ end
205
+
206
+ def loadWindowsZones
207
+ @WindowsZoneIds = {}
208
+ YAML.load_file(@DataDir + 'windows_offsets.yaml').each do |zone, data|
209
+ @Database.execute('INSERT INTO `WindowsZones` (`Name`, `Standard`, `Daylight`) VALUES (?, ?, ?)', [zone, data['standard'], data['daylight']])
210
+ @WindowsZoneIds[zone] = @Database.last_insert_row_id
211
+ end
212
+ end
213
+
214
+ def loadWindowsZoneTimezones
215
+ YAML.load_file(@DataDir + 'windows_timezones.yaml').each do |zone, data|
216
+ unless @WindowsZoneIds.has_key?(zone)
217
+ puts "Warning! Need to update windows_offsets.yaml! No timezone offset found for '#{zone}'"
218
+ next
219
+ end
220
+ data.each do |territory, entries|
221
+ entries.each do |timezone|
222
+ @Database.execute('INSERT INTO `WindowsZone_Timezones` (`Zone`, `Territory`, `Timezone`) VALUES (?, ?, ?)', [@WindowsZoneIds[zone], @TerritoryIds[territory], @TimezoneIds[timezone]])
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ def loadWindowsZoneNames
229
+ YAML.load_file(@DataDir + 'windows_zonenames.yaml').each do |locale, localeData|
230
+ localeData.each do |name, data|
231
+ @Database.execute('INSERT INTO `WindowsZoneNames` (`Locale`, `Name`, `NameLowercase`, `Types`) VALUES (?, ?, ?, ?)', [getLocaleId(locale), name, name.downcase, getTypes(data)])
232
+ nameid = @Database.last_insert_row_id
233
+ data['Metazones'].to_a.each do |zone|
234
+ @Database.execute('INSERT INTO `WindowsZoneName_Zones` (`Name`, `Zone`) VALUES (?, ?)', [nameid, @WindowsZoneIds[zone]])
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ end
241
+ end
242
+ end
@@ -1,172 +1,34 @@
1
1
  # encoding: utf-8
2
2
  require 'date'
3
3
  require 'insensitive_hash'
4
+ require 'sqlite3'
4
5
 
5
6
  module TimezoneParser
6
7
  class Data
7
8
  # Timezone data Storage class
8
9
  class Storage
9
10
  protected
10
- @@Abbreviations = nil
11
- @@Timezones = nil
12
- @@TimezoneCountries = nil
13
- @@Metazones = nil
14
- @@WindowsZones = nil
15
- @@WindowsTimezones = nil
16
- @@WindowsOffsets = nil
17
- @@RailsZones = nil
18
- @@RailsTranslated = nil
11
+ @@Database = nil
12
+ @@Statements = {}
19
13
 
20
14
  public
21
15
 
22
- def self.Abbreviations
23
- unless @@Abbreviations
24
- @@Abbreviations = Marshal.load(File.open(Data::DataDir + 'abbreviations.dat'))
25
- @@Abbreviations.each do |abbr, data|
26
- proccessData(data)
27
- end
28
- end
29
- @@Abbreviations
30
- end
31
-
32
- def self.Timezones
33
- unless @@Timezones
34
- @@Timezones = Marshal.load(File.open(Data::DataDir + 'timezones.dat')).insensitive
35
- end
36
- @@Timezones
37
- end
38
-
39
- def self.TimezoneCountries
40
- unless @@TimezoneCountries
41
- @@TimezoneCountries = Marshal.load(File.open(Data::DataDir + 'countries.dat')).insensitive
42
- end
43
- @@TimezoneCountries
44
- end
45
-
46
- def self.Metazones
47
- unless @@Metazones
48
- @@Metazones = Marshal.load(File.open(Data::DataDir + 'metazones.dat'))
49
- @@Metazones.each do |zone, data|
50
- proccessData(data)
51
- end
52
- end
53
- @@Metazones
54
- end
55
-
56
- def self.WindowsZones
57
- unless @@WindowsZones
58
- @@WindowsZones = Marshal.load(File.open(Data::DataDir + 'windows_zonenames.dat')).insensitive
59
- end
60
- @@WindowsZones
61
- end
62
-
63
- def self.WindowsTimezones
64
- unless @@WindowsTimezones
65
- @@WindowsTimezones = Marshal.load(File.open(Data::DataDir + 'windows_timezones.dat')).insensitive
66
- end
67
- @@WindowsTimezones
68
- end
69
-
70
- def self.WindowsOffsets
71
- unless @@WindowsOffsets
72
- @@WindowsOffsets = Marshal.load(File.open(Data::DataDir + 'windows_offsets.dat')).insensitive
73
- end
74
- @@WindowsOffsets
75
- end
76
-
77
- def self.RailsZones
78
- unless @@RailsZones
79
- @@RailsZones = Marshal.load(File.open(Data::DataDir + 'rails.dat')).insensitive
80
- end
81
- @@RailsZones
82
- end
83
-
84
- def self.RailsTranslated
85
- unless @@RailsTranslated
86
- @@RailsTranslated = Marshal.load(File.open(Data::DataDir + 'rails_i18n.dat')).insensitive
87
- end
88
- @@RailsTranslated
89
- end
90
-
91
- def self.preload(modules)
92
- preloaded = false
93
- modules.each do |m|
94
- case m
95
- when :Abbreviations
96
- self.Abbreviations
97
- self.Metazones
98
- preloaded = true
99
- when :Timezones
100
- self.Timezones
101
- self.Metazones
102
- preloaded = true
103
- when :WindowsZones
104
- self.WindowsZones
105
- self.WindowsTimezones
106
- self.WindowsOffsets
107
- preloaded = true
108
- when :RailsZones
109
- self.RailsZones
110
- self.RailsTranslated
111
- preloaded = true
112
- end
113
- end
114
- preloaded
115
- end
116
-
117
- def self.getTimezones(metazone, toTime, fromTime, regions = [])
118
- timezones = SortedSet.new
119
- if self.Metazones.has_key?(metazone)
120
- entries = Data::loadEntries(self.Metazones[metazone], toTime, fromTime)
121
- entries.each do |entry|
122
- add = true
123
- timezones += entry['Timezones'].select do |timezone|
124
- if regions.empty?
125
- true
126
- else
127
- timezoneRegions = self.TimezoneCountries[timezone]
128
- timezoneRegions && !(regions & timezoneRegions).empty?
129
- end
130
- end
131
- end
132
- end
133
- timezones
134
- end
16
+ DatabaseName = 'timezones.db'
135
17
 
136
- def self.getTimezones2(zone, regions = [])
137
- timezones = SortedSet.new
138
- if self.WindowsTimezones.has_key?(zone)
139
- entries = self.WindowsTimezones[zone]
140
- regions = entries.keys if regions.empty?
141
- regions.each do |region|
142
- next unless entries.has_key?(region)
143
- timezones += entries[region]
144
- end
18
+ def self.Database
19
+ unless @@Database
20
+ @@Database = SQLite3::Database.new((Data::DataDir + DatabaseName).to_s, { readonly: true } )
145
21
  end
146
- timezones
22
+ @@Database
147
23
  end
148
24
 
149
- def self.getOffsets(zone, types = [])
150
- offsets = SortedSet.new
151
- if self.WindowsOffsets.has_key?(zone)
152
- data = self.WindowsOffsets[zone]
153
- types = [:standard, :daylight] if types.empty?
154
- types.each do |type|
155
- next unless data.has_key?(type)
156
- offsets << data[type]
157
- end
25
+ def self.getStatement(statement)
26
+ unless @@Statements.has_key?(statement)
27
+ @@Statements[statement] = self.Database.prepare(statement)
158
28
  end
159
- offsets
29
+ @@Statements[statement]
160
30
  end
161
31
 
162
- protected
163
-
164
- def self.proccessData(data)
165
- data.each do |entry|
166
- entry['From'] = DateTime.parse(entry['From']) if entry['From']
167
- entry['To'] = DateTime.parse(entry['To']) if entry['To']
168
- end
169
- end
170
32
  end
171
33
  end
172
34
  end