TimezoneParser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+
3
+ module TimezoneParser
4
+ # Timezone abbreviation
5
+ class Abbreviation < ZoneInfo
6
+ protected
7
+ @@Regions = []
8
+
9
+ public
10
+ # Regions which will be used for Abbreviation methods if not specified there
11
+ #
12
+ # Each region is either ISO 3166-1 alpha-2 code
13
+ # @return [Array<String>] list containing region identifiers
14
+ # @see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
15
+ def self.Regions
16
+ @@Regions
17
+ end
18
+
19
+ attr_accessor :Regions
20
+ attr_accessor :Type
21
+
22
+ # Abbreviation instance
23
+ # @param abbreviation [String] Timezone abbreviation
24
+ def initialize(abbreviation)
25
+ @Abbreviation = abbreviation
26
+ @Data = Data.new
27
+ setTime
28
+ set(@@Regions.dup, nil)
29
+ end
30
+
31
+ # Set regions and type
32
+ # @param regions [Array<String>] filter for these regions
33
+ # @param type [Symbol] filter by type, :standard time or :daylight
34
+ # @return [Abbreviation] self
35
+ # @see Regions
36
+ def set(regions = nil, type = nil)
37
+ @Regions = regions unless regions.nil?
38
+ @Type = type.to_sym if type
39
+ self
40
+ end
41
+
42
+ # Check if abbreviation is valid
43
+ # @return [Boolean] whether abbreviation is valid
44
+ def isValid?
45
+ Data::Storage.Abbreviations.has_key?(@Abbreviation)
46
+ end
47
+
48
+ # Abbreviation data
49
+ # @return [Data] data
50
+ def getData
51
+ unless @Loaded
52
+ @Loaded = true
53
+ if isValid?
54
+ data = Data::Storage.Abbreviations[@Abbreviation]
55
+ if data.count == 1
56
+ @Data.processEntry(data.first, @ToTime, @FromTime, @Regions)
57
+ return @Data
58
+ end
59
+ entries = Data.filterData(data, @ToTime, @FromTime, @Type, @Regions)
60
+ entries.each do |entry|
61
+ @Data.processEntry(entry, @ToTime, @FromTime, @Regions)
62
+ end
63
+ return @Data
64
+ end
65
+ end
66
+ @Data
67
+ end
68
+
69
+ # Get UTC offsets in seconds
70
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
71
+ def getOffsets
72
+ unless @Offsets
73
+ @Offsets = getData.Offsets.to_a
74
+ if @Offsets.empty? and not getTimezones.empty?
75
+ types = nil
76
+ types = [@Type] if @Type
77
+ @Offsets = @Data.findOffsets(@ToTime, @FromTime, @Regions, types).to_a
78
+ end
79
+ end
80
+ @Offsets
81
+ end
82
+
83
+ # Check if given Timezone abbreviation is a valid timezone
84
+ # @param abbreviation [String] Timezone abbreviation
85
+ # @return [Boolean] whether Timezone is valid
86
+ def self.isValid?(abbreviation)
87
+ Data::Storage.Abbreviations.has_key?(abbreviation)
88
+ end
89
+
90
+ # Get UTC offsets in seconds for given Timezone abbreviation
91
+ # @param abbreviation [String] Timezone abbreviation
92
+ # @param toTime [DateTime] look for offsets which came into effect before this date, exclusive
93
+ # @param fromTime [DateTime] look for offsets which came into effect at this date, inclusive
94
+ # @param regions [Array<String>] look for offsets only for these regions
95
+ # @param type [Symbol] specify whether offset should be :standard time or :daylight
96
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
97
+ # @see Regions
98
+ def self.getOffsets(abbreviation, toTime = nil, fromTime = nil, regions = nil, type = nil)
99
+ self.new(abbreviation).setTime(toTime, fromTime).set(regions, type).getOffsets
100
+ end
101
+
102
+ # Get Timezone identifiers for given Timezone abbreviation
103
+ # @param abbreviation [String] Timezone abbreviation
104
+ # @param toTime [DateTime] look for timezones which came into effect before this date, exclusive
105
+ # @param fromTime [DateTime] look for timezones which came into effect at this date, inclusive
106
+ # @param regions [Array<String>] look for timezones only for these regions
107
+ # @param type [Symbol] specify whether timezones should be :standard time or :daylight
108
+ # @return [Array<String>] list of timezone identifiers
109
+ # @see Regions
110
+ def self.getTimezones(abbreviation, toTime = nil, fromTime = nil, regions = nil, type = nil)
111
+ self.new(abbreviation).setTime(toTime, fromTime).set(regions, type).getTimezones
112
+ end
113
+
114
+ # Get Metazone identifiers for given Timezone abbreviation
115
+ # @param abbreviation [String] Timezone abbreviation
116
+ def self.getMetazones(abbreviation)
117
+ self.new(abbreviation).getMetazones
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+ require 'pathname'
3
+ require 'set'
4
+ require 'tzinfo'
5
+ require 'timezone_parser/data/storage'
6
+
7
+ module TimezoneParser
8
+ # Timezone data
9
+ class Data
10
+ # Library Root directory
11
+ RootDir = Pathname.new(__FILE__).realpath.dirname.parent.parent
12
+ # Path to Data directory
13
+ DataDir = RootDir + 'data'
14
+ # Path to Vendor directory
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
+ ts = false
57
+ tz.transitions_up_to(toTime, fromTime).each do |transition|
58
+ ts = true
59
+ self.class.addOffset(offsets, transition.offset, types)
60
+ end
61
+ self.class.addOffset(offsets, tz.period_for_utc(toTime - 0.001).offset, types) unless ts
62
+ @Offsets += offsets
63
+ end
64
+ @Offsets
65
+ end
66
+
67
+ # Load data entries which match specified time
68
+ # @param data [Array<Hash>] array of entries
69
+ # @param toTime [DateTime] entries before this date, exclusive
70
+ # @param fromTime [DateTime] entries after/at this date, inclusive
71
+ # @return [Array<Hash>] resulting array containing filtered entries
72
+ def self.loadEntries(data, toTime, fromTime, offsets = false)
73
+ result = []
74
+ data.each do |entry|
75
+ result << entry if (entry['From'] && entry['To'] and toTime > entry['From'] and fromTime < entry['To']) or
76
+ (entry['From'] && !entry['To'] and toTime > entry['From']) or
77
+ (!entry['From'] && entry['To'] and fromTime < entry['To']) or
78
+ (!entry['From'] && !entry['To'])
79
+ end
80
+ result.each do |entry|
81
+ return result if not offsets or (offsets and entry['Offset'])
82
+ end
83
+ data.each_index do |i|
84
+ entry = data[i]
85
+ nextentry = offsets ? getNextEntry(data, i) : data[i+1]
86
+ result << entry if ( entry['From'] && entry['To'] and toTime > entry['From'] and fromTime < entry['To'] ) or
87
+ ( entry['To'] and ( (nextentry.nil? and fromTime >= entry['To']) or
88
+ (nextentry and nextentry['From'] and fromTime >= entry['To'] and toTime <= nextentry['From']) or
89
+ (!entry['From'] and fromTime < entry['To']) ) ) or
90
+ ( entry['From'] and ( (i.zero? and toTime <= entry['From']) or (!entry['To'] and toTime > entry['From']) ) )
91
+ end
92
+ result
93
+ end
94
+
95
+ def self.filterData(data, toTime, fromTime, type, regions)
96
+ entries = []
97
+ data.each do |entry|
98
+ next if type and entry['Types'] and not entry['Types'].include?(type)
99
+ next if not regions.empty? and entry['Countries'] and (entry['Countries'] & regions).empty?
100
+ entries << entry
101
+ end
102
+ loadEntries(entries, toTime, fromTime, true)
103
+ end
104
+
105
+ protected
106
+
107
+ def self.getNextEntry(data, i)
108
+ j = 1
109
+ begin
110
+ entry = data[i+j]
111
+ j += 1
112
+ end until entry.nil? or (entry and entry['Offset'])
113
+ entry
114
+ end
115
+
116
+ def self.addOffset(offsets, offset, types)
117
+ offsets << offset.utc_total_offset if (offset.dst? and types.include?(:daylight)) or (not offset.dst? and types.include?(:standard))
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,175 @@
1
+ # encoding: utf-8
2
+ require 'cldr'
3
+ require 'cldr/download'
4
+ require 'cldr/export/data'
5
+ require 'cldr/export/data/timezones'
6
+ require 'pathname'
7
+
8
+ module TimezoneParser
9
+ # CLDR module
10
+ module CLDR
11
+
12
+ # Data directory
13
+ DataDir = Pathname.new(Cldr::Export::Data.dir)
14
+
15
+ protected
16
+ @@Version = nil
17
+
18
+ public
19
+ def self.download(source = 'http://unicode.org/Public/cldr/latest/core.zip', target = nil)
20
+ Cldr.download(source, target)
21
+ end
22
+
23
+ def self.updateHash(hash, name, data)
24
+ hash[name] ||= []
25
+ hash[name] << data.to_s
26
+ hash[name].uniq!
27
+ hash[name].sort!
28
+ hash
29
+ end
30
+
31
+ def self.getVersion(source = DataDir)
32
+ return @@Version if @@Version
33
+ content = File.read(source + 'dtd' + 'ldml.dtd')
34
+ content.gsub!(/<!--.*?-->/, '')
35
+ data = content.match(/\s+cldrVersion\s+[\#\w\s]+\s+"(\d+)"\s*\>/)
36
+ @@Version = data[1].to_i if data
37
+ @@Version
38
+ end
39
+
40
+ def self.getTimezones
41
+ timezones = { }
42
+ Cldr::Export::Data.locales.sort.each do |locale|
43
+ tz = Cldr::Export::Data::Timezones.new(locale)
44
+ next if tz.timezones.empty? and tz.metazones.empty?
45
+
46
+ tz.timezones.each do |timezone, data|
47
+ next if timezone == :'Etc/Unknown' or data[:city].nil?
48
+ city = data[:city].to_s.encode(Encoding::UTF_8).chomp.strip
49
+ timezones[locale] ||= {}
50
+ timezones[locale][city] ||= {}
51
+ self.updateHash(timezones[locale][city], 'Timezones', timezone)
52
+ data[:long].to_a.each do |type, name|
53
+ name = name.to_s.encode(Encoding::UTF_8).chomp.strip
54
+ type = type.to_s.encode(Encoding::UTF_8)
55
+ timezones[locale][name] ||= {}
56
+ if type == 'generic'
57
+ self.updateHash(timezones[locale][name], 'Types', 'standard')
58
+ self.updateHash(timezones[locale][name], 'Types', 'daylight')
59
+ else
60
+ self.updateHash(timezones[locale][name], 'Types', type)
61
+ end
62
+ self.updateHash(timezones[locale][name], 'Timezones', timezone.to_s.encode(Encoding::UTF_8))
63
+ end
64
+ end
65
+ tz.metazones.each do |metazone, data|
66
+ data[:long].to_a.each do |type, name|
67
+ name = name.to_s.encode(Encoding::UTF_8).chomp.strip
68
+ next if name.empty?
69
+ type = type.to_s.encode(Encoding::UTF_8)
70
+ timezones[locale] ||= {}
71
+ timezones[locale][name] ||= {}
72
+ if type == 'generic'
73
+ self.updateHash(timezones[locale][name], 'Types', 'standard')
74
+ self.updateHash(timezones[locale][name], 'Types', 'daylight')
75
+ else
76
+ self.updateHash(timezones[locale][name], 'Types', type)
77
+ end
78
+ self.updateHash(timezones[locale][name], 'Metazones', metazone.to_s.encode(Encoding::UTF_8))
79
+ end
80
+ end
81
+ timezones[locale] = Hash[timezones[locale].to_a.sort_by { |d| d.first } ] if timezones[locale]
82
+ end
83
+ timezones
84
+ end
85
+
86
+ def self.updateAbbreviations(abbreviations)
87
+ Cldr::Export::Data.locales.sort.each do |locale|
88
+ tz = Cldr::Export::Data::Timezones.new(locale)
89
+ next if tz.timezones.empty? and tz.metazones.empty?
90
+ tz.timezones.each do |timezone, data|
91
+ data[:short].to_a.each do |type, name|
92
+ next if name == '∅∅∅'
93
+ name = name.chomp.strip
94
+ type = type.to_s.encode(Encoding::UTF_8)
95
+ abbreviations[name] ||= []
96
+ data = {}
97
+ add = true
98
+ abbreviations[name].each_index do |i|
99
+ next unless abbreviations[name][i]['Offset'].nil?
100
+ data = abbreviations[name][i]
101
+ add = false
102
+ break
103
+ end
104
+ if type == 'generic'
105
+ self.updateHash(data, 'Types', 'standard')
106
+ self.updateHash(data, 'Types', 'daylight')
107
+ else
108
+ self.updateHash(data, 'Types', type)
109
+ end
110
+ self.updateHash(data, 'Timezones', timezone)
111
+ abbreviations[name] << data if add
112
+ end
113
+ end
114
+ tz.metazones.each do |metazone, data|
115
+ data[:short].to_a.each do |type, name|
116
+ next if name == '∅∅∅'
117
+ name = name.chomp.strip
118
+ type = type.to_s.encode(Encoding::UTF_8)
119
+ abbreviations[name] ||= []
120
+ data = {}
121
+ add = true
122
+ abbreviations[name].each_index do |i|
123
+ next unless abbreviations[name][i]['Offset'].nil?
124
+ data = abbreviations[name][i]
125
+ add = false
126
+ break
127
+ end
128
+ if type == 'generic'
129
+ self.updateHash(data, 'Types', 'standard')
130
+ self.updateHash(data, 'Types', 'daylight')
131
+ else
132
+ self.updateHash(data, 'Types', type)
133
+ end
134
+ self.updateHash(data, 'Metazones', metazone)
135
+ abbreviations[name] << data if add
136
+ end
137
+ end
138
+ end
139
+ abbreviations = Hash[abbreviations.to_a.sort_by { |d| d.first } ]
140
+ abbreviations
141
+ end
142
+
143
+ def self.getMetazones
144
+ zones = {}
145
+ Cldr::Export::Data::Metazones.new[:timezones].each do |timezone, zonedata|
146
+ zonedata.each do |data|
147
+ entry = {}
148
+ add = true
149
+ zones[data['metazone']] ||= []
150
+ zones[data['metazone']].each_index do |i|
151
+ next if zones[data['metazone']][i]['From'].to_s != data['from'].to_s or zones[data['metazone']][i]['To'].to_s != data['to'].to_s
152
+ entry = zones[data['metazone']][i]
153
+ add = false
154
+ break
155
+ end
156
+ self.updateHash(entry, 'Timezones', timezone)
157
+ if add
158
+ entry['From'] = data['from'].to_s if data['from']
159
+ entry['To'] = data['to'].to_s if data['to']
160
+ zones[data['metazone']] << entry
161
+ end
162
+ zones[data['metazone']].sort_by! { |d| [d['To'] ? d['To'] : 'zzzz', d['From'] ? d['From'] : ''] }
163
+ end
164
+ end
165
+ zones = Hash[zones.to_a.sort_by { |d| d.first } ]
166
+ zones
167
+ end
168
+
169
+ def self.getWindowsZones
170
+ zones = Cldr::Export::Data::WindowsZones.new
171
+ zones = Hash[zones.to_a.sort_by { |d| d.first } ]
172
+ zones
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: utf-8
2
+ require 'yaml'
3
+ require 'date'
4
+ require 'insensitive_hash'
5
+
6
+ module TimezoneParser
7
+ class Data
8
+ # Timezone data Storage class
9
+ class Storage
10
+ protected
11
+ @@Abbreviations = nil
12
+ @@Timezones = nil
13
+ @@TimezoneCountries = nil
14
+ @@Metazones = nil
15
+ @@WindowsZones = nil
16
+ @@WindowsTimezones = nil
17
+ @@WindowsOffsets = nil
18
+ @@RailsZones = nil
19
+ @@RailsTranslated = nil
20
+
21
+ public
22
+ def self.Abbreviations
23
+ unless @@Abbreviations
24
+ @@Abbreviations = Marshal.load(File.open(Data::DataDir + 'abbreviations.dat')).insensitive
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')).insensitive
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
135
+
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
145
+ end
146
+ timezones
147
+ end
148
+
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
158
+ end
159
+ offsets
160
+ end
161
+
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
+ end
171
+ end
172
+ end