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 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
@@ -1 +1,5 @@
1
1
 
2
+ = Currency To Do List
3
+
4
+ * Refactor all configuration class variables into Currency::Config
5
+ * Refactor all cached values into objects that can be reinstantiated on a per-thread basis
@@ -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 ||= self.new
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[:currency_config] ||= self.default || (raise ::Currency::Exception::UndefinedConfig.new("Currency::Config.default not defined"))
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[:currency_config] = x
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 ||= Proc.new { |x| x }
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
- # External representation mixin
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
- # External representation mixin
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
- # External representation mixin
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
- # External representation mixin
32
+
33
33
  class String
34
34
  # Exact conversion to Money representation value.
35
35
  def Money_rep(currency, time = nil)
@@ -48,6 +48,10 @@ class Currency::Currency
48
48
  self.code = code
49
49
  self.symbol = symbol
50
50
  self.scale = scale
51
+
52
+ @formatter =
53
+ @parser =
54
+ nil
51
55
  end
52
56
 
53
57
 
@@ -1,5 +1,5 @@
1
1
  module Currency
2
- CurrencyVersion = '0.4.2'
2
+ CurrencyVersion = '0.4.3'
3
3
  end
4
4
  # DO NOT EDIT
5
5
  # This file is auto-generated by build scripts.
@@ -6,7 +6,7 @@ module Currency::Exception
6
6
  class Base < ::Exception
7
7
  end
8
8
 
9
- # Error during string parsing.
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
@@ -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 conversions.
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
- rr.save!
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
- # This is for demonstration purposes.
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').each do | 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].text.to_f
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].text
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
- @rate_timestamp = nil
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 ParseError, "Currencies header not found" if currency.empty?
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 ParseError, "Currency not found at column #{cur_i}" unless cur
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
- raise ParseError, "Currency rates not found" if rate.keys.empty?
98
- raise ParseError, "Not all rates found" if rate.keys.size != currency.size
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 ParseError, rx.inspect
118
+ raise ParserError, rx.inspect
116
119
  false
117
120
  end
118
121
 
@@ -34,6 +34,10 @@ class Currency::Formatter
34
34
 
35
35
 
36
36
  def initialize(opts = @@empty_hash)
37
+ @template =
38
+ @template_proc =
39
+ nil
40
+
37
41
  opts.each_pair{ | k, v | self.send("#{k}=", v) }
38
42
  end
39
43
 
@@ -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(100) => "$1.00 USD"
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
@@ -21,7 +21,8 @@ class Currency::Parser
21
21
  @@default = nil
22
22
  # Get the default Formatter.
23
23
  def self.default
24
- @@default || self.new
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
- #$stderr.puts "historical rates = #{rates.inspect}"
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
- #$stderr.puts "historical rates = #{rates.inspect}"
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::Money('1234.56', :USD, :now)
115
- #$stderr.puts "m_usd = #{m_usd.to_s(:code => true)}"
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
- #$stderr.puts "m_eur = #{m_eur.to_s(:code => true)}"
120
+ # $stderr.puts "m_eur = #{m_eur.to_s(:code => true)}"
118
121
 
119
- end
122
+ end
120
123
 
121
124
 
122
125
  def assert_h_rates(rates, writer = nil)
@@ -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 XE Exchange.
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.2
7
- date: 2007-03-11 00:00:00 -06:00
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