timezone 0.3.6 → 0.3.7

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.
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