allscripts_unity_client 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/.travis.yml +3 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +180 -0
- data/Rakefile +7 -0
- data/allscripts_unity_client.gemspec +39 -0
- data/lib/allscripts_unity_client.rb +43 -0
- data/lib/allscripts_unity_client/client.rb +594 -0
- data/lib/allscripts_unity_client/client_driver.rb +95 -0
- data/lib/allscripts_unity_client/json_client_driver.rb +110 -0
- data/lib/allscripts_unity_client/json_unity_request.rb +33 -0
- data/lib/allscripts_unity_client/json_unity_response.rb +27 -0
- data/lib/allscripts_unity_client/soap_client_driver.rb +128 -0
- data/lib/allscripts_unity_client/timezone.rb +99 -0
- data/lib/allscripts_unity_client/unity_request.rb +63 -0
- data/lib/allscripts_unity_client/unity_response.rb +110 -0
- data/lib/allscripts_unity_client/utilities.rb +66 -0
- data/lib/allscripts_unity_client/version.rb +3 -0
- data/spec/allscripts_unity_client_spec.rb +57 -0
- data/spec/client_driver_spec.rb +71 -0
- data/spec/client_spec.rb +406 -0
- data/spec/factories/allscripts_unity_client_parameters_factory.rb +13 -0
- data/spec/factories/client_driver_factory.rb +14 -0
- data/spec/factories/client_factory.rb +7 -0
- data/spec/factories/json_client_driver_factory.rb +3 -0
- data/spec/factories/json_unity_request_factory.rb +3 -0
- data/spec/factories/json_unity_response_factory.rb +3 -0
- data/spec/factories/magic_request_factory.rb +33 -0
- data/spec/factories/soap_client_driver_factory.rb +3 -0
- data/spec/factories/timezone_factory.rb +7 -0
- data/spec/factories/unity_request_factory.rb +10 -0
- data/spec/factories/unity_response_factory.rb +8 -0
- data/spec/fixtures/attributes_hash.yml +15 -0
- data/spec/fixtures/date_hash.yml +8 -0
- data/spec/fixtures/date_string_hash.yml +8 -0
- data/spec/fixtures/error.json +3 -0
- data/spec/fixtures/get_providers.json +69 -0
- data/spec/fixtures/get_providers.xml +119 -0
- data/spec/fixtures/get_providers_json.yml +65 -0
- data/spec/fixtures/get_providers_xml.yml +270 -0
- data/spec/fixtures/get_security_token.json +1 -0
- data/spec/fixtures/get_security_token.xml +7 -0
- data/spec/fixtures/get_server_info.json +10 -0
- data/spec/fixtures/get_server_info.xml +40 -0
- data/spec/fixtures/get_server_info_json.yml +8 -0
- data/spec/fixtures/get_server_info_xml.yml +55 -0
- data/spec/fixtures/no_attributes_hash.yml +7 -0
- data/spec/fixtures/retire_security_token.json +1 -0
- data/spec/fixtures/retire_security_token.xml +5 -0
- data/spec/fixtures/soap_fault.xml +13 -0
- data/spec/fixtures/string_keyed_hash.yml +8 -0
- data/spec/fixtures/symbol_keyed_hash.yml +8 -0
- data/spec/json_client_driver_spec.rb +209 -0
- data/spec/json_unity_request_spec.rb +37 -0
- data/spec/json_unity_response_spec.rb +44 -0
- data/spec/soap_client_driver_spec.rb +201 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/fixture_loader.rb +22 -0
- data/spec/support/shared_examples_for_client_driver.rb +139 -0
- data/spec/support/shared_examples_for_unity_request.rb +94 -0
- data/spec/support/shared_examples_for_unity_response.rb +26 -0
- data/spec/timezone_spec.rb +161 -0
- data/spec/unity_request_spec.rb +37 -0
- data/spec/unity_response_spec.rb +36 -0
- data/spec/utilities_spec.rb +69 -0
- metadata +323 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module AllscriptsUnityClient
|
4
|
+
class ClientDriver
|
5
|
+
LOG_FILE = "logs/unity_client.log"
|
6
|
+
|
7
|
+
attr_accessor :username, :password, :appname, :base_unity_url, :proxy, :security_token, :timezone, :logger, :log
|
8
|
+
|
9
|
+
def initialize(base_unity_url, username, password, appname, proxy = nil, timezone = nil, logger = nil, log = true)
|
10
|
+
raise ArgumentError, "base_unity_url can not be nil" if base_unity_url.nil?
|
11
|
+
raise ArgumentError, "username can not be nil" if username.nil?
|
12
|
+
raise ArgumentError, "password can not be nil" if password.nil?
|
13
|
+
raise ArgumentError, "appname can not be nil" if appname.nil?
|
14
|
+
|
15
|
+
@base_unity_url = base_unity_url.gsub /\/$/, ""
|
16
|
+
@username = username
|
17
|
+
@password = password
|
18
|
+
@appname = appname
|
19
|
+
@proxy = proxy
|
20
|
+
@log = log
|
21
|
+
|
22
|
+
if logger.nil?
|
23
|
+
@logger = Logger.new(STDOUT)
|
24
|
+
@logger.level = Logger::INFO
|
25
|
+
else
|
26
|
+
@logger = logger
|
27
|
+
end
|
28
|
+
|
29
|
+
unless timezone.nil?
|
30
|
+
@timezone = Timezone.new(timezone)
|
31
|
+
else
|
32
|
+
@timezone = Timezone.new("UTC")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def security_token?
|
37
|
+
return !@security_token.nil?
|
38
|
+
end
|
39
|
+
|
40
|
+
def log?
|
41
|
+
return @log
|
42
|
+
end
|
43
|
+
|
44
|
+
def client_type
|
45
|
+
return :none
|
46
|
+
end
|
47
|
+
|
48
|
+
def magic(parameters = {})
|
49
|
+
raise NotImplementedError, "magic not implemented"
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_security_token!(parameters = {})
|
53
|
+
raise NotImplementedError, "get_security_token! not implemented"
|
54
|
+
end
|
55
|
+
|
56
|
+
def retire_security_token!(parameters = {})
|
57
|
+
raise NotImplementedError, "retire_security_token! not implemented"
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def log_get_security_token
|
63
|
+
message = "Unity API GetSecurityToken request to #{@base_unity_url}"
|
64
|
+
log_info(message)
|
65
|
+
end
|
66
|
+
|
67
|
+
def log_retire_security_token
|
68
|
+
message = "Unity API RetireSecurityToken request to #{@base_unity_url}"
|
69
|
+
log_info(message)
|
70
|
+
end
|
71
|
+
|
72
|
+
def log_magic(request)
|
73
|
+
raise ArgumentError, "request can not be nil" if request.nil?
|
74
|
+
message = "Unity API Magic request to #{@base_unity_url} [#{request.parameters[:action]}]"
|
75
|
+
log_info(message)
|
76
|
+
end
|
77
|
+
|
78
|
+
def log_info(message)
|
79
|
+
if log? && !logger.nil? && !message.nil?
|
80
|
+
message += " #{@timer} seconds" unless @timer.nil?
|
81
|
+
@timer = nil
|
82
|
+
logger.info(message)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def start_timer
|
87
|
+
@start_time = Time.now.utc
|
88
|
+
end
|
89
|
+
|
90
|
+
def end_timer
|
91
|
+
@end_time = Time.now.utc
|
92
|
+
@timer = @end_time - @start_time
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "json"
|
2
|
+
require "httpi"
|
3
|
+
|
4
|
+
module AllscriptsUnityClient
|
5
|
+
class JSONClientDriver < ClientDriver
|
6
|
+
attr_accessor :json_base_url
|
7
|
+
|
8
|
+
UNITY_JSON_ENDPOINT = "/Unity/UnityService.svc/json"
|
9
|
+
|
10
|
+
def initialize(base_unity_url, username, password, appname, proxy = nil, timezone = nil, logger = nil, log = true)
|
11
|
+
super
|
12
|
+
|
13
|
+
# Disable HTTPI logging
|
14
|
+
HTTPI.log = false
|
15
|
+
|
16
|
+
@json_base_url = "#{@base_unity_url}#{UNITY_JSON_ENDPOINT}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def client_type
|
20
|
+
return :json
|
21
|
+
end
|
22
|
+
|
23
|
+
def magic(parameters = {})
|
24
|
+
request_data = JSONUnityRequest.new(parameters, @timezone, @appname, @security_token)
|
25
|
+
request = create_httpi_request("#{@json_base_url}/MagicJson", request_data.to_hash)
|
26
|
+
|
27
|
+
start_timer
|
28
|
+
response = HTTPI.post(request)
|
29
|
+
end_timer
|
30
|
+
|
31
|
+
response = JSON.parse(response.body)
|
32
|
+
|
33
|
+
raise_if_response_error(response)
|
34
|
+
log_magic(request_data)
|
35
|
+
|
36
|
+
response = JSONUnityResponse.new(response, @timezone)
|
37
|
+
response.to_hash
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_security_token!(parameters = {})
|
41
|
+
username = parameters[:username] || @username
|
42
|
+
password = parameters[:password] || @password
|
43
|
+
appname = parameters[:appname] || @appname
|
44
|
+
|
45
|
+
request_data = {
|
46
|
+
"Username" => username,
|
47
|
+
"Password" => password,
|
48
|
+
"Appname" => appname
|
49
|
+
}
|
50
|
+
request = create_httpi_request("#{@json_base_url}/GetToken", request_data)
|
51
|
+
|
52
|
+
start_timer
|
53
|
+
response = HTTPI.post(request, :net_http_persistent)
|
54
|
+
end_timer
|
55
|
+
|
56
|
+
raise_if_response_error(response.body)
|
57
|
+
log_get_security_token
|
58
|
+
|
59
|
+
@security_token = response.body
|
60
|
+
end
|
61
|
+
|
62
|
+
def retire_security_token!(parameters = {})
|
63
|
+
token = parameters[:token] || @security_token
|
64
|
+
appname = parameters[:appname] || @appname
|
65
|
+
|
66
|
+
request_data = {
|
67
|
+
"Token" => token,
|
68
|
+
"Appname" => appname
|
69
|
+
}
|
70
|
+
request = create_httpi_request("#{@json_base_url}/RetireSecurityToken", request_data)
|
71
|
+
|
72
|
+
start_timer
|
73
|
+
response = HTTPI.post(request, :net_http_persistent)
|
74
|
+
end_timer
|
75
|
+
|
76
|
+
raise_if_response_error(response.body)
|
77
|
+
log_retire_security_token
|
78
|
+
|
79
|
+
@security_token = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def create_httpi_request(url, data)
|
85
|
+
request = HTTPI::Request.new
|
86
|
+
request.url = url
|
87
|
+
request.headers = {
|
88
|
+
"Accept-Encoding" => "gzip,deflate",
|
89
|
+
"Content-type" => "application/json;charset=utf-8"
|
90
|
+
}
|
91
|
+
request.body = JSON.generate(data)
|
92
|
+
|
93
|
+
unless @proxy.nil?
|
94
|
+
request.proxy = @proxy
|
95
|
+
end
|
96
|
+
|
97
|
+
request
|
98
|
+
end
|
99
|
+
|
100
|
+
def raise_if_response_error(response)
|
101
|
+
if response.nil?
|
102
|
+
raise APIError, "Response was empty"
|
103
|
+
elsif response.is_a?(Array) && !response[0]["Error"].nil?
|
104
|
+
raise APIError, response[0]["Error"]
|
105
|
+
elsif response.is_a?(String) && response.include?("error:")
|
106
|
+
raise APIError, response
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AllscriptsUnityClient
|
2
|
+
class JSONUnityRequest < UnityRequest
|
3
|
+
def to_hash
|
4
|
+
action = @parameters[:action]
|
5
|
+
userid = @parameters[:userid]
|
6
|
+
appname = @parameters[:appname] || @appname
|
7
|
+
patientid = @parameters[:patientid]
|
8
|
+
token = @parameters[:token] || @security_token
|
9
|
+
parameter1 = process_date(@parameters[:parameter1]) || ""
|
10
|
+
parameter2 = process_date(@parameters[:parameter2]) || ""
|
11
|
+
parameter3 = process_date(@parameters[:parameter3]) || ""
|
12
|
+
parameter4 = process_date(@parameters[:parameter4]) || ""
|
13
|
+
parameter5 = process_date(@parameters[:parameter5]) || ""
|
14
|
+
parameter6 = process_date(@parameters[:parameter6]) || ""
|
15
|
+
data = Utilities::encode_data(@parameters[:data]) || ""
|
16
|
+
|
17
|
+
return {
|
18
|
+
"Action" => action,
|
19
|
+
"AppUserID" => userid,
|
20
|
+
"Appname" => appname,
|
21
|
+
"PatientID" => patientid,
|
22
|
+
"Token" => token,
|
23
|
+
"Parameter1" => parameter1,
|
24
|
+
"Parameter2" => parameter2,
|
25
|
+
"Parameter3" => parameter3,
|
26
|
+
"Parameter4" => parameter4,
|
27
|
+
"Parameter5" => parameter5,
|
28
|
+
"Parameter6" => parameter6,
|
29
|
+
"Data" => data
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module AllscriptsUnityClient
|
4
|
+
class JSONUnityResponse < UnityResponse
|
5
|
+
def to_hash
|
6
|
+
result = @response
|
7
|
+
|
8
|
+
# All JSON magic responses are an array with one item
|
9
|
+
result = result.first
|
10
|
+
|
11
|
+
# All JSON magic results contain one key on their object named
|
12
|
+
# actioninfo
|
13
|
+
result = result.values.first
|
14
|
+
|
15
|
+
# The data in a JSON magic result is always an array. If that array
|
16
|
+
# only has a single item, then just return that as the result. This is
|
17
|
+
# a compromise as some actions that should always return arrays
|
18
|
+
# (i.e. GetProviders) may return a single hash.
|
19
|
+
if result.count == 1
|
20
|
+
result = result.first
|
21
|
+
end
|
22
|
+
|
23
|
+
result = convert_dates_to_utc(result)
|
24
|
+
Utilities::recursively_symbolize_keys(result)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "savon"
|
2
|
+
|
3
|
+
module AllscriptsUnityClient
|
4
|
+
class SOAPClientDriver < ClientDriver
|
5
|
+
attr_accessor :savon_client
|
6
|
+
|
7
|
+
UNITY_SOAP_ENDPOINT = "/Unity/UnityService.svc/unityservice"
|
8
|
+
UNITY_ENDPOINT_NAMESPACE = "http://www.allscripts.com/Unity/IUnityService"
|
9
|
+
|
10
|
+
def initialize(base_unity_url, username, password, appname, proxy = nil, timezone = nil, logger = nil, log = true)
|
11
|
+
super
|
12
|
+
|
13
|
+
client_proxy = @proxy
|
14
|
+
base_unity_url = "#{@base_unity_url}#{UNITY_SOAP_ENDPOINT}"
|
15
|
+
|
16
|
+
@savon_client = Savon.client do
|
17
|
+
# Removes the wsdl: namespace from body elements in the SOAP
|
18
|
+
# request. Unity doesn't recognize elements otherwise.
|
19
|
+
namespace_identifier nil
|
20
|
+
|
21
|
+
# Manually registers SOAP endpoint since Unity WSDL is not very
|
22
|
+
# good.
|
23
|
+
endpoint base_unity_url
|
24
|
+
|
25
|
+
# Manually register SOAP namespace. This URL isn't live, but the
|
26
|
+
# internal SOAP endpoints expect it.
|
27
|
+
namespace "http://www.allscripts.com/Unity"
|
28
|
+
|
29
|
+
# Register proxy with Savon if one was given.
|
30
|
+
unless client_proxy.nil?
|
31
|
+
proxy client_proxy
|
32
|
+
end
|
33
|
+
|
34
|
+
# Unity expects the SOAP envelop to be namespaced with soap:
|
35
|
+
env_namespace :soap
|
36
|
+
|
37
|
+
# Unity uses Microsoft style CamelCase for keys. Only really useful when using
|
38
|
+
# symbol keyed hashes.
|
39
|
+
convert_request_keys_to :camelcase
|
40
|
+
|
41
|
+
# Enable gzip on HTTP responses. Unity does not currently support this
|
42
|
+
# as of Born On 10/7/2013, but it doesn't hurt to future-proof. If gzip
|
43
|
+
# is ever enabled, this library will get a speed bump for free.
|
44
|
+
headers({ "Accept-Encoding" => "gzip,deflate" })
|
45
|
+
|
46
|
+
# Disable Savon logs
|
47
|
+
log false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def client_type
|
52
|
+
return :soap
|
53
|
+
end
|
54
|
+
|
55
|
+
def magic(parameters = {})
|
56
|
+
request_data = UnityRequest.new(parameters, @timezone, @appname, @security_token)
|
57
|
+
call_data = {
|
58
|
+
:message => request_data.to_hash,
|
59
|
+
:soap_action => "#{UNITY_ENDPOINT_NAMESPACE}/Magic"
|
60
|
+
}
|
61
|
+
|
62
|
+
begin
|
63
|
+
start_timer
|
64
|
+
response = @savon_client.call("Magic", call_data)
|
65
|
+
end_timer
|
66
|
+
rescue Savon::SOAPFault => e
|
67
|
+
raise APIError, e.message
|
68
|
+
end
|
69
|
+
|
70
|
+
log_magic(request_data)
|
71
|
+
|
72
|
+
response = UnityResponse.new(response.body, @timezone)
|
73
|
+
response.to_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_security_token!(parameters = {})
|
77
|
+
username = parameters[:username] || @username
|
78
|
+
password = parameters[:password] || @password
|
79
|
+
appname = parameters[:appname] || @appname
|
80
|
+
|
81
|
+
call_data = {
|
82
|
+
:message => {
|
83
|
+
"Username" => username,
|
84
|
+
"Password" => password,
|
85
|
+
"Appname" => appname
|
86
|
+
},
|
87
|
+
:soap_action => "#{UNITY_ENDPOINT_NAMESPACE}/GetSecurityToken"
|
88
|
+
}
|
89
|
+
|
90
|
+
begin
|
91
|
+
start_timer
|
92
|
+
response = @savon_client.call("GetSecurityToken", call_data)
|
93
|
+
end_timer
|
94
|
+
rescue Savon::SOAPFault => e
|
95
|
+
raise APIError, e.message
|
96
|
+
end
|
97
|
+
|
98
|
+
log_get_security_token
|
99
|
+
|
100
|
+
@security_token = response.body[:get_security_token_response][:get_security_token_result]
|
101
|
+
end
|
102
|
+
|
103
|
+
def retire_security_token!(parameters = {})
|
104
|
+
token = parameters[:token] || @security_token
|
105
|
+
appname = parameters[:appname] || @appname
|
106
|
+
|
107
|
+
call_data = {
|
108
|
+
:message => {
|
109
|
+
"Token" => token,
|
110
|
+
"Appname" => appname
|
111
|
+
},
|
112
|
+
:soap_action => "#{UNITY_ENDPOINT_NAMESPACE}/RetireSecurityToken"
|
113
|
+
}
|
114
|
+
|
115
|
+
begin
|
116
|
+
start_timer
|
117
|
+
@savon_client.call("RetireSecurityToken", call_data)
|
118
|
+
end_timer
|
119
|
+
rescue Savon::SOAPFault => e
|
120
|
+
raise APIError, e.message
|
121
|
+
end
|
122
|
+
|
123
|
+
log_retire_security_token
|
124
|
+
|
125
|
+
@security_token = nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "date"
|
2
|
+
require "tzinfo"
|
3
|
+
|
4
|
+
module AllscriptsUnityClient
|
5
|
+
class Timezone
|
6
|
+
attr_accessor :tzinfo
|
7
|
+
|
8
|
+
def initialize(zone_identifier)
|
9
|
+
raise ArgumentError, "zone_identifier can not be nil" if zone_identifier.nil?
|
10
|
+
|
11
|
+
@tzinfo = TZInfo::Timezone.get(zone_identifier)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Use TZInfo to convert a given UTC datetime into
|
15
|
+
# a local
|
16
|
+
def local_to_utc(datetime)
|
17
|
+
convert_with_timezone(:local_to_utc, datetime)
|
18
|
+
end
|
19
|
+
|
20
|
+
def utc_to_local(datetime = nil)
|
21
|
+
convert_with_timezone(:utc_to_local, datetime)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Direction can be :utc_to_local or :local_to_utc
|
27
|
+
def convert_with_timezone(direction, datetime = nil)
|
28
|
+
if datetime.nil?
|
29
|
+
return nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# Dates have no time information and so timezone conversion
|
33
|
+
# doesn't make sense. Just return the date in this case.
|
34
|
+
if datetime.instance_of?(Date)
|
35
|
+
return datetime
|
36
|
+
end
|
37
|
+
|
38
|
+
if datetime.instance_of?(String)
|
39
|
+
datetime = DateTime.parse(datetime.to_s)
|
40
|
+
end
|
41
|
+
|
42
|
+
is_datetime = datetime.instance_of?(DateTime)
|
43
|
+
|
44
|
+
if direction == :local_to_utc
|
45
|
+
if is_datetime
|
46
|
+
# DateTime can do UTC conversions reliably, so use that instead of
|
47
|
+
# TZInfo
|
48
|
+
datetime = datetime.new_offset(0)
|
49
|
+
else
|
50
|
+
datetime = @tzinfo.local_to_utc(datetime)
|
51
|
+
end
|
52
|
+
|
53
|
+
if is_datetime
|
54
|
+
# Convert to a DateTime with a UTC offset
|
55
|
+
datetime = DateTime.parse("#{datetime.strftime("%FT%T")}Z")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if direction == :utc_to_local
|
60
|
+
datetime = @tzinfo.utc_to_local(datetime)
|
61
|
+
|
62
|
+
if is_datetime
|
63
|
+
# Convert to a DateTime with the correct timezone offset
|
64
|
+
datetime = DateTime.parse(iso8601_with_offset(datetime))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
return datetime
|
69
|
+
end
|
70
|
+
|
71
|
+
# TZInfo does not correctly update a DateTime's
|
72
|
+
# offset, so we manually format a ISO8601 with
|
73
|
+
# the correct format
|
74
|
+
def iso8601_with_offset(datetime)
|
75
|
+
if datetime.nil?
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
|
79
|
+
offset = @tzinfo.current_period.utc_offset
|
80
|
+
negative_offset = false
|
81
|
+
datetime_string = datetime.strftime("%FT%T")
|
82
|
+
|
83
|
+
if offset < 0
|
84
|
+
offset *= -1
|
85
|
+
negative_offset = true
|
86
|
+
end
|
87
|
+
|
88
|
+
if offset == 0
|
89
|
+
offset_string = "Z"
|
90
|
+
else
|
91
|
+
offset_string = Time.at(offset).utc.strftime("%H:%M")
|
92
|
+
offset_string = "-" + offset_string if negative_offset
|
93
|
+
offset_string = "+" + offset_string unless negative_offset
|
94
|
+
end
|
95
|
+
|
96
|
+
"#{datetime_string}#{offset_string}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|