currency 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Releases.txt +8 -0
- data/TODO.txt +4 -0
- data/lib/currency/config.rb +18 -4
- data/lib/currency/core_extensions.rb +4 -4
- data/lib/currency/currency.rb +4 -0
- data/lib/currency/currency_version.rb +1 -1
- data/lib/currency/exception.rb +9 -1
- data/lib/currency/exchange.rb +1 -1
- data/lib/currency/exchange/rate.rb +11 -0
- data/lib/currency/exchange/rate/deriver.rb +6 -0
- data/lib/currency/exchange/rate/source/historical/writer.rb +9 -1
- data/lib/currency/exchange/rate/source/new_york_fed.rb +20 -4
- data/lib/currency/exchange/rate/source/provider.rb +10 -0
- data/lib/currency/exchange/rate/source/xe.rb +12 -9
- data/lib/currency/formatter.rb +4 -0
- data/lib/currency/money.rb +4 -3
- data/lib/currency/parser.rb +5 -1
- data/test/historical_writer_test.rb +11 -8
- data/test/new_york_fed_test.rb +18 -2
- metadata +2 -2
data/Releases.txt
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
= Currency Release History
|
2
2
|
|
3
|
+
== Release 0.4.3: 2007/04/01
|
4
|
+
|
5
|
+
* Added available? checks for NewYorkFed.
|
6
|
+
* Created new UnavailableRates exception for rate providers that return no rates (New York Fed on weekends).
|
7
|
+
* Fixed xe.com rate dates.
|
8
|
+
* Refactored exceptions.
|
9
|
+
* Fixed comments.
|
10
|
+
|
3
11
|
== Release 0.4.2: 2007/03/11
|
4
12
|
|
5
13
|
* Missing Manifest changes
|
data/TODO.txt
CHANGED
data/lib/currency/config.rb
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
# The Currency::Config class is responsible for
|
5
5
|
# maintaining global configuration for the Currency package.
|
6
6
|
#
|
7
|
+
# TO DO:
|
8
|
+
#
|
9
|
+
# Migrate all class variable configurations to this object.
|
7
10
|
class Currency::Config
|
8
11
|
@@default = nil
|
9
12
|
|
@@ -12,7 +15,8 @@ class Currency::Config
|
|
12
15
|
# If one is not specfied an instance is
|
13
16
|
# created. This is a global, not thread-local.
|
14
17
|
def self.default
|
15
|
-
@@default ||=
|
18
|
+
@@default ||=
|
19
|
+
self.new
|
16
20
|
end
|
17
21
|
|
18
22
|
# Sets the default Currency::Config object.
|
@@ -26,13 +30,15 @@ class Currency::Config
|
|
26
30
|
# If #current= has not been called and #default= has not been called,
|
27
31
|
# then UndefinedExchange is raised.
|
28
32
|
def self.current
|
29
|
-
Thread.current[:
|
33
|
+
Thread.current[:Currency__Config] ||=
|
34
|
+
self.default ||
|
35
|
+
(raise ::Currency::Exception::UndefinedConfig.new("Currency::Config.default not defined"))
|
30
36
|
end
|
31
37
|
|
32
38
|
# Sets the current Currency::Config object used
|
33
39
|
# in the current thread.
|
34
40
|
def self.current=(x)
|
35
|
-
Thread.current[:
|
41
|
+
Thread.current[:Currency__Config] = x
|
36
42
|
end
|
37
43
|
|
38
44
|
# Clones the current configuration and makes it current
|
@@ -56,11 +62,19 @@ class Currency::Config
|
|
56
62
|
end
|
57
63
|
|
58
64
|
|
65
|
+
@@identity = Proc.new { |x| x } # :nodoc:
|
59
66
|
|
67
|
+
# Returns the current Float conversion filter.
|
68
|
+
# Can be used to set rounding or truncation policies when converting
|
69
|
+
# Float values to Money values.
|
70
|
+
# Defaults to an identity function.
|
71
|
+
# See Float#Money_rep.
|
60
72
|
def float_ref_filter
|
61
|
-
@float_ref_filter ||=
|
73
|
+
@float_ref_filter ||=
|
74
|
+
@@identity
|
62
75
|
end
|
63
76
|
|
77
|
+
# Sets the current Float conversion filter.
|
64
78
|
def float_ref_filter=(x)
|
65
79
|
@float_ref_filter = x
|
66
80
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# See LICENSE.txt for details.
|
3
3
|
|
4
4
|
|
5
|
-
|
5
|
+
|
6
6
|
class Object
|
7
7
|
# Exact conversion to Money representation value.
|
8
8
|
def money(*opts)
|
@@ -11,7 +11,7 @@ class Object
|
|
11
11
|
end
|
12
12
|
|
13
13
|
|
14
|
-
|
14
|
+
|
15
15
|
class Integer
|
16
16
|
# Exact conversion to Money representation value.
|
17
17
|
def Money_rep(currency, time = nil)
|
@@ -20,7 +20,7 @@ class Integer
|
|
20
20
|
end
|
21
21
|
|
22
22
|
|
23
|
-
|
23
|
+
|
24
24
|
class Float
|
25
25
|
# Inexact conversion to Money representation value.
|
26
26
|
def Money_rep(currency, time = nil)
|
@@ -29,7 +29,7 @@ class Float
|
|
29
29
|
end
|
30
30
|
|
31
31
|
|
32
|
-
|
32
|
+
|
33
33
|
class String
|
34
34
|
# Exact conversion to Money representation value.
|
35
35
|
def Money_rep(currency, time = nil)
|
data/lib/currency/currency.rb
CHANGED
data/lib/currency/exception.rb
CHANGED
@@ -6,7 +6,7 @@ module Currency::Exception
|
|
6
6
|
class Base < ::Exception
|
7
7
|
end
|
8
8
|
|
9
|
-
# Error during
|
9
|
+
# Error during parsing of Money values from String.
|
10
10
|
class InvalidMoneyString < Base
|
11
11
|
end
|
12
12
|
|
@@ -34,6 +34,14 @@ module Currency::Exception
|
|
34
34
|
class UnknownRate < Base
|
35
35
|
end
|
36
36
|
|
37
|
+
# Error if an Exchange Rate Source.
|
38
|
+
class RateSourceError < Base
|
39
|
+
end
|
40
|
+
|
41
|
+
# Error if an Exchange Rate Source cannot supply any rates.
|
42
|
+
class UnavailableRates < Base
|
43
|
+
end
|
44
|
+
|
37
45
|
# Error if an Exchange::Rate is not valid.
|
38
46
|
class InvalidRate < Base
|
39
47
|
end
|
data/lib/currency/exchange.rb
CHANGED
@@ -27,7 +27,7 @@ module Currency::Exchange
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# Returns the current Currency::Exchange object used during
|
30
|
-
# explicit and implicit Money
|
30
|
+
# explicit and implicit Money trading.
|
31
31
|
#
|
32
32
|
# If #current= has not been called and #default= has not been called,
|
33
33
|
# then UndefinedExchange is raised.
|
@@ -60,6 +60,17 @@ class Currency::Exchange::Rate
|
|
60
60
|
@date = date
|
61
61
|
@derived = derived
|
62
62
|
@reciprocal = reciprocal
|
63
|
+
|
64
|
+
#
|
65
|
+
@rate_avg =
|
66
|
+
@rate_samples =
|
67
|
+
@rate_lo =
|
68
|
+
@rate_hi =
|
69
|
+
@rate_date_0 =
|
70
|
+
@rate_date_1 =
|
71
|
+
@date_0 =
|
72
|
+
@date_1 =
|
73
|
+
nil
|
63
74
|
|
64
75
|
if opts
|
65
76
|
opts.each_pair do | k, v |
|
@@ -140,6 +140,12 @@ class Currency::Exchange::Rate::Deriver < Currency::Exchange::Rate::Source::Base
|
|
140
140
|
end
|
141
141
|
|
142
142
|
|
143
|
+
# Returns true if the underlying rate provider is available.
|
144
|
+
def available?(time = nil)
|
145
|
+
source.available?(time)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
143
149
|
end # class
|
144
150
|
|
145
151
|
|
@@ -3,6 +3,10 @@ require 'currency/exchange/rate/source/historical'
|
|
3
3
|
|
4
4
|
# Responsible for writing historical rates from a rate source.
|
5
5
|
class Currency::Exchange::Rate::Source::Historical::Writer
|
6
|
+
|
7
|
+
# Error during handling of historical rates.
|
8
|
+
class Error < ::Currency::Exception::Base; end
|
9
|
+
|
6
10
|
# The source of rates.
|
7
11
|
attr_accessor :source
|
8
12
|
|
@@ -187,7 +191,11 @@ class Currency::Exchange::Rate::Source::Historical::Writer
|
|
187
191
|
if existing_rate
|
188
192
|
stored_h_rates << existing_rate # Already existed.
|
189
193
|
else
|
190
|
-
|
194
|
+
begin
|
195
|
+
rr.save!
|
196
|
+
rescue Object => err
|
197
|
+
raise Error, "During save of #{rr.inspect} : #{err.inspect}"
|
198
|
+
end
|
191
199
|
stored_h_rates << rr # Written.
|
192
200
|
end
|
193
201
|
end
|
@@ -11,7 +11,7 @@ require 'rexml/document'
|
|
11
11
|
# Connects to http://www.newyorkfed.org/markets/fxrates/FXtoXML.cfm
|
12
12
|
# ?FEXdate=2007%2D02%2D14%2000%3A00%3A00%2E0&FEXtime=1200 and parses XML.
|
13
13
|
#
|
14
|
-
#
|
14
|
+
# No rates are available on Saturday and Sunday.
|
15
15
|
#
|
16
16
|
class Currency::Exchange::Rate::Source::NewYorkFed < ::Currency::Exchange::Rate::Source::Provider
|
17
17
|
# Defines the pivot currency for http://xe.com/.
|
@@ -30,6 +30,13 @@ class Currency::Exchange::Rate::Source::NewYorkFed < ::Currency::Exchange::Rate:
|
|
30
30
|
end
|
31
31
|
|
32
32
|
|
33
|
+
# New York Fed rates are not available on Saturday and Sunday.
|
34
|
+
def available?(time = nil)
|
35
|
+
time ||= Time.now
|
36
|
+
! [0, 6].include?(time.wday) ? true : false
|
37
|
+
end
|
38
|
+
|
39
|
+
|
33
40
|
def clear_rates
|
34
41
|
@raw_rates = nil
|
35
42
|
super
|
@@ -53,16 +60,24 @@ class Currency::Exchange::Rate::Source::NewYorkFed < ::Currency::Exchange::Rate:
|
|
53
60
|
$stderr.puts "#{self}: parse_rates: data =\n#{data}" if @verbose
|
54
61
|
|
55
62
|
doc = REXML::Document.new(data).root
|
56
|
-
doc.elements.to_a('//frbny:Series')
|
63
|
+
x_series = doc.elements.to_a('//frbny:Series')
|
64
|
+
raise ParserError, "no UNIT attribute" unless x_series
|
65
|
+
x_series.each do | series |
|
57
66
|
c1 = series.attributes['UNIT'] # WHAT TO DO WITH @UNIT_MULT?
|
67
|
+
raise ParserError, "no UNIT attribute" unless c1
|
58
68
|
c1 = c1.upcase.intern
|
59
69
|
|
60
70
|
c2 = series.elements.to_a('frbny:Key/frbny:CURR')[0].text
|
71
|
+
raise ParserError, "no frbny:CURR element" unless c2
|
61
72
|
c2 = c2.upcase.intern
|
62
73
|
|
63
|
-
rate = series.elements.to_a('frbny:Obs/frbny:OBS_VALUE')[0]
|
74
|
+
rate = series.elements.to_a('frbny:Obs/frbny:OBS_VALUE')[0]
|
75
|
+
raise ParserError, 'no frbny:OBS_VALUE' unless rate
|
76
|
+
rate = rate.text.to_f
|
64
77
|
|
65
|
-
date = series.elements.to_a('frbny:Obs/frbny:TIME_PERIOD')[0]
|
78
|
+
date = series.elements.to_a('frbny:Obs/frbny:TIME_PERIOD')[0]
|
79
|
+
raise ParserError, 'no frbny:TIME_PERIOD' unless date
|
80
|
+
date = date.text
|
66
81
|
date = Time.parse("#{date} 12:00:00 -05:00") # USA NY => EST
|
67
82
|
|
68
83
|
rates << new_rate(c1, c2, rate, date)
|
@@ -72,6 +87,7 @@ class Currency::Exchange::Rate::Source::NewYorkFed < ::Currency::Exchange::Rate:
|
|
72
87
|
end
|
73
88
|
|
74
89
|
# $stderr.puts "rates = #{rates.inspect}"
|
90
|
+
raise ::Currency::Exception::UnavailableRates, "No rates found in #{get_uri.inspect}" if rates.empty?
|
75
91
|
|
76
92
|
rates
|
77
93
|
end
|
@@ -7,6 +7,10 @@ require 'currency/exchange/rate/source'
|
|
7
7
|
# Base class for rate data providers.
|
8
8
|
# Assumes that rate sources provide more than one rate per query.
|
9
9
|
class Currency::Exchange::Rate::Source::Provider < Currency::Exchange::Rate::Source::Base
|
10
|
+
|
11
|
+
# Error during parsing of rates.
|
12
|
+
class ParserError < ::Currency::Exception::RateSourceError; end
|
13
|
+
|
10
14
|
# The URI used to access the rate source.
|
11
15
|
attr_accessor :uri
|
12
16
|
|
@@ -99,6 +103,12 @@ class Currency::Exchange::Rate::Source::Provider < Currency::Exchange::Rate::Sou
|
|
99
103
|
|
100
104
|
alias :get_rate_base :get_rate
|
101
105
|
|
106
|
+
|
107
|
+
# Returns true if a rate provider is available.
|
108
|
+
def available?(time = nil)
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
102
112
|
end # class
|
103
113
|
|
104
114
|
|
@@ -11,10 +11,7 @@ require 'open-uri'
|
|
11
11
|
# Connects to http://xe.com and parses "XE.com Quick Cross Rates"
|
12
12
|
# from home page HTML.
|
13
13
|
#
|
14
|
-
# This is for demonstration purposes.
|
15
|
-
#
|
16
14
|
class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source::Provider
|
17
|
-
class ParserError < ::Currency::Exception::Base; end
|
18
15
|
|
19
16
|
# Defines the pivot currency for http://xe.com/.
|
20
17
|
PIVOT_CURRENCY = :USD
|
@@ -57,7 +54,10 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
57
54
|
|
58
55
|
@lines = data = data.split(/\n/);
|
59
56
|
|
60
|
-
|
57
|
+
# xe.com no longer gives date/time.
|
58
|
+
# Remove usecs.
|
59
|
+
time = Time.at(Time.new.to_i).getutc
|
60
|
+
@rate_timestamp = time
|
61
61
|
|
62
62
|
eat_lines_until /More currencies\.\.\.<\/a>/i
|
63
63
|
eat_lines_until /^\s*<tr>/i
|
@@ -73,7 +73,7 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
73
73
|
$stderr.puts "Found currency header: #{cur.inspect} at #{cur_i}" if @verbose
|
74
74
|
end
|
75
75
|
end
|
76
|
-
raise
|
76
|
+
raise ParserError, "Currencies header not found" if currency.empty?
|
77
77
|
|
78
78
|
|
79
79
|
# Skip until "1 USD ="
|
@@ -87,18 +87,21 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
87
87
|
usd_to_cur = md[1].to_f
|
88
88
|
cur_i = cur_i + 1
|
89
89
|
cur = currency[cur_i]
|
90
|
-
raise
|
90
|
+
raise ParserError, "Currency not found at column #{cur_i}" unless cur
|
91
91
|
next if cur.to_s == PIVOT_CURRENCY.to_s
|
92
92
|
(rate[PIVOT_CURRENCY] ||= {})[cur] = usd_to_cur
|
93
93
|
(rate[cur] ||= { })[PIVOT_CURRENCY] ||= 1.0 / usd_to_cur
|
94
94
|
$stderr.puts "#{cur.inspect} => #{usd_to_cur}" if @verbose
|
95
95
|
end
|
96
96
|
end
|
97
|
-
|
98
|
-
raise
|
97
|
+
|
98
|
+
raise ::Currency::Exception::UnavailableRates, "No rates found in #{get_uri.inspect}" if rate.keys.empty?
|
99
|
+
raise ParserError, "Not all rates found" if rate.keys.size != currency.size
|
99
100
|
|
100
101
|
@lines = @line = nil
|
101
102
|
|
103
|
+
raise ParserError, "Rate date not found" unless @rate_timestamp
|
104
|
+
|
102
105
|
rate
|
103
106
|
end
|
104
107
|
|
@@ -112,7 +115,7 @@ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source:
|
|
112
115
|
end
|
113
116
|
yield @line if block_given?
|
114
117
|
end
|
115
|
-
raise
|
118
|
+
raise ParserError, rx.inspect
|
116
119
|
false
|
117
120
|
end
|
118
121
|
|
data/lib/currency/formatter.rb
CHANGED
data/lib/currency/money.rb
CHANGED
@@ -98,11 +98,12 @@ class Currency::Money
|
|
98
98
|
|
99
99
|
|
100
100
|
# Construct from post-scaled internal representation.
|
101
|
-
# using the same currency
|
101
|
+
# using the same currency.
|
102
102
|
#
|
103
103
|
# x = Currency.Money("1.98", :USD)
|
104
|
-
# x.new_rep(
|
104
|
+
# x.new_rep(123) => USD $1.23
|
105
105
|
#
|
106
|
+
# time defaults to self.time.
|
106
107
|
def new_rep(r, time = nil)
|
107
108
|
time ||= @time
|
108
109
|
x = self.class.new(0, @currency, time)
|
@@ -177,7 +178,7 @@ class Currency::Money
|
|
177
178
|
end
|
178
179
|
|
179
180
|
# Compares Money values.
|
180
|
-
# Will convert x currency before comparision.
|
181
|
+
# Will convert x to self.currency before comparision.
|
181
182
|
def <=>(x)
|
182
183
|
if @currency == x.currency
|
183
184
|
@rep <=> x.rep
|
data/lib/currency/parser.rb
CHANGED
@@ -21,7 +21,8 @@ class Currency::Parser
|
|
21
21
|
@@default = nil
|
22
22
|
# Get the default Formatter.
|
23
23
|
def self.default
|
24
|
-
@@default
|
24
|
+
@@default ||=
|
25
|
+
self.new
|
25
26
|
end
|
26
27
|
|
27
28
|
|
@@ -32,6 +33,9 @@ class Currency::Parser
|
|
32
33
|
|
33
34
|
|
34
35
|
def initialize(opt = { })
|
36
|
+
@time =
|
37
|
+
@currency =
|
38
|
+
nil
|
35
39
|
opt.each_pair{ | k, v | self.send("#{k}=", v) }
|
36
40
|
end
|
37
41
|
|
@@ -77,6 +77,7 @@ class HistoricalWriterTest < ArTestBase
|
|
77
77
|
def writer_src2
|
78
78
|
writer = test_writer
|
79
79
|
writer.source = @src2
|
80
|
+
return unless writer.source.available?
|
80
81
|
rates = writer.write_rates
|
81
82
|
assert_not_nil rates
|
82
83
|
assert rates.size > 0
|
@@ -105,18 +106,20 @@ class HistoricalWriterTest < ArTestBase
|
|
105
106
|
deriver = Exchange::Rate::Deriver.new(:source => source)
|
106
107
|
Exchange::Rate::Source.default = deriver
|
107
108
|
|
108
|
-
rates = source.get_raw_rates
|
109
|
-
|
109
|
+
assert_not_nil rates = source.get_raw_rates
|
110
|
+
assert ! rates.empty?
|
111
|
+
# $stderr.puts "historical rates = #{rates.inspect}"
|
110
112
|
|
111
|
-
rates = source.get_rates
|
112
|
-
|
113
|
+
assert_not_nil rates = source.get_rates
|
114
|
+
assert ! rates.empty?
|
115
|
+
# $stderr.puts "historical rates = #{rates.inspect}"
|
113
116
|
|
114
|
-
assert_not_nil m_usd = ::Currency
|
115
|
-
|
117
|
+
assert_not_nil m_usd = ::Currency.Money('1234.56', :USD, :now)
|
118
|
+
# $stderr.puts "m_usd = #{m_usd.to_s(:code => true)}"
|
116
119
|
assert_not_nil m_eur = m_usd.convert(:EUR)
|
117
|
-
|
120
|
+
# $stderr.puts "m_eur = #{m_eur.to_s(:code => true)}"
|
118
121
|
|
119
|
-
|
122
|
+
end
|
120
123
|
|
121
124
|
|
122
125
|
def assert_h_rates(rates, writer = nil)
|
data/test/new_york_fed_test.rb
CHANGED
@@ -14,16 +14,30 @@ class NewYorkFedTest < TestBase
|
|
14
14
|
end
|
15
15
|
|
16
16
|
|
17
|
+
@@available = nil
|
18
|
+
|
19
|
+
# New York Fed rates are not available on Saturday and Sunday.
|
20
|
+
def available?
|
21
|
+
if @@available == nil
|
22
|
+
@@available = @source.available?
|
23
|
+
STDERR.puts "Warning: NewYorkFed unavailable on Saturday and Sunday, skipping tests."
|
24
|
+
end
|
25
|
+
@@available
|
26
|
+
end
|
27
|
+
|
28
|
+
|
17
29
|
def get_rate_source
|
18
|
-
# Force
|
30
|
+
# Force NewYorkFed Exchange.
|
19
31
|
verbose = false
|
20
|
-
source = Exchange::Rate::Source::NewYorkFed.new(:verbose => verbose)
|
32
|
+
source = @source = Exchange::Rate::Source::NewYorkFed.new(:verbose => verbose)
|
21
33
|
deriver = Exchange::Rate::Deriver.new(:source => source, :verbose => source.verbose)
|
22
34
|
end
|
23
35
|
|
24
36
|
|
25
37
|
|
26
38
|
def test_usd_cad
|
39
|
+
return unless available?
|
40
|
+
|
27
41
|
assert_not_nil rates = Exchange::Rate::Source.default.source.raw_rates
|
28
42
|
assert_not_nil rates[:USD]
|
29
43
|
assert_not_nil usd_cad = rates[:USD][:CAD]
|
@@ -38,6 +52,8 @@ class NewYorkFedTest < TestBase
|
|
38
52
|
|
39
53
|
|
40
54
|
def test_cad_eur
|
55
|
+
return unless available?
|
56
|
+
|
41
57
|
assert_not_nil rates = Exchange::Rate::Source.default.source.raw_rates
|
42
58
|
assert_not_nil rates[:USD]
|
43
59
|
assert_not_nil usd_cad = rates[:USD][:CAD]
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: currency
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.4.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.4.3
|
7
|
+
date: 2007-04-01 00:00:00 -04:00
|
8
8
|
summary: "Currency models currencies, monetary values, foreign exchanges rates. Pulls live and historical rates from http://xe.com/, http://newyorkfed.org/, http://thefinancials.com/. Can store/retrieve historical rate data from database using ActiveRecord. Can store/retrieve Money values using ActiveRecord. For more details, see: http://currency.rubyforge.org/ http://currency.rubyforge.org/files/README.txt"
|
9
9
|
require_paths:
|
10
10
|
- lib
|