TimezoneParser 0.4.0 → 1.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.
@@ -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