TimezoneParser 0.1.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.
@@ -0,0 +1,159 @@
1
+ # encoding: utf-8
2
+
3
+ module TimezoneParser
4
+ # Timezone
5
+ class Timezone < ZoneInfo
6
+
7
+ protected
8
+ @@Locales = []
9
+ @@Regions = []
10
+
11
+ public
12
+ # Locales which will be used for Timezone methods if not specified there
13
+ #
14
+ # Each locale is language identifier based on IETF BCP 47
15
+ # @return [Array<String>] list containing locale identifiers
16
+ # @see http://en.wikipedia.org/wiki/IETF_language_tag
17
+ # @see http://unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
18
+ # @see http://www.unicode.org/cldr/charts/latest/supplemental/language_territory_information.html
19
+ def self.Locales
20
+ @@Locales
21
+ end
22
+
23
+ # Regions which will be used for Timezone methods if not specified there
24
+ #
25
+ # Each region is CLDR territory (UN M.49)
26
+ # @return [Array<String>] list containing region identifiers
27
+ # @see http://www.unicode.org/cldr/charts/latest/supplemental/territory_containment_un_m_49.html
28
+ def self.Regions
29
+ @@Regions
30
+ end
31
+
32
+ attr_accessor :Locales
33
+ attr_accessor :Regions
34
+ attr_accessor :All
35
+
36
+ # Timezone instance
37
+ # @param timezone [String] Timezone name
38
+ def initialize(timezone)
39
+ @Timezone = timezone
40
+ @Data = Data.new
41
+ @Valid = nil
42
+ setTime
43
+ set(@@Locales.dup, @@Regions.dup, true)
44
+ end
45
+
46
+ # Set locales, regions and all
47
+ # @param locales [Array<String>] search only in these locales
48
+ # @param regions [Array<String>] filter for these regions
49
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
50
+ # @return [Timezone] self
51
+ def set(locales = nil, regions = nil, all = true)
52
+ @Locales = locales unless locales.nil?
53
+ @Regions = regions unless regions.nil?
54
+ @All = all ? true : false
55
+ self
56
+ end
57
+
58
+ # Check if timezone is valid
59
+ # @return [Boolean] whether timezone is valid
60
+ def isValid?
61
+ if @Valid.nil?
62
+ locales = @Locales
63
+ locales = Data::Storage.Timezones.keys if locales.empty?
64
+ locales.each do |locale|
65
+ next unless Data::Storage.Timezones.has_key?(locale)
66
+ if Data::Storage.Timezones[locale].has_key?(@Timezone)
67
+ @Valid = true
68
+ return @Valid
69
+ end
70
+ end
71
+ end
72
+ @Valid = false
73
+ end
74
+
75
+ # Abbreviation data
76
+ # @return [Data] data
77
+ def getData
78
+ unless @Loaded
79
+ @Loaded = true
80
+ @Valid = false
81
+ locales = @Locales
82
+ locales = Data::Storage.Timezones.keys if locales.empty?
83
+ locales.each do |locale|
84
+ next unless Data::Storage.Timezones.has_key?(locale)
85
+ entry = Data::Storage.Timezones[locale][@Timezone]
86
+ if entry
87
+ @Data.processEntry(entry, @ToTime, @FromTime, @Regions)
88
+ @Valid = true
89
+ return @Data unless @All
90
+ end
91
+ end
92
+ end
93
+ @Data
94
+ end
95
+
96
+ # Get UTC offsets in seconds
97
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
98
+ def getOffsets
99
+ if not @Offsets and not getTimezones.empty?
100
+ types = [@Type] if @Type
101
+ @Offsets = @Data.findOffsets(@ToTime, @FromTime, @Regions, types).to_a
102
+ else
103
+ super
104
+ end
105
+ @Offsets
106
+ end
107
+
108
+ # Check if given Timezone name is a valid timezone
109
+ # @param timezone [String] Timezone name
110
+ # @param locales [Array<String>] search Timezone name only for these locales
111
+ # @return [Boolean] whether Timezone is valid
112
+ # @see Locales
113
+ def self.isValid?(timezone, locales = nil)
114
+ self.new(timezone).set(locales).isValid?
115
+ end
116
+
117
+ # Get UTC offsets in seconds for given Timezone name
118
+ # @param timezone [String] Timezone name
119
+ # @param toTime [DateTime] look for offsets which came into effect before this date, exclusive
120
+ # @param fromTime [DateTime] look for offsets which came into effect at this date, inclusive
121
+ # @param locales [Array<String>] search Timezone name only for these locales
122
+ # @param regions [Array<String>] look for offsets only for these regions
123
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
124
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
125
+ # @see Locales
126
+ # @see Regions
127
+ def self.getOffsets(timezone, toTime = nil, fromTime = nil, locales = nil, regions = nil, all = true)
128
+ self.new(timezone).setTime(toTime, fromTime).set(locales, regions, all).getOffsets
129
+ end
130
+
131
+ # Get Timezone identifiers for given Timezone name
132
+ # @param timezone [String] Timezone name
133
+ # @param toTime [DateTime] look for timezones which came into effect before this date, exclusive
134
+ # @param fromTime [DateTime] look for timezones which came into effect at this date, inclusive
135
+ # @param locales [Array<String>] search Timezone name only for these locales
136
+ # @param regions [Array<String>] look for timezones only for these regions
137
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
138
+ # @return [Array<String>] list of timezone identifiers
139
+ # @see Locales
140
+ # @see Regions
141
+ def self.getTimezones(timezone, toTime = nil, fromTime = nil, locales = nil, regions = nil, all = true)
142
+ self.new(timezone).setTime(toTime, fromTime).set(locales, regions, all).getTimezones
143
+ end
144
+
145
+ # Get Metazone identifiers for given Timezone name
146
+ # @param timezone [String] Timezone name
147
+ # @param toTime [DateTime] look for timezones which came into effect before this date, exclusive
148
+ # @param fromTime [DateTime] look for timezones which came into effect at this date, inclusive
149
+ # @param locales [Array<String>] search Timezone name only for these locales
150
+ # @param regions [Array<String>] look for timezones only for these regions
151
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
152
+ # @return [Array<String>] list of metazone identifiers
153
+ # @see Locales
154
+ # @see Regions
155
+ def self.getMetazones(timezone, toTime = nil, fromTime = nil, locales = nil, regions = nil, all = true)
156
+ self.new(timezone).setTime(toTime, fromTime).set(locales, regions, all).getMetazones
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ module TimezoneParser
3
+ # Version
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,169 @@
1
+ # encoding: utf-8
2
+
3
+ module TimezoneParser
4
+ # Windows Timezone data
5
+ class WindowsData < Data
6
+ protected
7
+ @WindowsZone = nil
8
+
9
+ public
10
+ attr_reader :WindowsZone
11
+ def processEntry(entry, region)
12
+ @Types += entry['Types'] if entry['Types']
13
+ if entry.has_key?('Metazones')
14
+ entry['Metazones'].each do |zone|
15
+ @WindowsZone = zone
16
+ @Metazones << zone
17
+ @Timezones += Storage.getTimezones2(zone, region)
18
+ @Offsets += Storage.getOffsets(zone, entry['Types'])
19
+ end
20
+ end
21
+ self
22
+ end
23
+ end
24
+
25
+ # Windows Timezone
26
+ class WindowsZone < ZoneInfo
27
+ protected
28
+ @@Locales = []
29
+ @@Regions = []
30
+
31
+ public
32
+ # Locales which will be used for WindowsZone methods if not specified there
33
+ #
34
+ # Each locale consists of language identifier and country/region identifier
35
+ # @return [Array<String>] list containing locale identifiers
36
+ # @see http://msdn.microsoft.com/en-us/library/dd318693.aspx
37
+ def self.Locales
38
+ @@Locales
39
+ end
40
+
41
+ # Regions which will be used for WindowsZone methods if not specified there
42
+ #
43
+ # Each region is either ISO 3166-1 alpha-2 code
44
+ # @return [Array<String>] list containing region identifiers
45
+ # @see http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
46
+ def self.Regions
47
+ @@Regions
48
+ end
49
+
50
+ attr_accessor :Locales
51
+ attr_accessor :Regions
52
+ attr_accessor :All
53
+
54
+ # Windows Timezone instance
55
+ # @param name [String] Windows Timezone name
56
+ def initialize(name)
57
+ @Name = name
58
+ @Data = WindowsData.new
59
+ @Valid = nil
60
+ set(@@Locales.dup, @@Regions.dup, true)
61
+ end
62
+
63
+ # Set locales, regions and all
64
+ # @param locales [Array<String>] search only in these locales
65
+ # @param regions [Array<String>] filter for these regions
66
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
67
+ # @return [WindowsZone] self
68
+ # @see Locales
69
+ # @see Regions
70
+ def set(locales = nil, regions = nil, all = true)
71
+ @Locales = locales unless locales.nil?
72
+ @Regions = regions unless regions.nil?
73
+ @All = all ? true : false
74
+ self
75
+ end
76
+
77
+ # Check if timezone is valid
78
+ # @return [Boolean] whether timezone is valid
79
+ def isValid?
80
+ if @Valid.nil?
81
+ locales = @Locales
82
+ locales = Data::Storage.WindowsZones.keys if locales.empty?
83
+ locales.each do |locale|
84
+ next unless Data::Storage.WindowsZones.has_key?(locale)
85
+ @Valid = Data::Storage.WindowsZones[locale].has_key?(@Name)
86
+ return @Valid if @Valid
87
+ end
88
+ end
89
+ @Valid = false
90
+ end
91
+
92
+ # Windows Timezone data
93
+ # @return [WindowsData] data
94
+ def getData
95
+ unless @Loaded
96
+ @Loaded = true
97
+ @Valid = false
98
+ locales = @Locales
99
+ locales = Data::Storage.WindowsZones.keys if locales.empty?
100
+ locales.each do |locale|
101
+ next unless Data::Storage.WindowsZones.has_key?(locale)
102
+ entry = Data::Storage.WindowsZones[locale][@Name]
103
+ if entry
104
+ @Data.processEntry(entry, @Regions)
105
+ @Valid = true
106
+ return @Data unless @All
107
+ end
108
+ end
109
+ end
110
+ @Data
111
+ end
112
+
113
+ # Windows Timezone identifier
114
+ # @return [String] Timezone identifier
115
+ def getZone
116
+ getData.WindowsZone
117
+ end
118
+
119
+ # Check if given Windows Timezone name is a valid timezone
120
+ # @param name [String] Windows Timezone name
121
+ # @param locales [Array<String>] search Timezone name only for these locales
122
+ # @return [Boolean] whether Timezone is valid
123
+ # @see Locales
124
+ def self.isValid?(name, locales = nil)
125
+ self.new(name).set(locales).isValid?
126
+ end
127
+
128
+ # Get UTC offsets in seconds for given Windows Timezone name
129
+ # @param name [String] Windows Timezone name
130
+ # @param locales [Array<String>] search Timezone name only for these locales
131
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
132
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
133
+ # @see Locales
134
+ def self.getOffsets(name, locales = nil, all = true)
135
+ self.new(name).set(locales, nil, all).getOffsets
136
+ end
137
+
138
+ # Get Timezone identifiers for given Windows Timezone name
139
+ # @param name [String] Windows Timezone name
140
+ # @param locales [Array<String>] search Timezone name only for these locales
141
+ # @param regions [Array<String>] look for timezones only for these regions
142
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
143
+ # @return [Array<String>] list of timezone identifiers
144
+ # @see Locales
145
+ # @see Regions
146
+ def self.getTimezones(name, locales = nil, regions = nil, all = true)
147
+ self.new(name).set(locales, regions, all).getTimezones
148
+ end
149
+
150
+ # Get Metazone identifiers for given Windows Timezone name
151
+ # @param name [String] Windows Timezone name
152
+ # @param locales [Array<String>] search Timezone name only for these locales
153
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
154
+ # @return [Array<String>] list of metazone identifiers
155
+ # @see Locales
156
+ def self.getMetazones(name, locales = nil, all = true)
157
+ self.new(name).set(locales, nil, all).getMetazones
158
+ end
159
+
160
+ # Windows Timezone identifier
161
+ # @param name [String] Windows Timezone name
162
+ # @param locales [Array<String>] search Timezone name only for these locales
163
+ # @param all [Boolean] specify whether should search for all timezones or return as soon as found any
164
+ # @return [String] Timezone identifier
165
+ def self.getZone(name, locales = nil, all = true)
166
+ self.new(name).set(locales, nil, all).getZone
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ module TimezoneParser
4
+ # Generic Timezone class
5
+ class ZoneInfo
6
+ protected
7
+ @Data = nil
8
+ @Loaded = false
9
+ @Offsets = nil
10
+ @Timezones = nil
11
+ @Metazones = nil
12
+
13
+ public
14
+ attr_accessor :ToTime
15
+ attr_accessor :FromTime
16
+ # Set time range
17
+ # @param toTime [DateTime] filter timezones before this date, exclusive
18
+ # @param fromTime [DateTime] filter timezones at this date, inclusive
19
+ # @return [ZoneInfo] self
20
+ def setTime(toTime = nil, fromTime = nil)
21
+ @ToTime = toTime
22
+ @ToTime = DateTime.now unless @ToTime
23
+ @FromTime = fromTime
24
+ @FromTime = DateTime.new(@ToTime.year - 1) unless @FromTime
25
+ self
26
+ end
27
+
28
+ # Get Timezone data
29
+ def getData
30
+ raise StandardError, '#getData must be implemented in subclass'
31
+ end
32
+
33
+ # Get UTC offsets in seconds
34
+ # @return [Array<Fixnum>] list of timezone offsets in seconds
35
+ def getOffsets
36
+ unless @Offsets
37
+ @Offsets = getData.Offsets.to_a
38
+ end
39
+ @Offsets
40
+ end
41
+
42
+ # Get Timezone identifiers
43
+ # @return [Array<String>] list of timezone identifiers
44
+ def getTimezones
45
+ unless @Timezones
46
+ @Timezones = getData.Timezones.to_a
47
+ end
48
+ @Timezones
49
+ end
50
+
51
+ # Get types
52
+ # @return [Symbol] types
53
+ def getTypes
54
+ unless @Types
55
+ @Types = getData.Types.to_a
56
+ end
57
+ @Types
58
+ end
59
+
60
+ # Get Metazone identifiers
61
+ # @return [Array<String>] list of Metazone identifiers
62
+ def getMetazones
63
+ unless @Metazones
64
+ @Metazones = getData.Metazones.to_a
65
+ end
66
+ @Metazones
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,122 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe TimezoneParser do
5
+ describe TimezoneParser::Abbreviation do
6
+ describe '#new' do
7
+ it 'should be an instance of a Abbreviation class' do
8
+ expect(TimezoneParser::Abbreviation.new('CET')).to be_an_instance_of TimezoneParser::Abbreviation
9
+ end
10
+ end
11
+
12
+ describe '#isValid?' do
13
+ it 'should be valid abbreviation' do
14
+ expect(TimezoneParser::Abbreviation.new('CET').isValid?).to be_true
15
+ end
16
+
17
+ it 'should not be valid abbreviation' do
18
+ expect(TimezoneParser::Abbreviation.new('LOL').isValid?).to be_false
19
+ end
20
+
21
+ it 'should be valid case-insensitive abbreviation' do
22
+ expect(TimezoneParser::Abbreviation.new('Pst').isValid?).to be_true
23
+ end
24
+ end
25
+
26
+ describe '#getOffsets' do
27
+ it 'should return offsets for CET abbreviation' do
28
+ expect(TimezoneParser::Abbreviation.new('CET').getOffsets).to eq([3600])
29
+ end
30
+
31
+ it 'should return offsets for WEZ abbreviation' do
32
+ expect(TimezoneParser::Abbreviation.new('WEZ').getOffsets).to eq([0, 3600])
33
+ end
34
+
35
+ context 'before specified time' do
36
+ it 'should return correct offsets for ADT' do
37
+ expect(TimezoneParser::Abbreviation.new('ADT').setTime(DateTime.parse('1982-04-30T21:00:00+00:00')).getOffsets).to eq([-10800])
38
+ expect(TimezoneParser::Abbreviation.new('ADT').setTime(DateTime.parse('1982-04-30T21:00:01+00:00')).getOffsets).to eq([-10800, 14400])
39
+ expect(TimezoneParser::Abbreviation.new('ADT').setTime(DateTime.parse('1982-09-30T19:59:59+00:00')).getOffsets).to eq([-10800, 14400])
40
+ expect(TimezoneParser::Abbreviation.new('ADT').setTime(DateTime.parse('1982-09-30T20:00:00+00:00')).getOffsets).to eq([-10800, 14400])
41
+ expect(TimezoneParser::Abbreviation.new('ADT').setTime(DateTime.parse('1983-03-30T21:00:00+00:00'), DateTime.parse('1982-09-30T20:00:00+00:00')).getOffsets).to eq([-10800])
42
+ end
43
+ end
44
+
45
+ context 'between specified time' do
46
+ it 'should return correct offsets for SAST' do
47
+ expect(TimezoneParser::Abbreviation.new('SAST').setTime(DateTime.parse('1943-09-19T00:00:00+00:00'), DateTime.parse('1943-03-20T23:00:00+00:00')).getOffsets).to eq([7200])
48
+ expect(TimezoneParser::Abbreviation.new('SAST').setTime(DateTime.parse('1943-09-19T00:00:00+00:00'), DateTime.parse('1943-03-20T22:59:59+00:00')).getOffsets).to eq([7200, 10800])
49
+ expect(TimezoneParser::Abbreviation.new('SAST').setTime(DateTime.parse('1943-09-19T00:00:01+00:00'), DateTime.parse('1943-03-20T23:00:00+00:00')).getOffsets).to eq([7200, 10800])
50
+ expect(TimezoneParser::Abbreviation.new('SAST').setTime(DateTime.parse('1943-09-19T00:00:01+00:00'), DateTime.parse('1943-09-19T00:00:00+00:00')).getOffsets).to eq([7200, 10800])
51
+ expect(TimezoneParser::Abbreviation.new('SAST').setTime(DateTime.parse('1944-03-18T23:00:00+00:00'), DateTime.parse('1944-03-18T22:59:59+00:00')).getOffsets).to eq([7200, 10800])
52
+ expect(TimezoneParser::Abbreviation.new('SAST').setTime(DateTime.now, DateTime.parse('1944-03-18T23:00:00+00:00')).getOffsets).to eq([7200])
53
+ end
54
+ end
55
+
56
+ context 'in specified region' do
57
+ it 'should return correct offsets for ADT' do
58
+ expect(TimezoneParser::Abbreviation.new('ADT').set([]).getOffsets).to eq([-10800])
59
+ expect(TimezoneParser::Abbreviation.new('ADT').set(['IQ']).getOffsets).to eq([14400])
60
+ expect(TimezoneParser::Abbreviation.new('ADT').set(['GL']).getOffsets).to eq([-10800])
61
+ expect(TimezoneParser::Abbreviation.new('ADT').setTime(DateTime.parse('2007-10-01T00:00:00+00:00')).set(['IQ', 'CA']).getOffsets).to eq([-10800, 14400])
62
+ end
63
+ end
64
+
65
+ context 'with specified type' do
66
+ it 'should return correct offsets for HAT' do
67
+ expect(TimezoneParser::Abbreviation.new('HAT').setTime(DateTime.parse('2014-02-05T10:00:00+00:00')).set(nil).getOffsets).to eq([-36000, -32400, -12600, -9000])
68
+ expect(TimezoneParser::Abbreviation.new('HAT').setTime(DateTime.parse('2014-04-05T10:00:00+00:00')).set(nil).getOffsets).to eq([-36000, -32400, -12600, -9000])
69
+ expect(TimezoneParser::Abbreviation.new('HAT').setTime(DateTime.parse('2014-02-05T10:00:00+00:00')).set(nil, :standard ).getOffsets).to eq([-36000, -12600])
70
+ expect(TimezoneParser::Abbreviation.new('HAT').setTime(DateTime.parse('2014-02-05T10:00:00+00:00')).set(nil, :daylight ).getOffsets).to eq([-32400, -9000])
71
+ expect(TimezoneParser::Abbreviation.new('HAT').setTime(DateTime.parse('2014-04-05T10:00:00+00:00')).set(nil, :standard ).getOffsets).to eq([-36000, -12600])
72
+ expect(TimezoneParser::Abbreviation.new('HAT').setTime(DateTime.parse('2014-04-05T10:00:00+00:00')).set(nil, :daylight ).getOffsets).to eq([-32400, -9000])
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#getTimezones' do
78
+ it 'should return all timezones for KMT abbreviation' do
79
+ expect(TimezoneParser::Abbreviation.new('KMT').getTimezones).to eq(['Europe/Kiev'])
80
+ end
81
+
82
+ context 'between specified time' do
83
+ it 'should include correct timezones for EET' do
84
+ expect(TimezoneParser::Abbreviation.new('EET').setTime(DateTime.parse('1919-04-15T00:00:00+00:00'), DateTime.parse('1918-09-16T01:00:00+00:00')).getTimezones).to include('Asia/Beirut', 'Europe/Istanbul', 'Europe/Warsaw')
85
+ expect(TimezoneParser::Abbreviation.new('EET').setTime(DateTime.parse('1919-09-16T00:00:00+00:00'), DateTime.parse('1919-04-15T00:00:00+00:00')).getTimezones).to_not include('Europe/Warsaw')
86
+ expect(TimezoneParser::Abbreviation.new('EET').setTime(DateTime.parse('1985-04-19T21:00:00+00:00'), DateTime.parse('1978-10-14T21:00:00+00:00')).getTimezones).to_not include('Europe/Istanbul')
87
+ end
88
+ end
89
+ end
90
+
91
+ describe '#getMetazones' do
92
+ it 'should return all metazones for HAT abbreviation' do
93
+ expect(TimezoneParser::Abbreviation.new('HAT').getMetazones).to eq(['Hawaii_Aleutian', 'Newfoundland'])
94
+ end
95
+ end
96
+
97
+ describe '.isValid?' do
98
+ it 'should be valid abbreviation' do
99
+ expect(TimezoneParser::Abbreviation::isValid?('WGT')).to be_true
100
+ end
101
+ end
102
+
103
+ describe '.getOffsets' do
104
+ it 'should return offsets for WGT abbreviation in GL region' do
105
+ expect(TimezoneParser::Abbreviation::getOffsets('WGT', DateTime.now, nil, ['GL'])).to eq([-10800])
106
+ end
107
+ end
108
+
109
+ describe '.getTimezones' do
110
+ it 'should return timezones for WGT abbreviation' do
111
+ expect(TimezoneParser::Abbreviation::getTimezones('WGT', DateTime.parse('1916-07-28T03:26:56+00:00'), DateTime.parse('1916-07-28T01:14:40+00:00'), ['GL'])).to eq(['America/Danmarkshavn'])
112
+ end
113
+ end
114
+
115
+ describe '.getMetazones' do
116
+ it 'should return metazones for HKT abbreviation' do
117
+ expect(TimezoneParser::Abbreviation::getMetazones('HKT')).to eq(['Hong_Kong'])
118
+ end
119
+ end
120
+
121
+ end
122
+ end