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 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