currency 0.4.2 → 0.4.3
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.
- 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
|