TimezoneParser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+ require 'tzinfo'
3
+ require 'tzinfo/data/tzdataparser'
4
+ require 'uri'
5
+ require 'open-uri'
6
+ require 'rubygems/package'
7
+ require 'zlib'
8
+ require 'yaml'
9
+ require 'pathname'
10
+ require 'timezone_parser/data'
11
+
12
+ module TimezoneParser
13
+ # TZInfo module
14
+ module TZInfo
15
+
16
+ protected
17
+ @@Version = nil
18
+ @@TimezoneCountries = nil
19
+
20
+ public
21
+
22
+ # TZData source location
23
+ TZDataSource = 'ftp://ftp.iana.org/tz/tzdata-latest.tar.gz'
24
+ # Extracted TZData path
25
+ TZDataPath = TimezoneParser::Data::VendorDir + 'zoneinfo'
26
+ # TZInfo data path
27
+ TZInfoData = TimezoneParser::Data::VendorDir + 'tzinfo'
28
+ # Max Timestamp
29
+ LastTimestamp = 2147483647
30
+ def self.download(source = TZDataSource, location = TZDataPath, target = TZInfoData)
31
+ URI.parse(source).open do |tempfile|
32
+ FileUtils.mkdir_p(location)
33
+ tar = Gem::Package::TarReader.new(Zlib::GzipReader.open(tempfile.path))
34
+ tar.each do |entry|
35
+ path = location + entry.full_name
36
+ FileUtils.mkdir_p(path.dirname)
37
+ if entry.file?
38
+ File.open(path, 'wb') do |file|
39
+ file.write(entry.read)
40
+ end
41
+ end
42
+ end
43
+ tar.close
44
+ end
45
+ parser = ::TZInfo::Data::TZDataParser.new(location, target)
46
+ parser.execute
47
+ getVersion(location)
48
+ end
49
+
50
+ def self.getVersion(source = TZDataPath)
51
+ return @@Version if @@Version
52
+ File.open(source + 'Makefile', 'r', { :encoding => 'UTF-8:UTF-8' }) do |file|
53
+ file.each_line do |line|
54
+ line = line.gsub(/#.*$/, '')
55
+ v = line.match(/^\s*VERSION\s*=\s*(\w+)\s*$/)
56
+ @@Version = v[1] if v
57
+ end
58
+ end
59
+ @@Version
60
+ end
61
+
62
+ def self.init
63
+ ::TZInfo::DataSource.set(:ruby, TZInfoData)
64
+ end
65
+
66
+ def self.getTimezoneCountries
67
+ unless @@TimezoneCountries
68
+ @@TimezoneCountries = {}
69
+ ::TZInfo::Country.all.each do |countryData|
70
+ countryData.zone_identifiers.each do |timezone|
71
+ @@TimezoneCountries[timezone] ||= []
72
+ @@TimezoneCountries[timezone] << countryData.code
73
+ @@TimezoneCountries[timezone].uniq!
74
+ @@TimezoneCountries[timezone].sort!
75
+ end
76
+ end
77
+ @@TimezoneCountries = Hash[@@TimezoneCountries.to_a.sort_by { |pair| pair.first }]
78
+ end
79
+ @@TimezoneCountries
80
+ end
81
+
82
+ def self.getAbbreviations
83
+ transitionData = {}
84
+ ::TZInfo::Timezone.all_data_zone_identifiers.each do |name|
85
+ zone = ::TZInfo::Timezone.get(name)
86
+ zone_transitions = zone.transitions_up_to(Time.at(LastTimestamp))
87
+ zone_transitions.each_index do |i|
88
+ offset = zone_transitions[i].offset
89
+ next if offset.abbreviation == :LMT or offset.abbreviation == :zzz
90
+ abbr = offset.abbreviation.to_s
91
+ transitionData[abbr] = [] unless transitionData[abbr]
92
+ period = ::TZInfo::TimezonePeriod.new(zone_transitions[i], zone_transitions[i+1])
93
+ transitionData[abbr] << { :name => name, :period => period }
94
+ end
95
+ end
96
+ timezoneData = {}
97
+ transitionData.keys.sort.each do |name|
98
+ transitions = []
99
+ transitionData[name].each do |transition|
100
+ period_start = transition[:period].utc_start
101
+ period_start = period_start.to_s
102
+ period_end = transition[:period].utc_end
103
+ period_end = period_end.to_s if period_end
104
+ timezone = transition[:name]
105
+ countries = getTimezoneCountries[timezone]
106
+ countries = countries.dup if countries
107
+ data = { 'Offset' => transition[:period].utc_total_offset, 'Timezones' => [timezone], 'Countries' => countries, 'From' => period_start }
108
+ data['To'] = period_end if period_end
109
+ transitions << data
110
+ end
111
+ transitions.sort_by! { |data| [data['To'] ? data['To'] : 'zzzz', data['From']] }
112
+
113
+ abbreviationData = []
114
+ abbreviationData << transitions.shift
115
+ transitions.each do |data|
116
+ current = abbreviationData.last
117
+ if data['Offset'] == current['Offset'] and (current['Timezones'].sort == data['Timezones'].sort || (data['From'] == current['From'] and data['To'] == current['To']))
118
+ current['Timezones'] += data['Timezones']
119
+ current['Timezones'].uniq!
120
+ current['Timezones'].sort!
121
+ if data['Countries']
122
+ current['Countries'] = current['Countries'].to_a + data['Countries']
123
+ current['Countries'].uniq!
124
+ current['Countries'].sort!
125
+ end
126
+ current['To'] = data['To']
127
+ current.delete('To') unless current['To']
128
+ else
129
+ abbreviationData << data
130
+ end
131
+ end
132
+ previous = nil
133
+ abbreviationData.delete_if do |item|
134
+ if previous.nil?
135
+ previous = item
136
+ false
137
+ elsif previous['Offset'] == item['Offset'] and previous['Timezones'] == item['Timezones']
138
+ previous['To'] = item['To']
139
+ previous.delete('To') unless previous['To']
140
+ true
141
+ else
142
+ previous = item
143
+ false
144
+ end
145
+ end
146
+ timezoneData[name] = abbreviationData
147
+ end
148
+ timezoneData
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,124 @@
1
+ # encoding: utf-8
2
+ require 'win32/registry'
3
+ require 'fiddle'
4
+
5
+ module TimezoneParser
6
+ # Windows module
7
+ module Windows
8
+
9
+ protected
10
+
11
+ @@Version = nil
12
+ @@Errors = ''
13
+
14
+ public
15
+ # Windows Registry path to Time Zone data
16
+ TimeZonePath = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones'
17
+ def self.errors
18
+ @@Errors
19
+ end
20
+
21
+ def self.getVersion(path = TimeZonePath)
22
+ return @@Version if @@Version
23
+ begin
24
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(path, Win32::Registry::KEY_READ) do |reg|
25
+ @@Version = reg['TzVersion', Win32::Registry::REG_DWORD].to_s(16)
26
+ end
27
+ rescue Win32::Registry::Error => e
28
+ @@Errors << e.message
29
+ end
30
+ @@Version
31
+ end
32
+
33
+ def self.getTimezones(path = TimeZonePath)
34
+ timezones = {}
35
+ begin
36
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(path, Win32::Registry::KEY_READ).each_key do |key, wtime|
37
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(path + '\\' + key, Win32::Registry::KEY_READ) do |reg|
38
+ timezones[key] ||= {}
39
+ tzi = reg.read('TZI', Win32::Registry::REG_BINARY).last
40
+ # TZI Structure (http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481.aspx)
41
+ # typedef struct _REG_TZI_FORMAT
42
+ # {
43
+ # LONG Bias;
44
+ # LONG StandardBias;
45
+ # LONG DaylightBias;
46
+ # SYSTEMTIME StandardDate;
47
+ # SYSTEMTIME DaylightDate;
48
+ # } REG_TZI_FORMAT;
49
+ unpacked = tzi.unpack('lllSSSSSSSSSSSSSSSS')
50
+ timezones[key]['standard'] = (0 - unpacked[0] - unpacked[1]) * 60
51
+ timezones[key]['daylight'] = (0 - unpacked[0] - unpacked[2]) * 60
52
+ end
53
+ end
54
+ rescue Win32::Registry::Error => e
55
+ @@Errors << e.message
56
+ end
57
+ timezones = Hash[timezones.to_a.sort_by { |d| d.first } ]
58
+ timezones
59
+ end
60
+
61
+ def self.getMUIOffsets(path = TimeZonePath)
62
+ offsets = {}
63
+ begin
64
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(path, Win32::Registry::KEY_READ).each_key do |key, wtime|
65
+ Win32::Registry::HKEY_LOCAL_MACHINE.open(path + '\\' + key, Win32::Registry::KEY_READ) do |reg|
66
+ muiDlt = reg.read_s('MUI_Dlt')
67
+ muiStd = reg.read_s('MUI_Std')
68
+
69
+ offsets[self.parseMUI(muiDlt)] = ['daylight', key]
70
+ offsets[self.parseMUI(muiStd)] = ['standard', key]
71
+ end
72
+ end
73
+ rescue Win32::Registry::Error => e
74
+ @@Errors << e.message
75
+ end
76
+ puts @@Errors
77
+ offsets
78
+ end
79
+
80
+ def self.parseMUI(str)
81
+ str.split(',').last.to_i.abs
82
+ end
83
+
84
+ def self.parseMetazones(metazoneList, offsets)
85
+ metazones = {}
86
+ metazoneList.each do |lcid, data|
87
+ localeMem = Fiddle::Pointer.malloc(LOCALE_NAME_MAX_LENGTH)
88
+ chars = LCIDToLocaleName.call(lcid, localeMem, LOCALE_NAME_MAX_LENGTH, 0)
89
+ return nil if chars.zero?
90
+ locale = localeMem.to_s((chars-1)*2).force_encoding(Encoding::UTF_16LE).encode(Encoding::UTF_8)
91
+ metazones[locale] = {}
92
+ offsets.each do |id, info|
93
+ name = data[id]
94
+ metazones[locale][name] ||= {}
95
+ metazones[locale][name]['Types'] ||= []
96
+ metazones[locale][name]['Metazones'] ||= []
97
+ metazones[locale][name]['Types'] << info.first
98
+ metazones[locale][name]['Metazones'] << info.last
99
+ metazones[locale][name]['Types'].uniq!
100
+ metazones[locale][name]['Metazones'].uniq!
101
+ end
102
+ metazones[locale] = Hash[metazones[locale].to_a.sort_by { |d| d.first } ]
103
+ end
104
+ metazones = Hash[metazones.to_a.sort_by { |d| d.first } ]
105
+ metazones
106
+ end
107
+
108
+ # Windows Kernel32 library
109
+ kernel32 = Fiddle.dlopen('kernel32')
110
+
111
+ # function
112
+ # int LCIDToLocaleName (
113
+ # _In_ LCID Locale,
114
+ # _Out_opt_ LPWSTR lpName,
115
+ # _In_ int cchName,
116
+ # _In_ DWORD dwFlags
117
+ # );
118
+ # @see http://msdn.microsoft.com/en-us/library/windows/desktop/dd318698.aspx
119
+ LCIDToLocaleName = Fiddle::Function.new( kernel32['LCIDToLocaleName'],
120
+ [Fiddle::TYPE_LONG, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_LONG], Fiddle::TYPE_INT )
121
+ # Max locale length
122
+ LOCALE_NAME_MAX_LENGTH = 85
123
+ end
124
+ end
@@ -0,0 +1,178 @@
1
+ # encoding: utf-8
2
+
3
+ module TimezoneParser
4
+ # Rails zone data
5
+ class RailsData < Data
6
+ protected
7
+ @RailsZone = nil
8
+
9
+ public
10
+ attr_reader :RailsZone
11
+ def processEntry(data, rails)
12
+ if rails
13
+ @RailsZone = rails
14
+ @Metazones << rails
15
+ @Timezones << data
16
+ else
17
+ rails = Storage.RailsZones[data]
18
+ if rails
19
+ @RailsZone = data
20
+ @Metazones << data
21
+ @Timezones << rails
22
+ end
23
+ end
24
+ self
25
+ end
26
+ end
27
+
28
+ # Rails zone
29
+ class RailsZone < ZoneInfo
30
+ protected
31
+ @@Locales = []
32
+
33
+ public
34
+ # Locales which will be used for RailsZone methods if not specified there
35
+ #
36
+ # Each locale is language identifier based on IETF BCP 47 and ISO 639 code
37
+ # @return [Array<String>] list containing locale identifiers
38
+ # @see http://en.wikipedia.org/wiki/IETF_language_tag
39
+ def self.Locales
40
+ @@Locales
41
+ end
42
+
43
+ attr_accessor :All
44
+
45
+ # Rails zone instance
46
+ # @param name [String] Rails zone name
47
+ def initialize(name)
48
+ @Name = name
49
+ @Data = RailsData.new
50
+ @Valid = nil
51
+ setTime
52
+ set(@@Locales.dup, true)
53
+ end
54
+
55
+ # Set locales and all
56
+ # @param locales [Array<String>] search only in these locales
57
+ # @param all [Boolean] specify whether should search for all zones or return as soon as found any
58
+ # @return [RailsZone] self
59
+ # @see Locales
60
+ def set(locales = nil, all = true)
61
+ @Locales = locales unless locales.nil?
62
+ @All = all ? true : false
63
+ self
64
+ end
65
+
66
+ # Check if Rails zone is valid
67
+ # @return [Boolean] whether Rails zone is valid
68
+ def isValid?
69
+ if @Valid.nil?
70
+ @Valid = false
71
+ @Valid = Data::Storage.RailsZones.has_key?(@Name) if (not @Locales) or (@Locales and @Locales.include?('en'))
72
+ return @Valid if @Valid
73
+ locales = @Locales
74
+ locales = Data::Storage.RailsTranslated.keys if locales.empty?
75
+ locales.each do |locale|
76
+ next unless Data::Storage.RailsTranslated.has_key?(locale)
77
+ @Valid = Data::Storage.RailsTranslated[locale].has_key?(@Name)
78
+ return @Valid if @Valid
79
+ end
80
+ end
81
+ @Valid = false
82
+ end
83
+
84
+ # Rails zone data
85
+ # @return [RailsData] data
86
+ def getData
87
+ unless @Loaded
88
+ @Loaded = true
89
+ @Valid = false
90
+ @Valid = Data::Storage.RailsZones.has_key?(@Name) if (not @Locales) or (@Locales and @Locales.include?('en'))
91
+ if @Valid
92
+ @Data.processEntry(Data::Storage.RailsZones[@Name], @Name)
93
+ return @Data unless @All
94
+ end
95
+ locales = @Locales
96
+ locales = Data::Storage.RailsTranslated.keys if locales.empty?
97
+ locales.each do |locale|
98
+ next unless Data::Storage.RailsTranslated.has_key?(locale)
99
+ entry = Data::Storage.RailsTranslated[locale][@Name]
100
+ if entry
101
+ @Data.processEntry(entry, false)
102
+ @Valid = true
103
+ return @Data unless @All
104
+ end
105
+ end
106
+ end
107
+ @Data
108
+ end
109
+
110
+ # Get UTC offsets in seconds
111
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
112
+ def getOffsets
113
+ if not @Offsets and not getTimezones.empty?
114
+ @Offsets = @Data.findOffsets(@ToTime, @FromTime).to_a
115
+ else
116
+ super
117
+ end
118
+ @Offsets
119
+ end
120
+
121
+ # Rails zone identifier
122
+ # @return [String] Rails zone identifier
123
+ def getZone
124
+ getData.RailsZone
125
+ end
126
+
127
+ # Check if given Rails zone name is a valid timezone
128
+ # @param name [String] Rails zone name
129
+ # @param locales [Array<String>] search zone name only for these locales
130
+ # @return [Boolean] whether Timezone is valid
131
+ # @see Locales
132
+ def self.isValid?(name, locales = nil)
133
+ self.new(name).set(locales).isValid?
134
+ end
135
+
136
+ # Get UTC offsets in seconds for given Rails zone name
137
+ # @param name [String] Rails zone name
138
+ # @param toTime [DateTime] look for offsets which came into effect before this date, exclusive
139
+ # @param fromTime [DateTime] look for offsets which came into effect at this date, inclusive
140
+ # @param locales [Array<String>] search zone name only for these locales
141
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
142
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
143
+ # @see Locales
144
+ def self.getOffsets(name, toTime = nil, fromTime = nil, locales = nil, all = true)
145
+ self.new(name).setTime(toTime, fromTime).set(locales, all).getOffsets
146
+ end
147
+
148
+ # Get Timezone identifiers for given Rails zone name
149
+ # @param name [String] Rails zone name
150
+ # @param locales [Array<String>] search zone name only for these locales
151
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
152
+ # @return [Array<String>] list of timezone identifiers
153
+ # @see Locales
154
+ def self.getTimezones(name, locales = nil, all = true)
155
+ self.new(name).set(locales, all).getTimezones
156
+ end
157
+
158
+ # Get Metazone identifiers for given Rails zone name
159
+ # @param name [String] Rails zone name
160
+ # @param locales [Array<String>] search zone name only for these locales
161
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
162
+ # @return [Array<String>] list of metazone identifiers
163
+ # @see Locales
164
+ # @see Regions
165
+ def self.getMetazones(name, locales = nil, all = true)
166
+ self.new(name).set(locales, all).getMetazones
167
+ end
168
+
169
+ # Rails zone identifier
170
+ # @param name [String] Rails zone name
171
+ # @param locales [Array<String>] search zone name only for these locales
172
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
173
+ # @return [String] Timezone identifier
174
+ def self.getZone(name, locales = nil, all = true)
175
+ self.new(name).set(locales, all).getZone
176
+ end
177
+ end
178
+ end