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 +10 -0
- data/README.markdown +10 -0
- data/benchmark.rb +44 -0
- data/lib/timezone/configure.rb +13 -0
- data/lib/timezone/loader.rb +50 -0
- data/lib/timezone/lookup/basic.rb +19 -0
- data/lib/timezone/lookup/geonames.rb +36 -0
- data/lib/timezone/lookup/google.rb +36 -0
- data/lib/timezone/lookup.rb +2 -0
- data/lib/timezone/version.rb +1 -1
- data/lib/timezone/zone.rb +88 -71
- data/test/geonames_lookup_test.rb +42 -0
- data/test/google_lookup_test.rb +40 -0
- data/test/http_test_client.rb +14 -0
- data/test/timezone_test.rb +25 -69
- metadata +15 -3
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
|
data/lib/timezone/configure.rb
CHANGED
@@ -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
|
data/lib/timezone/version.rb
CHANGED
data/lib/timezone/zone.rb
CHANGED
@@ -2,9 +2,10 @@ require 'json'
|
|
2
2
|
require 'date'
|
3
3
|
require 'time'
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
151
|
-
|
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
|
-
|
155
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
timestamp = Time.now.to_i
|
170
|
-
lookupUrl = "/maps/api/timezone/json?location=#{lat},#{lon}×tamp=#{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
|
-
|
207
|
+
return from if from == to
|
178
208
|
|
179
|
-
|
180
|
-
data = JSON.parse(response.body)
|
209
|
+
mid = (from + to) / 2
|
181
210
|
|
182
|
-
|
183
|
-
|
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
|
-
|
194
|
-
|
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
|
-
|
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
|
-
|
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
|
data/test/timezone_test.rb
CHANGED
@@ -31,7 +31,7 @@ class TimezoneTest < Test::Unit::TestCase
|
|
31
31
|
assert list.first[:zone] == "Australia/Sydney"
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
212
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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
|
-
|
239
|
-
|
240
|
-
utc = Time.utc(
|
241
|
-
|
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.
|
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-
|
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.
|
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
|