exchange_rates 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in exchange_rates.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jakub Hampl
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+ # ExchangeRates
2
+
3
+ ExchangeRates is a Ruby Gem that allows currency conversion between a number of currencies and allows a historical overview of the currencies. It takes its information from the [European Central Banks historical feed](
4
+ http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml).
5
+
6
+ A demo application can be seen running at http://exchange.herokuapp.com and the source code is [available here](https://github.com/gampleman/exchange_rates_demo).
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'exchange_rates'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install exchange_rates
21
+
22
+ ## Usage
23
+
24
+ The first task that the programmer must use is to obtain the data source. The library assumes it will be in `./exchange-rates.xml`, however the path can be set via the environment variable `EXCHANGE_RATE_FILE`, which should point to the exchange rate file. This file can be downloaded from the [ECB website](http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml).
25
+
26
+ You can add this to your crontab to automatically download the updated files (where MY_PROJECT_PATH is an absolute path to your project).
27
+
28
+ @daily curl -o MY_PROJECT_PATH/exchange-rates.xml http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml >/dev/null 2>&1
29
+
30
+ Finally if you have a different datasource, you can simply parse it and call `ExchangeRates.set_rates(data, base_currency)`, where `data` will be your currency data organized by date and `base_currency` will be the currency code to which your rates are relative to. For example:
31
+
32
+ ExchangeRates.set_rates({
33
+ Date::civil(2013, 11, 21) => {
34
+ 'EUR' => 0.9472, # means 1 USD = 0.9472 EUR on the 21st Oct 2013
35
+ 'JPY' => 135.83,
36
+ 'BGN' => 1.9558,
37
+ 'CZK' => 27.197
38
+ },
39
+ Date::civil(2013, 11, 20) => {
40
+ 'EUR' => 0.9527,
41
+ 'JPY' => 135.2,
42
+ 'BGN' => 1.9558,
43
+ 'CZK' => 27.329
44
+ }
45
+ }, 'USD')
46
+
47
+ Then there are three methods you can call:
48
+
49
+ #### at
50
+
51
+ `ExchangeRates.at(date, from_currency, to_currency)` returns the exchange rate from `from_currency` to `to_currency` on the date `date`. Given the data above, the call `ExchangeRates.at(Date::civil(2013, 11, 20), 'JPY', 'CZK')` would return `0.202137574`.
52
+
53
+ The method throws exceptions if asked about missing dates/currencies.
54
+
55
+ #### convert
56
+
57
+ `ExchangeRates.convert(amount, opts)` converts an amount of money between currencies. The options for this method are:
58
+
59
+ `:from` - from currency, defaults to base_currency (see above, for the default data this is EUR)
60
+ `:to` - to currency, defaults to base_currency
61
+ `:date` - date at which to perform conversion, defaults to current date
62
+
63
+ #### over_time
64
+
65
+ `ExchangeRates.over_time(from, to)` gives a hash of all known dates and the exchange rate between `from` and `to`.
66
+
67
+ Again given the above data, calling `ExchangeRates.over_time('JPY', 'CZK')` gives `{Date::civil(2013, 11, 21) => 0.200228226, Date::civil(2013, 11, 20) => 0.202137574}`.
68
+
69
+ ## Contributing
70
+
71
+ 1. Fork it
72
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
73
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
74
+ 4. Push to the branch (`git push origin my-new-feature`)
75
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/exchange_rates/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Jakub Hampl"]
6
+ gem.email = ["honitom@seznam.cz"]
7
+ gem.description = %q{exchange_rates is a gem that allows currency conversion and rate history tracking.}
8
+ gem.summary = %q{Gives historical exchange rates}
9
+ gem.homepage = "https://github.com/gampleman/exchange_rates"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "exchange_rates"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = ExchangeRates::VERSION
17
+
18
+ gem.add_dependency "nokogiri"
19
+ gem.add_development_dependency "rspec"
20
+ gem.add_development_dependency "rake"
21
+ end
@@ -0,0 +1,87 @@
1
+ require "exchange_rates/version"
2
+ require "nokogiri"
3
+
4
+ # Exchange Rates is responsible for parsing a locally available XML file
5
+ # listing ratios between the base_currency and target currencies. All currencies should
6
+ # be provided as strings with their 3 letter code.
7
+ class ExchangeRates
8
+ # ExchangeRates.at gives an exchange rate between two currencies at a particular
9
+ # date. Raises exceptions if the dates are out of range of the file or if the
10
+ # currencies are unknown.
11
+ def self.at(date, from, to)
12
+ parse_rates
13
+ if rates = @@rates[date]
14
+ unless from_rate = rates[from]
15
+ if from == 'EUR'
16
+ from_rate = 1.0
17
+ else
18
+ raise "Unknown 'from' currency"
19
+ end
20
+ end
21
+ unless to_rate = rates[to]
22
+ if to == 'EUR'
23
+ to_rate = 1.0
24
+ else
25
+ raise "Unknown 'to' currency"
26
+ end
27
+ end
28
+ to_rate / from_rate
29
+ else
30
+ raise "Date out of known dates."
31
+ end
32
+ end
33
+
34
+ # Converts an amount of money between currencies. The options for this method are:
35
+ #
36
+ # from - from currency, defaults to base_currency (typically EUR)
37
+ # to - to currency, defaults to base_currency (typically EUR)
38
+ # date - date at which to perform conversion, defaults to current date
39
+ def self.convert(amount, opts = {})
40
+ options = {from: @@base_currency, to: @@base_currency, date: Date.today}.merge(opts)
41
+ amount.to_f * at(options[:date], options[:from], options[:to])
42
+ end
43
+
44
+ # Calculates exchange rates between two currencies over all available dates.
45
+ def self.over_time(from, to)
46
+ parse_rates
47
+ results = {}
48
+ @@rates.each do |date, rates|
49
+ results[date] = self.at(date, from, to)
50
+ end
51
+ results
52
+ end
53
+
54
+ # Reads and parses the XML data feed providing the underlying data source.
55
+ # The data source is currently assumed to be in the format of the European
56
+ # Central Bank feed accesible at <http://www.ecb.europa.eu/stats/eurofxref/eurofxref­hist­90d.xml>
57
+ # which provides a 90 day history of exchange rates. It is assumed that this
58
+ # file is saved locally, best through a cron tab (see README).
59
+ # If a different source is to be used, call the `set_rates` method.
60
+ def self.parse_rates
61
+ @@rates ||= nil
62
+ return @@rates if @@rates
63
+ @@base_currency = 'EUR'
64
+ rate_file = ENV['EXCHANGE_RATE_FILE'] || './exchange-rates.xml'
65
+ doc = Nokogiri::XML(File.read(rate_file))
66
+ @@rates = {}
67
+ doc.css('Cube>Cube[time]').each do |day|
68
+ time = Date.parse day.attr('time')
69
+ @@rates[time] = {}
70
+ day.css('Cube').each{ |c|
71
+ @@rates[time][c.attr('currency')] = c.attr('rate').to_f
72
+ }
73
+ end
74
+ @@rates
75
+ end
76
+
77
+ # Set custom rates data. The data should be a hash of hashes, where the keys in the
78
+ # outer hash are Date objects and the keys in the inner hashes are three letter currency
79
+ # codes. The values are floats showing the exchange rate between a currency and the base
80
+ # currency. This second argument should be the base currency to which all values are relative
81
+ # to.
82
+ def self.set_rates(rates, base = 'EUR')
83
+ @@rates = rates
84
+ @@base_currency = base
85
+ end
86
+
87
+ end
@@ -0,0 +1,3 @@
1
+ class ExchangeRates
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,59 @@
1
+ require "exchange_rates"
2
+
3
+ describe ExchangeRates do
4
+
5
+ before(:all) do
6
+ File.open('/tmp/currencies.xml', 'w') {|file| file.write(SAMPLE_XML)}
7
+ ENV['EXCHANGE_RATE_FILE'] = '/tmp/currencies.xml'
8
+ end
9
+
10
+ it "parses the XML data format correctly" do
11
+ ExchangeRates.parse_rates.should eq(SAMPLE_DATA)
12
+ end
13
+
14
+ it "should get correct to EUR rates" do
15
+ ExchangeRates.at(DATE1, 'CZK', 'EUR').should eq(1.0/27.197)
16
+ end
17
+
18
+ it "should get correct from EUR rates" do
19
+ ExchangeRates.at(DATE1, 'EUR', 'CZK').should eq(27.197)
20
+ end
21
+
22
+ it "should get correct cross rates" do
23
+ ExchangeRates.at(DATE1, 'JPY', 'BGN').should eq(1.9558/135.83)
24
+ end
25
+
26
+ it "should raise exceptions when out of range" do
27
+ expect{ ExchangeRates.at(Date::civil(2012, 11, 21), 'JPY', 'BGN') }.to raise_error
28
+ end
29
+
30
+ it "should convert currencies accurately" do
31
+ ExchangeRates.convert(123, date: DATE1, from: 'CZK').should eq(123.0/27.197)
32
+ end
33
+
34
+ it "should spread data over time" do
35
+ ExchangeRates.over_time('EUR', 'CZK').should eq({Date::civil(2013, 11, 20) => 27.329, DATE1 => 27.197})
36
+ end
37
+ end
38
+
39
+
40
+ DATE1 = Date::civil(2013, 11, 21)
41
+
42
+ SAMPLE_XML = '<?xml version="1.0" encoding="UTF-8"?><gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref"><gesmes:subject>Reference rates</gesmes:subject><gesmes:Sender><gesmes:name>European Central Bank</gesmes:name></gesmes:Sender><Cube><Cube time="2013-11-21"><Cube currency="USD" rate="1.3472"/><Cube currency="JPY" rate="135.83"/><Cube currency="BGN" rate="1.9558"/><Cube currency="CZK" rate="27.197"/></Cube>
43
+ <Cube time="2013-11-20"><Cube currency="USD" rate="1.3527"/><Cube currency="JPY" rate="135.2"/><Cube currency="BGN" rate="1.9558"/><Cube currency="CZK" rate="27.329"/></Cube>
44
+ </Cube></gesmes:Envelope>'
45
+
46
+ SAMPLE_DATA = {
47
+ DATE1 => {
48
+ 'USD' => 1.3472,
49
+ 'JPY' => 135.83,
50
+ 'BGN' => 1.9558,
51
+ 'CZK' => 27.197
52
+ },
53
+ Date::civil(2013, 11, 20) => {
54
+ 'USD' => 1.3527,
55
+ 'JPY' => 135.2,
56
+ 'BGN' => 1.9558,
57
+ 'CZK' => 27.329
58
+ }
59
+ }
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: exchange_rates
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jakub Hampl
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: exchange_rates is a gem that allows currency conversion and rate history
63
+ tracking.
64
+ email:
65
+ - honitom@seznam.cz
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - exchange_rates.gemspec
76
+ - lib/exchange_rates.rb
77
+ - lib/exchange_rates/version.rb
78
+ - spec/exchange_rates_spec.rb
79
+ homepage: https://github.com/gampleman/exchange_rates
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ segments:
92
+ - 0
93
+ hash: 2857346963252068634
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ segments:
101
+ - 0
102
+ hash: 2857346963252068634
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.24
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Gives historical exchange rates
109
+ test_files:
110
+ - spec/exchange_rates_spec.rb