timezone 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.markdown CHANGED
@@ -1,6 +1,16 @@
1
+ # Next
2
+
3
+ * Cache timezone data in memory for performance. (panthomakos)
4
+ * Find timezone rule using binary search for performance. (panthomakos)
5
+
6
+ # 0.3.7
7
+
8
+ * Added `Timezone::Zone#local_to_utc` function. (panthomakos)
9
+
1
10
  # 0.3.6
2
11
 
3
12
  * Added `Timezone::Zone#time_with_offset` functionality. (panthomakos)
13
+ * Fixed `Timezone::Zone#names`. (panthomakos)
4
14
 
5
15
  # 0.3.5
6
16
 
data/README.markdown CHANGED
@@ -43,6 +43,16 @@ historical changes in timezone, which can alter the offset. If you want a time
43
43
  with the appropriate offset at the given time, then use the `time_with_offset`
44
44
  function as shown above.
45
45
 
46
+ You can use the timezone object to convert local times into the best UTC
47
+ estimate. The reason this is an estimate is that some local times do not
48
+ actually map to UTC times (for example when time jumps forward) and some
49
+ local times map to multiple UTC times (for example when time falls back).
50
+
51
+ timezone = Timezone::Zone.new :zone => 'America/Los_Angeles'
52
+
53
+ timezone.local_to_utc(Time.utc(2015,11,1,1,50,0))
54
+ => 2015-11-01 08:50:00 UTC
55
+
46
56
  You can also query a `Timezone::Zone` object to determine if it was in Daylight
47
57
  Savings Time:
48
58
 
data/benchmark.rb ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'benchmark'
4
+ require 'timezone'
5
+
6
+ def load_tz(timezone)
7
+ Timezone::Zone.new(zone: timezone)
8
+ end
9
+
10
+ puts 'Loading timezones'
11
+
12
+ LOAD_ITERATIONS = 1_000
13
+ Benchmark.bm do |x|
14
+ x.report('la'){ LOAD_ITERATIONS.times{ load_tz('America/Los_Angeles') } }
15
+ x.report('hk'){ LOAD_ITERATIONS.times{ load_tz('Asia/Hong_Kong') } }
16
+ end
17
+
18
+ def calc_local(timezone)
19
+ timezone.time(Time.utc(3000,1,1))
20
+ end
21
+
22
+ puts 'Calculating LOCAL'
23
+
24
+ LOCAL_ITERATIONS = 10_000
25
+ Benchmark.bm do |x|
26
+ timezone = Timezone::Zone.new(zone: 'America/Los_Angeles')
27
+ x.report('la'){ LOCAL_ITERATIONS.times{ calc_local(timezone) } }
28
+ timezone = Timezone::Zone.new(zone: 'Asia/Hong_Kong')
29
+ x.report('hk'){ LOCAL_ITERATIONS.times{ calc_local(timezone) } }
30
+ end
31
+
32
+ def calc_utc(timezone)
33
+ timezone.local_to_utc(Time.utc(3000,1,1))
34
+ end
35
+
36
+ puts 'Calculating UTC'
37
+
38
+ UTC_ITERATIONS = 10_000
39
+ Benchmark.bm do |x|
40
+ timezone = Timezone::Zone.new(zone: 'America/Los_Angeles')
41
+ x.report('la'){ UTC_ITERATIONS.times{ calc_utc(timezone) } }
42
+ timezone = Timezone::Zone.new(zone: 'Asia/Hong_Kong')
43
+ x.report('hk'){ UTC_ITERATIONS.times{ calc_utc(timezone) } }
44
+ end
@@ -1,4 +1,5 @@
1
1
  require 'timezone/net_http_client'
2
+ require 'timezone/lookup'
2
3
 
3
4
  module Timezone
4
5
  # Configuration class for the Timezone gem.
@@ -46,6 +47,18 @@ module Timezone
46
47
  !!google_api_key
47
48
  end
48
49
 
50
+ def self.lookup
51
+ use_google? ? google_lookup : geonames_lookup
52
+ end
53
+
54
+ def self.google_lookup
55
+ @google_lookup ||= Timezone::Lookup::Google.new(self)
56
+ end
57
+
58
+ def self.geonames_lookup
59
+ @geonames_lookup ||= Timezone::Lookup::Geonames.new(self)
60
+ end
61
+
49
62
  # The Geonames API URL
50
63
  #
51
64
  # @return [String]
@@ -0,0 +1,50 @@
1
+ require 'timezone/error'
2
+
3
+ module Timezone
4
+ module Loader
5
+ ZONE_FILE_PATH = File.expand_path(File.dirname(__FILE__)+'/../../data')
6
+ SOURCE_BIT = 0
7
+
8
+ class << self
9
+ def load(zone)
10
+ @rules ||= {}
11
+ @rules[zone] ||= parse_zone_data(get_zone_data(zone))
12
+ end
13
+
14
+ def names
15
+ @@names ||= Dir[File.join(ZONE_FILE_PATH, "**/*")].map{ |file|
16
+ next if File.directory?(file)
17
+ file.gsub("#{ZONE_FILE_PATH}/", '')
18
+ }.compact
19
+ end
20
+
21
+ private
22
+
23
+ def parse_zone_data(data)
24
+ rules = []
25
+
26
+ data.split("\n").each do |line|
27
+ source, name, dst, offset = line.split(':')
28
+ source = source.to_i
29
+ dst = dst == '1'
30
+ offset = offset.to_i
31
+ source = rules.last[SOURCE_BIT]+source if rules.last
32
+ rules << [source, name, dst, offset]
33
+ end
34
+
35
+ rules
36
+ end
37
+
38
+ # Retrieve the data from a particular time zone
39
+ def get_zone_data(zone)
40
+ file = File.join(ZONE_FILE_PATH, zone)
41
+
42
+ if !File.exists?(file)
43
+ raise Timezone::Error::InvalidZone, "'#{zone}' is not a valid zone."
44
+ end
45
+
46
+ File.read(file)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ module Timezone
2
+ module Lookup
3
+ class Basic
4
+ attr_reader :config
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ def client
11
+ @client ||= config.http_client.new(config.protocol, config.url)
12
+ end
13
+
14
+ def lookup(lat, lng)
15
+ raise NoMethodError, 'lookup is not implemented'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ require 'timezone/lookup/basic'
2
+ require 'timezone/error'
3
+ require 'json'
4
+ require 'uri'
5
+
6
+ module Timezone
7
+ module Lookup
8
+ class Geonames < ::Timezone::Lookup::Basic
9
+ def lookup(lat, lng)
10
+ response = client.get(url(lat, lng))
11
+
12
+ return unless response.code =~ /^2\d\d$/
13
+
14
+ data = JSON.parse(response.body)
15
+
16
+ if data['status'] && data['status']['value'] == 18
17
+ raise(Timezone::Error::GeoNames, 'api limit reached')
18
+ end
19
+
20
+ data['timezoneId']
21
+ rescue => e
22
+ raise(Timezone::Error::GeoNames, e.message)
23
+ end
24
+
25
+ private
26
+
27
+ def url(lat, lng)
28
+ query = URI.encode_www_form(
29
+ 'lat' => lat,
30
+ 'lng' => lng,
31
+ 'username' => config.username)
32
+ "/timezoneJSON?#{query}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ require 'timezone/lookup/basic'
2
+ require 'timezone/error'
3
+ require 'json'
4
+ require 'uri'
5
+
6
+ module Timezone
7
+ module Lookup
8
+ class Google < ::Timezone::Lookup::Basic
9
+ def lookup(lat,lng)
10
+ response = client.get(url(lat,lng))
11
+
12
+ return unless response.code =~ /^2\d\d$/
13
+ data = JSON.parse(response.body)
14
+
15
+ if data['status'] != 'OK'
16
+ raise(Timezone::Error::Google, data['errorMessage'])
17
+ end
18
+
19
+ data['timeZoneId']
20
+ rescue => e
21
+ raise(Timezone::Error::Google, e.message)
22
+ end
23
+
24
+ private
25
+
26
+ def url(lat,lng)
27
+ query = URI.encode_www_form(
28
+ 'location' => "#{lat},#{lng}",
29
+ 'timestamp' => Time.now.to_i,
30
+ 'key' => config.google_api_key)
31
+
32
+ "/maps/api/timezone/json?#{query}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,2 @@
1
+ require 'timezone/lookup/geonames'
2
+ require 'timezone/lookup/google'
@@ -1,3 +1,3 @@
1
1
  module Timezone
2
- VERSION = "0.3.6"
2
+ VERSION = "0.3.7"
3
3
  end
data/lib/timezone/zone.rb CHANGED
@@ -2,9 +2,10 @@ require 'json'
2
2
  require 'date'
3
3
  require 'time'
4
4
 
5
- require File.expand_path(File.dirname(__FILE__) + '/error')
6
- require File.expand_path(File.dirname(__FILE__) + '/configure')
7
- require File.expand_path(File.dirname(__FILE__) + '/active_support')
5
+ require 'timezone/loader'
6
+ require 'timezone/error'
7
+ require 'timezone/configure'
8
+ require 'timezone/active_support'
8
9
 
9
10
  module Timezone
10
11
  class Zone
@@ -16,8 +17,6 @@ module Timezone
16
17
  DST_BIT = 2
17
18
  OFFSET_BIT = 3
18
19
 
19
- ZONE_FILE_PATH = File.expand_path(File.dirname(__FILE__)+'/../../data')
20
-
21
20
  # Create a new Timezone object.
22
21
  #
23
22
  # Timezone.new(options)
@@ -38,20 +37,8 @@ module Timezone
38
37
 
39
38
  raise Timezone::Error::NilZone, 'No zone was found. Please specify a zone.' if options[:zone].nil?
40
39
 
41
- data = get_zone_data(options[:zone])
42
-
43
- @rules = []
44
-
45
- data.split("\n").each do |line|
46
- source, name, dst, offset = line.split(':')
47
- source = source.to_i
48
- dst = dst == '1'
49
- offset = offset.to_i
50
- source = @rules.last[SOURCE_BIT]+source if @rules.last
51
- @rules << [source, name, dst, offset]
52
- end
53
-
54
40
  @zone = options[:zone]
41
+ @rules = Timezone::Loader.load(@zone)
55
42
  end
56
43
 
57
44
  def active_support_time_zone
@@ -71,6 +58,20 @@ module Timezone
71
58
  reference.utc + utc_offset(reference)
72
59
  end
73
60
 
61
+ alias :utc_to_local :time
62
+
63
+ # Determine the UTC time for a given time in the timezone.
64
+ #
65
+ # timezone.local_to_utc(time)
66
+ #
67
+ # The UTC equivalent is a "best guess". There are cases where local times do not map to UTC
68
+ # at all (during a time skip forward). There are also cases where local times map to two
69
+ # separate UTC times (during a fall back). All of these cases are ignored here and the best
70
+ # (first) guess is used instead.
71
+ def local_to_utc(time)
72
+ time.utc - rule_for_local(time).rules.first[OFFSET_BIT]
73
+ end
74
+
74
75
  # Determine the time in the timezone w/ the appropriate offset.
75
76
  #
76
77
  # timezone.time_with_offset(reference)
@@ -90,14 +91,14 @@ module Timezone
90
91
 
91
92
  # Whether or not the time in the timezone is in DST.
92
93
  def dst?(reference)
93
- rule_for_reference(reference)[DST_BIT]
94
+ rule_for_utc(reference)[DST_BIT]
94
95
  end
95
96
 
96
97
  # Get the current UTC offset in seconds for this timezone.
97
98
  #
98
99
  # timezone.utc_offset(reference)
99
100
  def utc_offset(reference=Time.now)
100
- rule_for_reference(reference)[OFFSET_BIT]
101
+ rule_for_utc(reference)[OFFSET_BIT]
101
102
  end
102
103
 
103
104
  def <=>(zone) #:nodoc:
@@ -107,9 +108,7 @@ module Timezone
107
108
  class << self
108
109
  # Instantly grab all possible time zone names.
109
110
  def names
110
- @@names ||= Dir[File.join(ZONE_FILE_PATH, "**/**/*")].collect do |file|
111
- file.gsub("#{ZONE_FILE_PATH}/", '')
112
- end
111
+ Timezone::Loader.names
113
112
  end
114
113
 
115
114
  # Get a list of specified timezones and the basic information accompanying that zone
@@ -141,71 +140,89 @@ module Timezone
141
140
  end
142
141
  end
143
142
 
144
- private
145
-
146
- # Retrieve the data from a particular time zone
147
- def get_zone_data(zone)
148
- file = File.join(ZONE_FILE_PATH, zone)
143
+ private
149
144
 
150
- if !File.exists?(file)
151
- raise Timezone::Error::InvalidZone, "'#{zone}' is not a valid zone."
145
+ # Does the given time (in seconds) match this rule?
146
+ #
147
+ # Each rule has a SOURCE bit which is the number of seconds, since the
148
+ # Epoch, up to which the rule is valid.
149
+ def match?(seconds, rule) #:nodoc:
150
+ seconds <= rule[SOURCE_BIT]
152
151
  end
153
152
 
154
- File.read(file)
155
- end
153
+ RuleSet = Struct.new(:type, :rules)
154
+
155
+ def rule_for_local(local)
156
+ local = local.utc if local.respond_to?(:utc)
157
+ local = local.to_i
158
+
159
+ # For each rule, convert the local time into the UTC equivalent for
160
+ # that rule offset, and then check if the UTC time matches the rule.
161
+ index = binary_search(local){ |t,r| match?(t-r[OFFSET_BIT], r) }
162
+ match = @rules[index]
156
163
 
157
- def rule_for_reference reference
158
- reference = reference.utc.to_i
159
- @rules.each do |rule|
160
- return rule if reference <= rule[SOURCE_BIT]
164
+ utc = local-match[OFFSET_BIT]
165
+
166
+ # If the UTC rule for the calculated UTC time does not map back to the
167
+ # same rule, then we have a skip in time and there is no applicable rule.
168
+ return RuleSet.new(:missing, [match]) if rule_for_utc(utc) != match
169
+
170
+ # If the match is the last rule, then return it.
171
+ return RuleSet.new(:single, [match]) if index == @rules.length-1
172
+
173
+ # If the UTC equivalent time falls within the last hour(s) of the time
174
+ # change which were replayed during a fall-back in time, then return
175
+ # the matched rule and the next one.
176
+ #
177
+ # Example:
178
+ #
179
+ # rules = [
180
+ # [ 8:00 UTC, -1 ], # UTC-1 up to and including 8:00 UTC
181
+ # [ 14:00 UTC, -2 ], # UTC-2 up to and including 14:00 UTC
182
+ # ]
183
+ #
184
+ # 6:50 local (7:50 UTC) by the first rule
185
+ # 6:50 local (8:50 UTC) by the second rule
186
+ #
187
+ # Since both rules provide valid mappings for the local time,
188
+ # we need to return both values.
189
+ if utc > match[SOURCE_BIT] - match[OFFSET_BIT] + @rules[index+1][OFFSET_BIT]
190
+ RuleSet.new(:double, @rules[index..(index+1)])
191
+ else
192
+ RuleSet.new(:single, [match])
161
193
  end
194
+ end
162
195
 
163
- @rules.last if !@rules.empty? && reference >= @rules.last[SOURCE_BIT]
196
+ def rule_for_utc(time) #:nodoc:
197
+ time = time.utc if time.respond_to?(:utc)
198
+ time = time.to_i
199
+
200
+ return @rules[binary_search(time){ |t,r| match?(t,r) }]
164
201
  end
165
202
 
166
- def timezone_id lat, lon #:nodoc:
167
- begin
168
- if Timezone::Configure.use_google?
169
- timestamp = Time.now.to_i
170
- lookupUrl = "/maps/api/timezone/json?location=#{lat},#{lon}&timestamp=#{timestamp}&key=#{Timezone::Configure.google_api_key}"
171
- timezoneId = 'timeZoneId' # uppercase 'Z'
172
- else
173
- lookupUrl = "/timezoneJSON?lat=#{lat}&lng=#{lon}&username=#{Timezone::Configure.username}"
174
- timezoneId = 'timezoneId' # lowercase 'z'
175
- end
203
+ # Find the first rule that matches using binary search.
204
+ def binary_search(time, from=0, to=nil, &block)
205
+ to = @rules.length-1 if to.nil?
176
206
 
177
- response = http_client.get(lookupUrl)
207
+ return from if from == to
178
208
 
179
- if response.code =~ /^2\d\d$/
180
- data = JSON.parse(response.body)
209
+ mid = (from + to) / 2
181
210
 
182
- # check response
183
- if Timezone::Configure.use_google?
184
- if data['status'] != 'OK'
185
- raise Timezone::Error::Google, data['errorMessage']
186
- end
187
- else
188
- if data['status'] && data['status']['value'] == 18
189
- raise Timezone::Error::GeoNames, 'api limit reached'
190
- end
191
- end
211
+ if block.call(time, @rules[mid])
212
+ return mid if mid == 0
192
213
 
193
- return data[timezoneId]
194
- end
195
- rescue => e
196
- if Timezone::Configure.use_google?
197
- raise Timezone::Error::Google, e.message
214
+ if !block.call(time, @rules[mid-1])
215
+ return mid
198
216
  else
199
- raise Timezone::Error::GeoNames, e.message
217
+ return binary_search(time, from, mid-1, &block)
200
218
  end
219
+ else
220
+ return binary_search(time, mid + 1, to, &block)
201
221
  end
202
222
  end
203
223
 
204
- private
205
-
206
- def http_client #:nodoc:
207
- @http_client ||= Timezone::Configure.http_client.new(
208
- Timezone::Configure.protocol, Timezone::Configure.url)
224
+ def timezone_id lat, lon #:nodoc:
225
+ Timezone::Configure.lookup.lookup(lat,lon)
209
226
  end
210
227
  end
211
228
  end
@@ -0,0 +1,42 @@
1
+ require 'timezone/configure'
2
+ require 'timezone/lookup/geonames'
3
+ require 'test/unit'
4
+ require_relative 'http_test_client'
5
+
6
+ class GeonamesLookupTest < ::Test::Unit::TestCase
7
+ def setup
8
+ Timezone::Configure.begin do |c|
9
+ c.google_api_key = nil
10
+ c.http_client = HTTPTestClient
11
+ c.username = 'timezone'
12
+ end
13
+ end
14
+
15
+ def coordinates
16
+ [-34.92771808058, 138.477041423321]
17
+ end
18
+
19
+ def lookup
20
+ ::Timezone::Lookup::Geonames.new(Timezone::Configure)
21
+ end
22
+
23
+ def test_lookup
24
+ HTTPTestClient.body = File.open(mock_path + '/lat_lon_coords.txt').read
25
+
26
+ assert_equal 'Australia/Adelaide', lookup.lookup(*coordinates)
27
+ end
28
+
29
+ def test_api_limit
30
+ HTTPTestClient.body = File.open(mock_path + '/api_limit_reached.txt').read
31
+
32
+ assert_raise Timezone::Error::GeoNames, 'api limit reached' do
33
+ lookup.lookup(*coordinates)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def mock_path
40
+ File.expand_path(File.join(File.dirname(__FILE__), 'mocks'))
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ require 'timezone/configure'
2
+ require 'timezone/lookup/google'
3
+ require 'test/unit'
4
+ require_relative 'http_test_client'
5
+
6
+ class GoogleLookupTest < ::Test::Unit::TestCase
7
+ def setup
8
+ Timezone::Configure.begin do |c|
9
+ c.google_api_key = nil
10
+ c.http_client = HTTPTestClient
11
+ c.google_api_key = '123abc'
12
+ end
13
+ end
14
+
15
+ def coordinates
16
+ [-34.92771808058, 138.477041423321]
17
+ end
18
+
19
+ def lookup
20
+ ::Timezone::Lookup::Google.new(Timezone::Configure)
21
+ end
22
+
23
+ def test_google_using_lat_lon_coordinates
24
+ HTTPTestClient.body = File.open(mock_path + '/google_lat_lon_coords.txt').read
25
+
26
+ assert_equal 'Australia/Adelaide', lookup.lookup(*coordinates)
27
+ end
28
+
29
+ def test_google_request_denied_read_lat_lon_coordinates
30
+ assert_raise Timezone::Error::Google, 'The provided API key is invalid.' do
31
+ lookup.lookup(*coordinates)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def mock_path
38
+ File.expand_path(File.join(File.dirname(__FILE__), 'mocks'))
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ class HTTPTestClient
2
+ class << self ; attr_accessor :body ; end
3
+
4
+ Response = Struct.new(:body) do
5
+ def code ; '200' ; end
6
+ end
7
+
8
+ def initialize(protocol, host)
9
+ end
10
+
11
+ def get(url)
12
+ HTTPTestClient::Response.new(self.class.body)
13
+ end
14
+ end
@@ -31,7 +31,7 @@ class TimezoneTest < Test::Unit::TestCase
31
31
  assert list.first[:zone] == "Australia/Sydney"
32
32
  end
33
33
 
34
- def test_timezone_list
34
+ def test_timezone_list_current_time
35
35
  Timecop.freeze(Time.new(2012,2,2,0,0,0)) do
36
36
  assert !Timezone::Zone.list('EST5EDT').first[:dst]
37
37
  end
@@ -166,80 +166,35 @@ class TimezoneTest < Test::Unit::TestCase
166
166
  assert_equal local.to_s, zone.time_with_offset(utc).to_s
167
167
  end
168
168
 
169
- class HTTPTestClient
170
- class << self ; attr_accessor :body ; end
171
-
172
- Response = Struct.new(:body) do
173
- def code ; '200' ; end
174
- end
175
-
176
- def initialize(protocol, host)
177
- end
178
-
179
- def get(url)
180
- HTTPTestClient::Response.new(self.class.body)
181
- end
182
- end
183
-
184
- def test_geonames_using_lat_lon_coordinates
185
- mock_path = File.expand_path(File.join(File.dirname(__FILE__), 'mocks'))
186
- HTTPTestClient.body = File.open(mock_path + '/lat_lon_coords.txt').read
187
-
188
- Timezone::Configure.begin do |c|
189
- c.http_client = HTTPTestClient
190
- c.username = 'timezone'
191
- end
192
-
193
- timezone = Timezone::Zone.new :latlon => [-34.92771808058, 138.477041423321]
194
- assert_equal 'Australia/Adelaide', timezone.zone
195
- end
196
-
197
- def test_api_limit_read_lat_lon_coordinates
198
- mock_path = File.expand_path(File.join(File.dirname(__FILE__), 'mocks'))
199
- HTTPTestClient.body = File.open(mock_path + '/api_limit_reached.txt').read
200
-
201
- Timezone::Configure.begin do |c|
202
- c.http_client = HTTPTestClient
203
- c.username = 'timezone'
204
- end
205
-
206
- assert_raise Timezone::Error::GeoNames, 'api limit reached' do
207
- Timezone::Zone.new :latlon => [-34.92771808058, 138.477041423321]
208
- end
169
+ def test_australian_timezone_with_dst
170
+ timezone = Timezone::Zone.new :zone => 'Australia/Adelaide'
171
+ utc = Time.utc(2010, 12, 23, 19, 37, 15)
172
+ local = Time.utc(2010, 12, 24, 6, 7, 15)
173
+ assert_equal local.to_i, timezone.time(utc).to_i
209
174
  end
210
175
 
211
- def test_google_using_lat_lon_coordinates
212
- mock_path = File.expand_path(File.join(File.dirname(__FILE__), 'mocks'))
213
- HTTPTestClient.body = File.open(mock_path + '/google_lat_lon_coords.txt').read
214
-
215
- Timezone::Configure.begin do |c|
216
- c.http_client = HTTPTestClient
217
- c.google_api_key = '123abc'
218
- end
219
-
220
- timezone = Timezone::Zone.new :latlon => [-34.92771808058, 138.477041423321]
221
- assert_equal 'Australia/Adelaide', timezone.zone
222
- end
176
+ def test_local_to_utc
177
+ timezone = Timezone::Zone.new(:zone => 'America/Los_Angeles')
223
178
 
224
- def test_google_request_denied_read_lat_lon_coordinates
225
- mock_path = File.expand_path(File.join(File.dirname(__FILE__), 'mocks'))
226
- HTTPTestClient.body = File.open(mock_path + '/google_request_denied.txt').read
179
+ # Time maps to two rules - we pick the first
180
+ local = Time.utc(2015,11,1,1,50,0)
181
+ utc = Time.utc(2015,11,1,8,50,0)
182
+ assert_equal(utc.to_s, timezone.local_to_utc(local).to_s)
227
183
 
228
- Timezone::Configure.begin do |c|
229
- c.http_client = HTTPTestClient
230
- c.google_api_key = 'invalid-api-key'
231
- end
184
+ # Time is above the maximum - we pick the last rule
185
+ local = Time.utc(3000,1,1,0,0,0)
186
+ utc = Time.utc(3000,1,1,8,0,0)
187
+ assert_equal(utc.to_s, timezone.local_to_utc(local).to_s)
232
188
 
233
- assert_raise Timezone::Error::Google, 'The provided API key is invalid.' do
234
- Timezone::Zone.new :latlon => [-34.92771808058, 138.477041423321]
235
- end
236
- end
189
+ # Time maps to a single rule - we pick that rule
190
+ local = Time.utc(2015,11,1,0,1,0)
191
+ utc = Time.utc(2015,11,1,7,1,0)
192
+ assert_equal(utc.to_s, timezone.local_to_utc(local).to_s)
237
193
 
238
- def test_australian_timezone_with_dst
239
- timezone = Timezone::Zone.new :zone => 'Australia/Adelaide'
240
- utc = Time.utc(2010, 12, 23, 19, 37, 15)
241
- local = Time.utc(2010, 12, 24, 6, 7, 15)
242
- assert_equal local.to_i, timezone.time(utc).to_i
194
+ # Time is missing - we pick the first closest rule
195
+ local = Time.utc(2015,3,8,2,50,0)
196
+ utc = Time.utc(2015,3,8,9,50,0)
197
+ assert_equal(utc.to_s, timezone.local_to_utc(local).to_s)
243
198
  end
244
199
 
245
200
  def test_configure_url_default
@@ -247,6 +202,7 @@ class TimezoneTest < Test::Unit::TestCase
247
202
  end
248
203
 
249
204
  def test_configure_url_custom
205
+ Timezone::Configure.begin { |c| c.google_api_key = nil }
250
206
  Timezone::Configure.begin { |c| c.geonames_url = 'www.newtimezoneserver.com' }
251
207
  assert_equal 'www.newtimezoneserver.com', Timezone::Configure.url
252
208
  # clean up url after test
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timezone
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.3.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-12-16 00:00:00.000000000 Z
12
+ date: 2014-12-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -97,6 +97,7 @@ files:
97
97
  - License.txt
98
98
  - README.markdown
99
99
  - Rakefile
100
+ - benchmark.rb
100
101
  - data/Africa/Abidjan
101
102
  - data/Africa/Accra
102
103
  - data/Africa/Addis_Ababa
@@ -684,12 +685,20 @@ files:
684
685
  - lib/timezone/active_support.rb
685
686
  - lib/timezone/configure.rb
686
687
  - lib/timezone/error.rb
688
+ - lib/timezone/loader.rb
689
+ - lib/timezone/lookup.rb
690
+ - lib/timezone/lookup/basic.rb
691
+ - lib/timezone/lookup/geonames.rb
692
+ - lib/timezone/lookup/google.rb
687
693
  - lib/timezone/net_http_client.rb
688
694
  - lib/timezone/parser.rb
689
695
  - lib/timezone/version.rb
690
696
  - lib/timezone/zone.rb
691
697
  - test/data/Helsinki_rules_without_timestamps.json
692
698
  - test/data/asia
699
+ - test/geonames_lookup_test.rb
700
+ - test/google_lookup_test.rb
701
+ - test/http_test_client.rb
693
702
  - test/mocks/api_limit_reached.txt
694
703
  - test/mocks/google_lat_lon_coords.txt
695
704
  - test/mocks/google_request_denied.txt
@@ -721,10 +730,13 @@ rubyforge_project: timezone
721
730
  rubygems_version: 1.8.23.2
722
731
  signing_key:
723
732
  specification_version: 3
724
- summary: timezone-0.3.6
733
+ summary: timezone-0.3.7
725
734
  test_files:
726
735
  - test/data/Helsinki_rules_without_timestamps.json
727
736
  - test/data/asia
737
+ - test/geonames_lookup_test.rb
738
+ - test/google_lookup_test.rb
739
+ - test/http_test_client.rb
728
740
  - test/mocks/api_limit_reached.txt
729
741
  - test/mocks/google_lat_lon_coords.txt
730
742
  - test/mocks/google_request_denied.txt