ofx 0.3.2 → 0.3.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c152721af7763671b8e2c6f912f85e3e3a0b7f44
4
- data.tar.gz: 8e5f68901a6215bf71079cf67ff756c50630a7fb
2
+ SHA256:
3
+ metadata.gz: f010daf75c910a87b19c737c27a0970a4a410f5e5b6ba5916f4546e2bb5f9abb
4
+ data.tar.gz: abf6b0090855e5c063ce2d627e8f2b4907be64a71289cb520d4ae4bf7687371e
5
5
  SHA512:
6
- metadata.gz: 4eb56b361cf81ec4b091509fec2272a335514fe697fc7dd060d18e8bafa7a255cf2a55f4e0ecd0438fe3303aee2178ca591510d07767365ea310000e01c61d2c
7
- data.tar.gz: 3629da082dc5e93a97220e378ac8e8811db2b5f651594ecfe887cce923455325e23981845cc4b82ac434b330d4c01b1c5550e01dbc7db2c03de94d4d73bfc69a
6
+ metadata.gz: f3ccb6719848c32e3047243715a3ec2aabbc80b2f241bfd2ff7dcbda819bf1baeb3086ee751459a28643345d4272bf4b4481dd56ba727f9ebf94ea6a680d4cf0
7
+ data.tar.gz: 48076b4428d20fa368544099691ebe1bf877b724b49cfce83265545a84b62b07e45759e00ae0febaf254090d53c157a1f9b7b6340f5c9d59237aa1847e590ca5
data/README.rdoc CHANGED
@@ -1,5 +1,8 @@
1
1
  = OFX
2
2
 
3
+ {<img src="https://badge.fury.io/rb/ofx.png" alt="Gem Version" />}[http://badge.fury.io/rb/ofx]
4
+ {<img src="https://travis-ci.org/annacruz/ofx.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/annacruz/ofx]
5
+
3
6
  A simple OFX (Open Financial Exchange) parser built on top of Nokogiri. Currently supports both OFX 1.0.2 and 2.1.1.
4
7
 
5
8
  Works on both ruby 1.9 and 2.0.
@@ -14,6 +17,8 @@ Works on both ruby 1.9 and 2.0.
14
17
  p account.transactions
15
18
  end
16
19
 
20
+ Invalid files will raise an OFX::UnsupportedFileError.
21
+
17
22
  == Creator
18
23
 
19
24
  * Nando Vieira - http://simplesideias.com.br
@@ -1,20 +1,31 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OFX
2
4
  module Parser
3
5
  class OFX102
4
- VERSION = "1.0.2"
6
+ VERSION = '1.0.2'
5
7
 
6
8
  ACCOUNT_TYPES = {
7
- "CHECKING" => :checking
8
- }
9
-
10
- TRANSACTION_TYPES = [
11
- 'ATM', 'CASH', 'CHECK', 'CREDIT', 'DEBIT', 'DEP', 'DIRECTDEBIT', 'DIRECTDEP', 'DIV',
12
- 'FEE', 'INT', 'OTHER', 'PAYMENT', 'POS', 'REPEATPMT', 'SRVCHG', 'XFER'
13
- ].inject({}) { |hash, tran_type| hash[tran_type] = tran_type.downcase.to_sym; hash }
14
-
15
- attr_reader :headers
16
- attr_reader :body
17
- attr_reader :html
9
+ 'CHECKING' => :checking,
10
+ 'SAVINGS' => :savings,
11
+ 'CREDITLINE' => :creditline,
12
+ 'MONEYMRKT' => :moneymrkt
13
+ }.freeze
14
+
15
+ TRANSACTION_TYPES = %w[
16
+ ATM CASH CHECK CREDIT DEBIT DEP DIRECTDEBIT DIRECTDEP DIV
17
+ FEE INT OTHER PAYMENT POS REPEATPMT SRVCHG XFER
18
+ ].each_with_object({}) do |tran_type, hash|
19
+ hash[tran_type] = tran_type.downcase.to_sym
20
+ end
21
+
22
+ SEVERITY = {
23
+ 'INFO' => :info,
24
+ 'WARN' => :warn,
25
+ 'ERROR' => :error
26
+ }.freeza
27
+
28
+ attr_reader :headers, :body, :html
18
29
 
19
30
  def initialize(options = {})
20
31
  @headers = options[:headers]
@@ -22,8 +33,17 @@ module OFX
22
33
  @html = Nokogiri::HTML.parse(body)
23
34
  end
24
35
 
36
+ def statements
37
+ @statements ||= html.search('stmttrnrs, ccstmttrnrs').collect { |node| build_statement(node) }
38
+ end
39
+
40
+ def accounts
41
+ @accounts ||= html.search('stmttrnrs, ccstmttrnrs').collect { |node| build_account(node) }
42
+ end
43
+
44
+ # DEPRECATED: kept for legacy support
25
45
  def account
26
- @account ||= build_account
46
+ @account ||= build_account(html.search('stmttrnrs, ccstmttrnrs').first)
27
47
  end
28
48
 
29
49
  def sign_on
@@ -32,106 +52,153 @@ module OFX
32
52
 
33
53
  def self.parse_headers(header_text)
34
54
  # Change single CR's to LF's to avoid issues with some banks
35
- header_text.gsub!(/\r(?!\n)/, "\n")
55
+ header_text.gsub!(/\r(?!\n)/, '\n')
36
56
 
37
57
  # Parse headers. When value is NONE, convert it to nil.
38
- headers = header_text.to_enum(:each_line).inject({}) do |memo, line|
58
+ headers = header_text.to_enum(:each_line).each_with_object({}) do |line, memo|
39
59
  _, key, value = *line.match(/^(.*?):(.*?)\s*(\r?\n)*$/)
40
60
 
41
61
  unless key.nil?
42
- memo[key] = value == "NONE" ? nil : value
62
+ memo[key] = value == 'NONE' ? nil : value
43
63
  end
44
-
45
- memo
46
64
  end
47
65
 
48
66
  return headers unless headers.empty?
49
67
  end
50
68
 
51
69
  private
52
- def build_account
70
+
71
+ def build_statement(node)
72
+ stmrs_node = node.search('stmtrs, ccstmtrs')
73
+ account = build_account(node)
74
+ OFX::Statement.new(
75
+ currency: stmrs_node.search('curdef').inner_text,
76
+ start_date: build_date(stmrs_node.search('banktranlist > dtstart').inner_text),
77
+ end_date: build_date(stmrs_node.search('banktranlist > dtend').inner_text),
78
+ account: account,
79
+ transactions: account.transactions,
80
+ balance: account.balance,
81
+ available_balance: account.available_balance
82
+ )
83
+ end
84
+
85
+ def build_account(node)
53
86
  OFX::Account.new({
54
- :bank_id => html.search("bankacctfrom > bankid").inner_text,
55
- :id => html.search("bankacctfrom > acctid, ccacctfrom > acctid").inner_text,
56
- :type => ACCOUNT_TYPES[html.search("bankacctfrom > accttype").inner_text.to_s.upcase],
57
- :transactions => build_transactions,
58
- :balance => build_balance,
59
- :available_balance => build_available_balance,
60
- :currency => html.search("bankmsgsrsv1 > stmttrnrs > stmtrs > curdef, " +
61
- "creditcardmsgsrsv1 > ccstmttrnrs > ccstmtrs > curdef").inner_text
62
- })
87
+ bank_id: node.search('bankacctfrom > bankid').inner_text,
88
+ id: node.search('bankacctfrom > acctid, ccacctfrom > acctid').inner_text,
89
+ type: ACCOUNT_TYPES[node.search('bankacctfrom > accttype').inner_text.to_s.upcase],
90
+ transactions: build_transactions(node),
91
+ balance: build_balance(node),
92
+ available_balance: build_available_balance(node),
93
+ currency: node.search('stmtrs > curdef, ccstmtrs > curdef').inner_text
94
+ })
95
+ end
96
+
97
+ def build_status(node)
98
+ OFX::Status.new({
99
+ code: node.search('code').inner_text.to_i,
100
+ severity: SEVERITY[node.search('severity').inner_text],
101
+ message: node.search('message').inner_text
102
+ })
63
103
  end
64
104
 
65
105
  def build_sign_on
66
106
  OFX::SignOn.new({
67
- :language => html.search("signonmsgsrsv1 > sonrs > language").inner_text,
68
- :fi_id => html.search("signonmsgsrsv1 > sonrs > fi > fid").inner_text,
69
- :fi_name => html.search("signonmsgsrsv1 > sonrs > fi > org").inner_text
70
- })
107
+ language: html.search('signonmsgsrsv1 > sonrs > language').inner_text,
108
+ fi_id: html.search('signonmsgsrsv1 > sonrs > fi > fid').inner_text,
109
+ fi_name: html.search('signonmsgsrsv1 > sonrs > fi > org').inner_text,
110
+ status: build_status(html.search('signonmsgsrsv1 > sonrs > status'))
111
+ })
71
112
  end
72
113
 
73
- def build_transactions
74
- html.search("banktranlist > stmttrn").collect do |element|
114
+ def build_transactions(node)
115
+ node.search('banktranlist > stmttrn').collect do |element|
75
116
  build_transaction(element)
76
117
  end
77
118
  end
78
119
 
79
120
  def build_transaction(element)
121
+ occurred_at = begin
122
+ build_date(element.search('dtuser').inner_text)
123
+ rescue StandardError
124
+ nil
125
+ end
126
+
80
127
  OFX::Transaction.new({
81
- :amount => build_amount(element),
82
- :amount_in_pennies => (build_amount(element) * 100).to_i,
83
- :fit_id => element.search("fitid").inner_text,
84
- :memo => element.search("memo").inner_text,
85
- :name => element.search("name").inner_text,
86
- :payee => element.search("payee").inner_text,
87
- :check_number => element.search("checknum").inner_text,
88
- :ref_number => element.search("refnum").inner_text,
89
- :posted_at => build_date(element.search("dtposted").inner_text),
90
- :type => build_type(element),
91
- :sic => element.search("sic").inner_text
92
- })
128
+ amount: build_amount(element),
129
+ amount_in_pennies: (build_amount(element) * 100).to_i,
130
+ fit_id: element.search('fitid').inner_text,
131
+ memo: element.search('memo').inner_text,
132
+ name: element.search('name').inner_text,
133
+ payee: element.search('payee').inner_text,
134
+ check_number: element.search('checknum').inner_text,
135
+ ref_number: element.search('refnum').inner_text,
136
+ posted_at: build_date(element.search('dtposted').inner_text),
137
+ occurred_at:,
138
+ type: build_type(element),
139
+ sic: element.search('sic').inner_text
140
+ })
93
141
  end
94
142
 
95
143
  def build_type(element)
96
- TRANSACTION_TYPES[element.search("trntype").inner_text.to_s.upcase]
144
+ TRANSACTION_TYPES[element.search('trntype').inner_text.to_s.upcase]
97
145
  end
98
146
 
99
147
  def build_amount(element)
100
- BigDecimal.new(element.search("trnamt").inner_text)
148
+ to_decimal(element.search('trnamt').inner_text)
101
149
  end
102
150
 
151
+ # Input format is `YYYYMMDDHHMMSS.XXX[gmt offset[:tz name]]`
103
152
  def build_date(date)
104
- _, year, month, day, hour, minutes, seconds = *date.match(/(\d{4})(\d{2})(\d{2})(?:(\d{2})(\d{2})(\d{2}))?/)
153
+ tz_pattern = /(?:\[([+-]?\d{1,4}):\S{3}\])?\z/
154
+
155
+ # Timezone offset handling
156
+ date.sub!(tz_pattern, '')
157
+ offset = Regexp.last_match(1)
158
+
159
+ if offset
160
+ # Offset padding
161
+ _, hours, mins = *offset.match(/\A([+-]?\d{1,2})(\d{0,2})?\z/)
162
+ offset = format('%+03d%02d', hours.to_i, mins.to_i)
163
+ else
164
+ offset = '+0000'
165
+ end
105
166
 
106
- date = "#{year}-#{month}-#{day} "
107
- date << "#{hour}:#{minutes}:#{seconds}" if hour && minutes && seconds
167
+ date << ' #{offset}'
108
168
 
109
169
  Time.parse(date)
110
170
  end
111
171
 
112
- def build_balance
113
- amount = html.search("ledgerbal > balamt").inner_text.to_f
172
+ def build_balance(node)
173
+ amount = to_decimal(node.search('ledgerbal > balamt').inner_text)
174
+ posted_at = begin
175
+ build_date(node.search('ledgerbal > dtasof').inner_text)
176
+ rescue StandardError
177
+ nil
178
+ end
114
179
 
115
180
  OFX::Balance.new({
116
- :amount => amount,
117
- :amount_in_pennies => (amount * 100).to_i,
118
- :posted_at => build_date(html.search("ledgerbal > dtasof").inner_text)
119
- })
181
+ amount:,
182
+ amount_in_pennies: (amount * 100).to_i,
183
+ posted_at:
184
+ })
120
185
  end
121
186
 
122
- def build_available_balance
123
- if html.search("availbal").size > 0
124
- amount = html.search("availbal > balamt").inner_text.to_f
187
+ def build_available_balance(node)
188
+ if node.search('availbal').size > 0
189
+ amount = to_decimal(node.search('availbal > balamt').inner_text)
125
190
 
126
191
  OFX::Balance.new({
127
- :amount => amount,
128
- :amount_in_pennies => (amount * 100).to_i,
129
- :posted_at => build_date(html.search("availbal > dtasof").inner_text)
130
- })
131
- else
132
- return nil
192
+ amount:,
193
+ amount_in_pennies: (amount * 100).to_i,
194
+ posted_at: build_date(node.search('availbal > dtasof').inner_text)
195
+ })
133
196
  end
134
197
  end
198
+
199
+ def to_decimal(amount)
200
+ BigDecimal(amount.to_s.gsub(',', '.'))
201
+ end
135
202
  end
136
203
  end
137
204
  end
@@ -0,0 +1,7 @@
1
+ module OFX
2
+ module Parser
3
+ class OFX103 < OFX102
4
+ VERSION = '1.0.3'
5
+ end
6
+ end
7
+ end
data/lib/ofx/parser.rb CHANGED
@@ -12,14 +12,16 @@ module OFX
12
12
  begin
13
13
  @content = convert_to_utf8(resource.read)
14
14
  @headers, @body = prepare(content)
15
- rescue Exception
15
+ rescue
16
16
  raise OFX::UnsupportedFileError
17
17
  end
18
18
 
19
19
  case headers["VERSION"]
20
20
  when /102/ then
21
21
  @parser = OFX102.new(:headers => headers, :body => body)
22
- when /200|211/ then
22
+ when /103/ then
23
+ @parser = OFX103.new(:headers => headers, :body => body)
24
+ when /200|202|211|220/ then
23
25
  @parser = OFX211.new(:headers => headers, :body => body)
24
26
  else
25
27
  raise OFX::UnsupportedFileError
@@ -32,7 +34,7 @@ module OFX
32
34
  else
33
35
  open(resource)
34
36
  end
35
- rescue Exception
37
+ rescue
36
38
  StringIO.new(resource)
37
39
  end
38
40
 
data/lib/ofx/sign_on.rb CHANGED
@@ -3,5 +3,6 @@ module OFX
3
3
  attr_accessor :language
4
4
  attr_accessor :fi_id
5
5
  attr_accessor :fi_name
6
+ attr_accessor :status
6
7
  end
7
8
  end
@@ -0,0 +1,11 @@
1
+ module OFX
2
+ class Statement < Foundation
3
+ attr_accessor :account
4
+ attr_accessor :available_balance
5
+ attr_accessor :balance
6
+ attr_accessor :currency
7
+ attr_accessor :start_date
8
+ attr_accessor :end_date
9
+ attr_accessor :transactions
10
+ end
11
+ end
data/lib/ofx/status.rb ADDED
@@ -0,0 +1,12 @@
1
+ module OFX
2
+ # Error Reporting Aggregate
3
+ class Status < Foundation
4
+ attr_accessor :code # Error code
5
+ attr_accessor :severity # Severity of the error
6
+ attr_accessor :message # Textual explanation
7
+
8
+ def success?
9
+ code == 0
10
+ end
11
+ end
12
+ end
@@ -8,6 +8,7 @@ module OFX
8
8
  attr_accessor :name
9
9
  attr_accessor :payee
10
10
  attr_accessor :posted_at
11
+ attr_accessor :occurred_at
11
12
  attr_accessor :ref_number
12
13
  attr_accessor :type
13
14
  attr_accessor :sic
data/lib/ofx/version.rb CHANGED
@@ -2,7 +2,7 @@ module OFX
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 3
5
- PATCH = 2
5
+ PATCH = 4
6
6
  STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
7
  end
8
8
  end
data/lib/ofx.rb CHANGED
@@ -1,19 +1,24 @@
1
- require "open-uri"
2
- require "nokogiri"
3
- require "bigdecimal"
1
+ # frozen_string_literal: true
4
2
 
5
- require "kconv"
3
+ require 'open-uri'
4
+ require 'nokogiri'
5
+ require 'bigdecimal'
6
6
 
7
- require "ofx/errors"
8
- require "ofx/parser"
9
- require "ofx/parser/ofx102"
10
- require "ofx/parser/ofx211"
11
- require "ofx/foundation"
12
- require "ofx/balance"
13
- require "ofx/account"
14
- require "ofx/sign_on"
15
- require "ofx/transaction"
16
- require "ofx/version"
7
+ require 'kconv'
8
+
9
+ require 'ofx/errors'
10
+ require 'ofx/parser'
11
+ require 'ofx/parser/ofx102'
12
+ require 'ofx/parser/ofx103'
13
+ require 'ofx/parser/ofx211'
14
+ require 'ofx/foundation'
15
+ require 'ofx/balance'
16
+ require 'ofx/account'
17
+ require 'ofx/sign_on'
18
+ require 'ofx/status'
19
+ require 'ofx/statement'
20
+ require 'ofx/transaction'
21
+ require 'ofx/version'
17
22
 
18
23
  def OFX(resource, &block)
19
24
  parser = OFX::Parser::Base.new(resource).parser
@@ -11,49 +11,49 @@ describe OFX::Account do
11
11
  it "should return currency" do
12
12
  @account.currency.should == "BRL"
13
13
  end
14
-
14
+
15
15
  it "should return bank id" do
16
16
  @account.bank_id.should == "0356"
17
17
  end
18
-
18
+
19
19
  it "should return id" do
20
20
  @account.id.should == "03227113109"
21
21
  end
22
-
22
+
23
23
  it "should return type" do
24
24
  @account.type.should == :checking
25
25
  end
26
-
26
+
27
27
  it "should return transactions" do
28
28
  @account.transactions.should be_a_kind_of(Array)
29
29
  @account.transactions.size.should == 36
30
30
  end
31
-
31
+
32
32
  it "should return balance" do
33
- @account.balance.amount.should == 598.44
33
+ @account.balance.amount.should == BigDecimal('598.44')
34
34
  end
35
-
35
+
36
36
  it "should return balance in pennies" do
37
37
  @account.balance.amount_in_pennies.should == 59844
38
38
  end
39
-
39
+
40
40
  it "should return balance date" do
41
- @account.balance.posted_at.should == Time.parse("2009-11-01")
41
+ @account.balance.posted_at.should == Time.gm(2009,11,1)
42
42
  end
43
-
43
+
44
44
  context "available_balance" do
45
45
  it "should return available balance" do
46
- @account.available_balance.amount.should == 1555.99
46
+ @account.available_balance.amount.should == BigDecimal('1555.99')
47
47
  end
48
-
48
+
49
49
  it "should return available balance in pennies" do
50
50
  @account.available_balance.amount_in_pennies.should == 155599
51
51
  end
52
-
52
+
53
53
  it "should return available balance date" do
54
- @account.available_balance.posted_at.should == Time.parse("2009-11-01")
54
+ @account.available_balance.posted_at.should == Time.gm(2009,11,1)
55
55
  end
56
-
56
+
57
57
  it "should return nil if AVAILBAL not found" do
58
58
  @ofx = OFX::Parser::Base.new("spec/fixtures/utf8.ofx")
59
59
  @parser = @ofx.parser
@@ -61,7 +61,7 @@ describe OFX::Account do
61
61
  @account.available_balance.should be_nil
62
62
  end
63
63
  end
64
-
64
+
65
65
  context "Credit Card" do
66
66
  before do
67
67
  @ofx = OFX::Parser::Base.new("spec/fixtures/creditcard.ofx")
@@ -72,10 +72,60 @@ describe OFX::Account do
72
72
  it "should return id" do
73
73
  @account.id.should == "XXXXXXXXXXXX1111"
74
74
  end
75
-
75
+
76
76
  it "should return currency" do
77
77
  @account.currency.should == "USD"
78
78
  end
79
79
  end
80
+ context "With Issue" do # Bradesco do not provide a valid date in balance
81
+ before do
82
+ @ofx = OFX::Parser::Base.new("spec/fixtures/dtsof_balance_issue.ofx")
83
+ @parser = @ofx.parser
84
+ @account = @parser.account
85
+ end
86
+
87
+ it "should return nil for date balance" do
88
+ @account.balance.posted_at.should be_nil
89
+ end
90
+ end
91
+
92
+ context "Invalid Dates" do
93
+ before do
94
+ @ofx = OFX::Parser::Base.new("spec/fixtures/bradesco.ofx")
95
+ @parser = @ofx.parser
96
+ end
97
+ it "should not raise error when balance has date zero" do
98
+ expect { @parser.account.balance }.to_not raise_error
99
+ end
100
+ it "should return NIL in balance.posted_at when balance date is zero" do
101
+ @parser.account.balance.posted_at.should be_nil
102
+ end
103
+ end
104
+
105
+ context "decimal values using a comma" do
106
+ before do
107
+ @ofx = OFX::Parser::Base.new("spec/fixtures/santander.ofx")
108
+ @parser = @ofx.parser
109
+ @account = @parser.account
110
+ end
111
+
112
+ it "should return balance" do
113
+ @account.balance.amount.should == BigDecimal('348.29')
114
+ end
115
+
116
+ it "should return balance in pennies" do
117
+ @account.balance.amount_in_pennies.should == 34829
118
+ end
119
+
120
+ context "available_balance" do
121
+ it "should return available balance" do
122
+ @account.available_balance.amount.should == BigDecimal('2415.87')
123
+ end
124
+
125
+ it "should return available balance in pennies" do
126
+ @account.available_balance.amount_in_pennies.should == 241587
127
+ end
128
+ end
129
+ end
80
130
  end
81
131
  end
@@ -5,11 +5,11 @@ describe OFX::Parser::OFX102 do
5
5
  @ofx = OFX::Parser::Base.new("spec/fixtures/sample.ofx")
6
6
  @parser = @ofx.parser
7
7
  end
8
-
8
+
9
9
  it "should have a version" do
10
10
  OFX::Parser::OFX102::VERSION.should == "1.0.2"
11
11
  end
12
-
12
+
13
13
  it "should set headers" do
14
14
  @parser.headers.should == @ofx.headers
15
15
  end
@@ -22,7 +22,7 @@ describe OFX::Parser::OFX102 do
22
22
  it "should set body" do
23
23
  @parser.body.should == @ofx.body
24
24
  end
25
-
25
+
26
26
  it "should set account" do
27
27
  @parser.account.should be_a_kind_of(OFX::Account)
28
28
  end
@@ -30,17 +30,38 @@ describe OFX::Parser::OFX102 do
30
30
  it "should set account" do
31
31
  @parser.sign_on.should be_a_kind_of(OFX::SignOn)
32
32
  end
33
+
34
+ it "should set statements" do
35
+ @parser.statements.size.should == 1
36
+ @parser.statements.first.should be_a_kind_of(OFX::Statement)
37
+ end
33
38
 
34
39
  it "should know about all transaction types" do
35
40
  valid_types = [
36
- 'CREDIT', 'DEBIT', 'INT', 'DIV', 'FEE', 'SRVCHG', 'DEP', 'ATM', 'POS', 'XFER',
41
+ 'CREDIT', 'DEBIT', 'INT', 'DIV', 'FEE', 'SRVCHG', 'DEP', 'ATM', 'POS', 'XFER',
37
42
  'CHECK', 'PAYMENT', 'CASH', 'DIRECTDEP', 'DIRECTDEBIT', 'REPEATPMT', 'OTHER'
38
43
  ]
39
44
  valid_types.sort.should == OFX::Parser::OFX102::TRANSACTION_TYPES.keys.sort
40
-
45
+
41
46
  valid_types.each do |transaction_type|
42
47
  transaction_type.downcase.to_sym.should equal OFX::Parser::OFX102::TRANSACTION_TYPES[transaction_type]
43
48
  end
44
49
  end
45
-
50
+
51
+ describe "#build_date" do
52
+ context "without a Time Zone" do
53
+ it "should default to GMT" do
54
+ @parser.send(:build_date, "20170904").should == Time.gm(2017, 9, 4)
55
+ @parser.send(:build_date, "20170904082855").should == Time.gm(2017, 9, 4, 8, 28, 55)
56
+ end
57
+ end
58
+
59
+ context "with a Time Zone" do
60
+ it "should returns the correct date" do
61
+ @parser.send(:build_date, "20150507164333[-0300:BRT]").should == Time.new(2015, 5, 7, 16, 43, 33, "-03:00")
62
+ @parser.send(:build_date, "20180507120000[0:GMT]").should == Time.gm(2018, 5, 7, 12)
63
+ @parser.send(:build_date, "20170904082855[-3:GMT]").should == Time.new(2017, 9, 4, 8, 28, 55, "-03:00")
64
+ end
65
+ end
66
+ end
46
67
  end