ecb-currency_converter 0.1.0 → 0.2.0
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 +15 -0
- data/README.md +29 -7
- data/ecb-currency_converter.gemspec +2 -1
- data/lib/ecb/currency_converter/exceptions.rb +17 -6
- data/lib/ecb/currency_converter/version.rb +1 -1
- data/lib/ecb/currency_converter.rb +157 -28
- data/spec/ecb/currency_converter_spec.rb +173 -61
- data/spec/fixtures/eurofxref-hist-90d.xml +2144 -0
- data/spec/spec_helper.rb +11 -2
- metadata +22 -18
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ODIyZmJiZWIxOGNhYTMyZDBkNjE4ZmYyOWQ3ZmNmNzUyZDBhMjJmMg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MjQxYTA2YjBkYzE0N2M0NWQ1MmUwODkyNjgzMzgzYTNiNTE1MDA1Ng==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzY5YWZiYTg3YTViY2MyNDAxMjgzZDVmYzE0NGU2YTRlN2VmZmU3MGViZDdl
|
10
|
+
Y2MxOTJiMTY0MDE0MWExNDBkYjA1NDY5ZmFhODU4ZTU5NDVhNTI0OTg0MDJj
|
11
|
+
Y2U3ZTFiMjgwZjcwMzVkZDg1NTE0NjA1NzJkNjNmNzNmYzA5NTQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGE1OTQ3OTQyODNkZDY4ZTUzZDE4MmEzYmQxODAyYTg4ZTk4YjA5ZjE4MWJh
|
14
|
+
ZWVlMDg5Y2Y3MTdiNWYxYzk2N2QyMjY3NmNkZTMwMjkzZjY1ZmI3ZjM5MjBm
|
15
|
+
N2MwNzZhZjc0ZTkzMWQzMzk5YmEwNDM1NTgyNTRhMDdmMmExNzk=
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# ECB::CurrencyConverter
|
2
2
|
|
3
|
-
Currency Conversion using the European Central Bank's Euro foreign exchange
|
3
|
+
Currency Conversion using the European Central Bank's Euro foreign exchange
|
4
|
+
reference rates. All calculations are performed and returned in +BigDecimal+.
|
4
5
|
|
5
6
|
## Installation
|
6
7
|
|
@@ -20,19 +21,40 @@ Or install it yourself as:
|
|
20
21
|
|
21
22
|
To convert between two currencies:
|
22
23
|
|
23
|
-
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD')
|
24
|
-
=> 133.
|
24
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD').to_f
|
25
|
+
=> 133.74
|
25
26
|
|
26
|
-
ECB::CurrencyConverter.exchange(100, 'USD', 'EUR')
|
27
|
+
ECB::CurrencyConverter.exchange(100, 'USD', 'EUR').to_f
|
27
28
|
=> 74.77194556602363
|
28
29
|
|
30
|
+
To convert between two currencies using historical data:
|
31
|
+
|
32
|
+
date = Date.parse('2013-05-17')
|
33
|
+
|
34
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD', date).to_f
|
35
|
+
=> 128.69
|
36
|
+
|
37
|
+
ECB::CurrencyConverter.exchange(100, 'USD', 'EUR', date).to_f
|
38
|
+
=> 77.70611547128759
|
39
|
+
|
40
|
+
|
29
41
|
To get the most recent exchange rate between two currencies:
|
30
42
|
|
31
|
-
ECB::CurrencyConverter.rate('EUR', 'USD')
|
43
|
+
ECB::CurrencyConverter.rate('EUR', 'USD').to_f
|
32
44
|
=> 1.3374
|
33
45
|
|
34
|
-
ECB::CurrencyConverter.rate('USD', 'EUR')
|
35
|
-
=> 0.
|
46
|
+
ECB::CurrencyConverter.rate('USD', 'EUR').to_f
|
47
|
+
=> 0.7477194556602362
|
48
|
+
|
49
|
+
To get the historical exchange rate between two currencies:
|
50
|
+
|
51
|
+
date = Date.parse('2013-05-17')
|
52
|
+
|
53
|
+
ECB::CurrencyConverter.rate('EUR', 'USD', date).to_f
|
54
|
+
=> 1.2869
|
55
|
+
|
56
|
+
ECB::CurrencyConverter.rate('USD', 'EUR', date).to_f
|
57
|
+
=> 0.777061154712876
|
36
58
|
|
37
59
|
## Contributing
|
38
60
|
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ['developer@sepcot.com']
|
11
11
|
spec.description = %q{Currency Conversion using the European Central Bank's Euro foreign exchange reference rates.}
|
12
12
|
spec.summary = %q{Currency Conversion using the European Central Bank's Euro foreign exchange reference rates.}
|
13
|
-
spec.homepage = ''
|
13
|
+
spec.homepage = 'https://github.com/msepcot/ecb-currency_converter'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency 'fakeweb'
|
23
23
|
spec.add_development_dependency 'rake'
|
24
24
|
spec.add_development_dependency 'rspec'
|
25
|
+
spec.add_development_dependency 'timecop'
|
25
26
|
|
26
27
|
spec.add_dependency 'httparty', '~> 0.11.0'
|
27
28
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module ECB # :nodoc:
|
2
|
-
# = ECB
|
2
|
+
# = ECB Missing Date Error
|
3
3
|
#
|
4
|
-
# Raised when
|
4
|
+
# Raised when the data for a date is +nil+.
|
5
5
|
#
|
6
|
-
# * +
|
7
|
-
class
|
8
|
-
def initialize(
|
9
|
-
super("#{
|
6
|
+
# * +date+ - the missing date.
|
7
|
+
class MissingDateError < StandardError
|
8
|
+
def initialize(date)
|
9
|
+
super("Foreign exchange reference rate for #{date.to_s} is missing.")
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -20,4 +20,15 @@ module ECB # :nodoc:
|
|
20
20
|
super("Foreign exchange reference rate for #{currency_code} is missing.")
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
# = ECB Unknown Currency Error
|
25
|
+
#
|
26
|
+
# Raised when we try to grab data for an unsupported currency code.
|
27
|
+
#
|
28
|
+
# * +currency_code+ - the unsupported ISO 4217 Currency Code.
|
29
|
+
class UnknownCurrencyError < StandardError
|
30
|
+
def initialize(currency_code)
|
31
|
+
super("#{currency_code} is not supported.")
|
32
|
+
end
|
33
|
+
end
|
23
34
|
end
|
@@ -1,9 +1,46 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'bigdecimal/util'
|
3
|
+
require 'date'
|
1
4
|
require 'ecb/currency_converter/exceptions'
|
2
5
|
require 'ecb/currency_converter/version'
|
3
6
|
require 'httparty'
|
4
7
|
|
5
|
-
|
6
|
-
|
8
|
+
# = European Central Bank
|
9
|
+
#
|
10
|
+
# The reference rates are usually updated by 3 p.m. CET. They are based on a
|
11
|
+
# regular daily concertation procedure between central banks across Europe and
|
12
|
+
# worldwide, which normally takes place at 2.15 p.m. CET.
|
13
|
+
#
|
14
|
+
# The procedure for computation of Euro Foreign Exchange Reference Rates is
|
15
|
+
# stated in: http://www.ecb.int/press/pr/date/2010/html/pr101203.en.html
|
16
|
+
#
|
17
|
+
# The current procedure for the computation and publication of the foreign
|
18
|
+
# exchange reference rates will also apply to the currency that is to be added
|
19
|
+
# to the list:
|
20
|
+
#
|
21
|
+
# * The reference rates are based on the daily concertation procedure between
|
22
|
+
# central banks within and outside the European System of Central Banks,
|
23
|
+
# which normally takes place at 2.15 p.m. CET. The reference exchange rates
|
24
|
+
# are published both by electronic market information providers and on the
|
25
|
+
# ECB's website shortly after the concertation procedure has been completed.
|
26
|
+
# * Only one reference exchange rate (i.e. the mid-rate) is published for each
|
27
|
+
# currency, using the "certain" method (i.e. EUR 1 = x foreign currency
|
28
|
+
# units).
|
29
|
+
# * The number of significant digits used may vary according to the currency,
|
30
|
+
# reflecting market conventions. In most cases, however, five significant
|
31
|
+
# digits are used.
|
32
|
+
# * The euro area national central banks may publish more comprehensive lists
|
33
|
+
# of euro reference exchange rates than that published by the ECB.
|
34
|
+
#
|
35
|
+
# The ECB pays due attention to ensuring that the published exchange rates
|
36
|
+
# reflect the market conditions prevailing at the time of the daily
|
37
|
+
# concertation procedure. Since the exchange rates of the above currencies
|
38
|
+
# against the euro are averages of buying and selling rates, they do not
|
39
|
+
# necessarily reflect the rates at which actual market transactions have been
|
40
|
+
# made. The exchange rates against the euro published by the ECB are released
|
41
|
+
# for reference purposes only.
|
42
|
+
module ECB
|
43
|
+
# = Currency Converter
|
7
44
|
#
|
8
45
|
# Convert values or find the most recent foreign exchange reference rate
|
9
46
|
# between any of the +CURRENCIES+ supported by the European Central Bank.
|
@@ -13,8 +50,12 @@ module ECB # :nodoc:
|
|
13
50
|
#
|
14
51
|
# http://www.ecb.int/stats/exchange/eurofxref/html/index.en.html
|
15
52
|
#
|
53
|
+
# All calculations are performed using +BigDecimal+.
|
16
54
|
module CurrencyConverter
|
17
|
-
|
55
|
+
BASE_URI = 'http://www.ecb.europa.eu/stats/eurofxref'
|
56
|
+
DAILY_URI = "#{BASE_URI}/eurofxref-daily.xml"
|
57
|
+
NINTY_URI = "#{BASE_URI}/eurofxref-hist-90d.xml"
|
58
|
+
|
18
59
|
CURRENCIES = [
|
19
60
|
'AUD', # Australian Dollar (A$)
|
20
61
|
'BGN', # Bulgarian Lev (BGN)
|
@@ -55,52 +96,140 @@ module ECB # :nodoc:
|
|
55
96
|
class << self # Class methods
|
56
97
|
# Converts the +value+ between +from+ and +to+ currencies.
|
57
98
|
#
|
58
|
-
# = Example
|
99
|
+
# = Example (Date.today == 2013-06-18)
|
59
100
|
#
|
60
|
-
# ECB::CurrencyConverter.exchange(100, 'EUR', 'USD')
|
61
|
-
# => 133.
|
101
|
+
# ECB::CurrencyConverter.exchange(100, 'EUR', 'USD').to_f
|
102
|
+
# => 133.74
|
62
103
|
#
|
63
|
-
# ECB::CurrencyConverter.exchange(100, 'USD', 'EUR')
|
104
|
+
# ECB::CurrencyConverter.exchange(100, 'USD', 'EUR').to_f
|
64
105
|
# => 74.77194556602363
|
65
|
-
|
66
|
-
|
106
|
+
#
|
107
|
+
# = Historical Example (2013-05-17)
|
108
|
+
#
|
109
|
+
# date = Date.parse('2013-05-17')
|
110
|
+
#
|
111
|
+
# ECB::CurrencyConverter.exchange(100, 'EUR', 'USD', date).to_f
|
112
|
+
# => 128.69
|
113
|
+
#
|
114
|
+
# ECB::CurrencyConverter.exchange(100, 'USD', 'EUR', date).to_f
|
115
|
+
# => 77.70611547128759
|
116
|
+
def exchange(value, from, to, date = Date.today)
|
117
|
+
value.to_d * rate(from, to, date)
|
67
118
|
end
|
68
119
|
|
69
120
|
# Return the exchange rate between +from+ and +to+ currencies.
|
70
121
|
#
|
71
|
-
# = Example
|
122
|
+
# = Example (Date.today == 2013-06-18)
|
72
123
|
#
|
73
|
-
# ECB::CurrencyConverter.rate('EUR', 'USD')
|
124
|
+
# ECB::CurrencyConverter.rate('EUR', 'USD').to_f
|
74
125
|
# => 1.3374
|
75
126
|
#
|
76
|
-
# ECB::CurrencyConverter.rate('USD', 'EUR')
|
77
|
-
# => 0.
|
78
|
-
|
79
|
-
|
80
|
-
|
127
|
+
# ECB::CurrencyConverter.rate('USD', 'EUR').to_f
|
128
|
+
# => 0.7477194556602362
|
129
|
+
#
|
130
|
+
# = Historical Example (2013-05-17)
|
131
|
+
#
|
132
|
+
# date = Date.parse('2013-05-17')
|
133
|
+
#
|
134
|
+
# ECB::CurrencyConverter.rate('EUR', 'USD', date).to_f
|
135
|
+
# => 1.2869
|
136
|
+
#
|
137
|
+
# ECB::CurrencyConverter.rate('USD', 'EUR', date).to_f
|
138
|
+
# => 0.777061154712876
|
139
|
+
def rate(from, to, date = Date.today)
|
140
|
+
load_data!(date)
|
141
|
+
date = closest_available_date(date)
|
142
|
+
validate(from, to, date)
|
143
|
+
|
144
|
+
BigDecimal('1.0') / data_for(from, date) * data_for(to, date)
|
145
|
+
end
|
81
146
|
|
82
|
-
|
147
|
+
# Silence warning messages that can arise from +closest_available_date+
|
148
|
+
def silence_warnings(silence = true)
|
149
|
+
@silence_warnings = silence
|
83
150
|
end
|
84
151
|
private
|
152
|
+
# Find the closest date with available data.
|
153
|
+
#
|
154
|
+
# Returns the requested date if data is available. If not, we search back
|
155
|
+
# up to five days to see if there is available data. This is primarily
|
156
|
+
# used to handle weekends and holidays when ECB does not publish data. We
|
157
|
+
# will print a warning message (see +silence_warnings+) if we are using
|
158
|
+
# a date other than the one specified.
|
159
|
+
def closest_available_date(date)
|
160
|
+
closest = date.to_date
|
161
|
+
5.times do
|
162
|
+
if @euro.key?(closest.to_s)
|
163
|
+
unless closest == date || @silence_warnings
|
164
|
+
warn "Foreign exchange reference rate for #{date.to_s} " \
|
165
|
+
"is not available, using rate for #{closest.to_s}."
|
166
|
+
end
|
167
|
+
return closest
|
168
|
+
end
|
169
|
+
closest = closest - 1
|
170
|
+
end
|
171
|
+
raise MissingDateError.new(date)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Grab the exchange rate for a currency on a particular date.
|
175
|
+
def data_for(currency, date)
|
176
|
+
@euro[date.to_s][currency] || BigDecimal('0')
|
177
|
+
end
|
178
|
+
|
179
|
+
# Load exchange rate data based on the date requested.
|
180
|
+
#--
|
181
|
+
# NOTE: should we just load the 90-day historical file? It includes the
|
182
|
+
# most recent date, but takes 2-3x the time to load (.6 or .9 seconds,
|
183
|
+
# compared to .3 for the daily feed in local tests).
|
184
|
+
# NOTE: this loads the 90-day historical file even if the requested date
|
185
|
+
# is outside the 90-day window.
|
186
|
+
#++
|
187
|
+
def load_data!(date)
|
188
|
+
if date.to_date == Date.today
|
189
|
+
@euro ||= load_daily
|
190
|
+
elsif !defined?(@euro) || @euro.size < 2
|
191
|
+
@euro = load_ninty
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
85
195
|
# Load data from European Central Bank's daily XML feed.
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
196
|
+
def load_daily
|
197
|
+
response = HTTParty.get(DAILY_URI, format: :xml)
|
198
|
+
load(response['Envelope']['Cube']['Cube'])
|
199
|
+
end
|
200
|
+
|
201
|
+
# Load data from European Central Bank's historical 90-day XML feed.
|
202
|
+
def load_ninty
|
203
|
+
data = {}
|
204
|
+
|
205
|
+
response = HTTParty.get(NINTY_URI, format: :xml)
|
206
|
+
response['Envelope']['Cube']['Cube'].each do |cube|
|
207
|
+
data.merge!(load(cube))
|
94
208
|
end
|
209
|
+
|
210
|
+
data
|
211
|
+
end
|
212
|
+
|
213
|
+
# Load one +Cube+ worth of data.
|
214
|
+
def load(hash)
|
215
|
+
date = hash['time']
|
216
|
+
data = hash['Cube']
|
217
|
+
data = data.reduce({}) do |hash, element|
|
218
|
+
hash[element['currency']] = BigDecimal.new(element['rate'])
|
219
|
+
hash
|
220
|
+
end
|
221
|
+
data['EUR'] = BigDecimal('1.0')
|
222
|
+
|
223
|
+
{ date => data }
|
95
224
|
end
|
96
225
|
|
97
226
|
# Raise errors for invalid currencies or missing data.
|
98
|
-
def validate(from, to)
|
227
|
+
def validate(from, to, date)
|
99
228
|
raise UnknownCurrencyError.new(from) unless CURRENCIES.include?(from)
|
100
229
|
raise UnknownCurrencyError.new(to) unless CURRENCIES.include?(to)
|
101
230
|
|
102
|
-
raise MissingExchangeRateError.new(from) if
|
103
|
-
raise MissingExchangeRateError.new(to) if
|
231
|
+
raise MissingExchangeRateError.new(from) if data_for(from, date).zero?
|
232
|
+
raise MissingExchangeRateError.new(to) if data_for(to, date).zero?
|
104
233
|
end
|
105
234
|
end
|
106
235
|
end
|
@@ -1,17 +1,21 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
2
|
|
3
|
-
def rate_comparison(from, to, actual)
|
4
|
-
ECB::CurrencyConverter.rate(from, to).should
|
3
|
+
def rate_comparison(from, to, date, actual)
|
4
|
+
ECB::CurrencyConverter.rate(from, to, date).should(
|
5
|
+
be_within(0.0001).of(actual)
|
6
|
+
)
|
5
7
|
end
|
6
8
|
|
7
|
-
def exchange_comparison(amount, from, to, actual)
|
8
|
-
ECB::CurrencyConverter.exchange(amount, from, to).should(
|
9
|
+
def exchange_comparison(amount, from, to, date, actual)
|
10
|
+
ECB::CurrencyConverter.exchange(amount, from, to, date).should(
|
9
11
|
be_within(0.0001).of(actual)
|
10
12
|
)
|
11
13
|
end
|
12
14
|
|
13
15
|
describe ECB::CurrencyConverter do
|
14
16
|
describe '#exchange' do
|
17
|
+
let(:date) { Date.today }
|
18
|
+
|
15
19
|
it 'should convert between currencies through the EUR' do
|
16
20
|
[ ['AUD', 'LTL', 244.34222], ['BGN', 'LVL', 35.87278],
|
17
21
|
['BRL', 'MXN', 591.82960], ['CAD', 'MYR', 309.46094],
|
@@ -23,12 +27,33 @@ describe ECB::CurrencyConverter do
|
|
23
27
|
['ILS', 'TRY', 52.38214], ['INR', 'USD', 1.70152],
|
24
28
|
['JPY', 'ZAR', 10.51728],
|
25
29
|
].each do |from, to, actual|
|
26
|
-
exchange_comparison(100, from, to, actual)
|
30
|
+
exchange_comparison(100, from, to, date, actual)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#exchange(historical)' do
|
36
|
+
let(:date) { Date.parse('2013-05-17') }
|
37
|
+
|
38
|
+
it 'should convert between currencies through the EUR' do
|
39
|
+
[ ['AUD', 'LTL', 261.25907], ['BGN', 'LVL', 35.75518],
|
40
|
+
['BRL', 'MXN', 606.90804], ['CAD', 'MYR', 294.17549],
|
41
|
+
['CHF', 'NOK', 605.61490], ['CNY', 'NZD', 20.13790],
|
42
|
+
['CZK', 'PHP', 203.79391], ['DKK', 'PLN', 55.96049],
|
43
|
+
['EUR', 'KRW', 143784.00], ['GBP', 'RON', 513.40633],
|
44
|
+
['HKD', 'RUB', 404.23576], ['HRK', 'SEK', 113.48831],
|
45
|
+
['HUF', 'SGD', 0.55602], ['IDR', 'THB', 0.30525],
|
46
|
+
['ILS', 'TRY', 50.16654], ['INR', 'USD', 1.82264],
|
47
|
+
['JPY', 'ZAR', 9.15780],
|
48
|
+
].each do |from, to, actual|
|
49
|
+
exchange_comparison(100, from, to, date, actual)
|
27
50
|
end
|
28
51
|
end
|
29
52
|
end
|
30
53
|
|
31
54
|
describe '#rate' do
|
55
|
+
let(:date) { Date.today }
|
56
|
+
|
32
57
|
it 'should match the provided rates when converting from EUR' do
|
33
58
|
[ ['AUD', 1.4131], ['BGN', 1.9558], ['BRL', 2.9203],
|
34
59
|
['CAD', 1.3635], ['CHF', 1.2315], ['CNY', 8.1963],
|
@@ -42,7 +67,7 @@ describe ECB::CurrencyConverter do
|
|
42
67
|
['SEK', 8.6753], ['SGD', 1.6855], ['THB', 41.259],
|
43
68
|
['TRY', 2.5167], ['USD', 1.3374], ['ZAR', 13.4148]
|
44
69
|
].each do |currency, value|
|
45
|
-
rate_comparison('EUR', currency, value)
|
70
|
+
rate_comparison('EUR', currency, date, value)
|
46
71
|
end
|
47
72
|
end
|
48
73
|
|
@@ -59,12 +84,12 @@ describe ECB::CurrencyConverter do
|
|
59
84
|
['SEK', 8.6753], ['SGD', 1.6855], ['THB', 41.259],
|
60
85
|
['TRY', 2.5167], ['USD', 1.3374], ['ZAR', 13.4148]
|
61
86
|
].each do |currency, value|
|
62
|
-
rate_comparison(currency, 'EUR', 1.0 / value)
|
87
|
+
rate_comparison(currency, 'EUR', date, 1.0 / value)
|
63
88
|
end
|
64
89
|
end
|
65
90
|
|
66
91
|
it 'should report a rate of 1.0 between the same currency' do
|
67
|
-
rate_comparison('KRW', 'KRW', 1.0)
|
92
|
+
rate_comparison('KRW', 'KRW', date, 1.0)
|
68
93
|
end
|
69
94
|
|
70
95
|
it 'should convert rates between currencies through the EUR' do
|
@@ -77,62 +102,149 @@ describe ECB::CurrencyConverter do
|
|
77
102
|
['IDR', 'THB', 0.00311], ['ILS', 'TRY', 0.52382],
|
78
103
|
['INR', 'USD', 0.01701], ['JPY', 'ZAR', 0.10517],
|
79
104
|
].each do |from, to, value|
|
80
|
-
rate_comparison(from, to, value)
|
105
|
+
rate_comparison(from, to, date, value)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#rate(historical)' do
|
111
|
+
let(:date) { Date.parse('2013-05-17') }
|
112
|
+
it 'should match the provided rates when converting from EUR' do
|
113
|
+
[ ['AUD', 1.3216], ['BGN', 1.9558], ['BRL', 2.61],
|
114
|
+
['CAD', 1.322], ['CHF', 1.2449], ['CNY', 7.904],
|
115
|
+
['CZK', 25.989], ['DKK', 7.4524], ['GBP', 0.84475],
|
116
|
+
['HKD', 9.9911], ['HRK', 7.571], ['HUF', 290.56],
|
117
|
+
['IDR', 12554.93], ['ILS', 4.7135], ['INR', 70.606],
|
118
|
+
['JPY', 131.87], ['KRW', 1437.84], ['LTL', 3.4528],
|
119
|
+
['LVL', 0.6993], ['MXN', 15.8403], ['MYR', 3.889],
|
120
|
+
['NOK', 7.5393], ['NZD', 1.5917], ['PHP', 52.964],
|
121
|
+
['PLN', 4.1704], ['RON', 4.337], ['RUB', 40.3876],
|
122
|
+
['SEK', 8.5922], ['SGD', 1.6156], ['THB', 38.324],
|
123
|
+
['TRY', 2.3646], ['USD', 1.2869], ['ZAR', 12.0764]
|
124
|
+
].each do |currency, value|
|
125
|
+
rate_comparison('EUR', currency, date, value)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should match the inverted rates when converting to EUR' do
|
130
|
+
[ ['AUD', 1.3216], ['BGN', 1.9558], ['BRL', 2.61],
|
131
|
+
['CAD', 1.322], ['CHF', 1.2449], ['CNY', 7.904],
|
132
|
+
['CZK', 25.989], ['DKK', 7.4524], ['GBP', 0.84475],
|
133
|
+
['HKD', 9.9911], ['HRK', 7.571], ['HUF', 290.56],
|
134
|
+
['IDR', 12554.93], ['ILS', 4.7135], ['INR', 70.606],
|
135
|
+
['JPY', 131.87], ['KRW', 1437.84], ['LTL', 3.4528],
|
136
|
+
['LVL', 0.6993], ['MXN', 15.8403], ['MYR', 3.889],
|
137
|
+
['NOK', 7.5393], ['NZD', 1.5917], ['PHP', 52.964],
|
138
|
+
['PLN', 4.1704], ['RON', 4.337], ['RUB', 40.3876],
|
139
|
+
['SEK', 8.5922], ['SGD', 1.6156], ['THB', 38.324],
|
140
|
+
['TRY', 2.3646], ['USD', 1.2869], ['ZAR', 12.0764]
|
141
|
+
].each do |currency, value|
|
142
|
+
rate_comparison(currency, 'EUR', date, 1.0 / value)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should report a rate of 1.0 between the same currency' do
|
147
|
+
rate_comparison('KRW', 'KRW', date, 1.0)
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should convert rates between currencies through the EUR' do
|
151
|
+
[ ['AUD', 'LTL', 2.61259], ['BGN', 'LVL', 0.35755],
|
152
|
+
['BRL', 'MXN', 6.06908], ['CAD', 'MYR', 2.94175],
|
153
|
+
['CHF', 'NOK', 6.05614], ['CNY', 'NZD', 0.20137],
|
154
|
+
['CZK', 'PHP', 2.03793], ['DKK', 'PLN', 0.55960],
|
155
|
+
['GBP', 'RON', 5.13406], ['HKD', 'RUB', 4.04235],
|
156
|
+
['HRK', 'SEK', 1.13488], ['HUF', 'SGD', 0.00556],
|
157
|
+
['IDR', 'THB', 0.00305], ['ILS', 'TRY', 0.50166],
|
158
|
+
['INR', 'USD', 0.01822], ['JPY', 'ZAR', 0.09157],
|
159
|
+
].each do |from, to, value|
|
160
|
+
rate_comparison(from, to, date, value)
|
81
161
|
end
|
82
162
|
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe '#silence_warnings' do
|
166
|
+
let(:date) { Date.parse('2013-05-18') } # NOTE: weekend date
|
167
|
+
around { ECB::CurrencyConverter.silence_warnings(false) }
|
168
|
+
|
169
|
+
it 'should warn users when not using their requested date' do
|
170
|
+
ECB::CurrencyConverter.silence_warnings
|
171
|
+
Kernal.should_not_receive(:warn)
|
172
|
+
|
173
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD', date)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should not warn users when they silence warnings' do
|
177
|
+
Kernal.should_receive(:warn)
|
178
|
+
|
179
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD', date)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe '#validate' do
|
184
|
+
describe ECB::MissingDateError do
|
185
|
+
it 'should raise an error for a date more than 90 days ago' do
|
186
|
+
expect {
|
187
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD', Date.today - 90)
|
188
|
+
}.to raise_error(ECB::MissingDateError)
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should not raise an error for a date less than 90 days ago' do
|
192
|
+
expect {
|
193
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD', Date.today - 89)
|
194
|
+
}.to_not raise_error(ECB::MissingDateError)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe ECB::MissingExchangeRateError do
|
199
|
+
before do
|
200
|
+
ECB::CurrencyConverter.send(:load_data!, Date.today)
|
201
|
+
|
202
|
+
euro = ECB::CurrencyConverter.instance_variable_get(:@euro)
|
203
|
+
euro[Date.today.to_s]['USD'] = nil
|
204
|
+
|
205
|
+
ECB::CurrencyConverter.instance_variable_set(:@euro, euro)
|
206
|
+
end
|
207
|
+
|
208
|
+
after do
|
209
|
+
ECB::CurrencyConverter.instance_variable_set(:@euro, nil)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should raise an error if supported data is missing' do
|
213
|
+
expect {
|
214
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD')
|
215
|
+
}.to raise_error(ECB::MissingExchangeRateError)
|
216
|
+
|
217
|
+
expect {
|
218
|
+
ECB::CurrencyConverter.exchange(100, 'USD', 'EUR')
|
219
|
+
}.to raise_error(ECB::MissingExchangeRateError)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should not raise an error if we have data available' do
|
223
|
+
expect {
|
224
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'GBP')
|
225
|
+
}.to_not raise_error(ECB::MissingExchangeRateError)
|
226
|
+
|
227
|
+
expect {
|
228
|
+
ECB::CurrencyConverter.exchange(100, 'GBP', 'EUR')
|
229
|
+
}.to_not raise_error(ECB::MissingExchangeRateError)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe ECB::UnknownCurrencyError do
|
234
|
+
it 'should raise an error on an unsupported currency code' do
|
235
|
+
expect {
|
236
|
+
ECB::CurrencyConverter.exchange(100, 'USD', 'UNKNOWN')
|
237
|
+
}.to raise_error(ECB::UnknownCurrencyError)
|
238
|
+
|
239
|
+
expect {
|
240
|
+
ECB::CurrencyConverter.exchange(100, 'UNKNOWN', 'USD')
|
241
|
+
}.to raise_error(ECB::UnknownCurrencyError)
|
242
|
+
end
|
83
243
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
ECB::CurrencyConverter.exchange(100, 'USD', 'UNKNOWN')
|
89
|
-
}.to raise_error(ECB::UnknownCurrencyError)
|
90
|
-
|
91
|
-
expect {
|
92
|
-
ECB::CurrencyConverter.exchange(100, 'UNKNOWN', 'USD')
|
93
|
-
}.to raise_error(ECB::UnknownCurrencyError)
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'should not raise an error for supported currency codes' do
|
97
|
-
expect {
|
98
|
-
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD')
|
99
|
-
}.to_not raise_error(ECB::UnknownCurrencyError)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
describe ECB::MissingExchangeRateError do
|
104
|
-
before do
|
105
|
-
ECB::CurrencyConverter.send(:load_data!)
|
106
|
-
|
107
|
-
euro = ECB::CurrencyConverter.instance_variable_get(:@euro)
|
108
|
-
euro['USD'] = nil
|
109
|
-
|
110
|
-
ECB::CurrencyConverter.instance_variable_set(:@euro, euro)
|
111
|
-
end
|
112
|
-
|
113
|
-
after do
|
114
|
-
ECB::CurrencyConverter.instance_variable_set(:@euro, nil)
|
115
|
-
end
|
116
|
-
|
117
|
-
it 'should raise an error if supported data is missing' do
|
118
|
-
expect {
|
119
|
-
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD')
|
120
|
-
}.to raise_error(ECB::MissingExchangeRateError)
|
121
|
-
|
122
|
-
expect {
|
123
|
-
ECB::CurrencyConverter.exchange(100, 'USD', 'EUR')
|
124
|
-
}.to raise_error(ECB::MissingExchangeRateError)
|
125
|
-
end
|
126
|
-
|
127
|
-
it 'should not raise an error if we have data available' do
|
128
|
-
expect {
|
129
|
-
ECB::CurrencyConverter.exchange(100, 'EUR', 'GBP')
|
130
|
-
}.to_not raise_error(ECB::MissingExchangeRateError)
|
131
|
-
|
132
|
-
expect {
|
133
|
-
ECB::CurrencyConverter.exchange(100, 'GBP', 'EUR')
|
134
|
-
}.to_not raise_error(ECB::MissingExchangeRateError)
|
135
|
-
end
|
244
|
+
it 'should not raise an error for supported currency codes' do
|
245
|
+
expect {
|
246
|
+
ECB::CurrencyConverter.exchange(100, 'EUR', 'USD')
|
247
|
+
}.to_not raise_error(ECB::UnknownCurrencyError)
|
136
248
|
end
|
137
249
|
end
|
138
250
|
end
|