mumboe-currency 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/bin/currency_historical_rate_load +105 -0
  2. data/examples/ex1.rb +13 -0
  3. data/examples/xe1.rb +20 -0
  4. data/lib/currency/active_record.rb +265 -0
  5. data/lib/currency/config.rb +91 -0
  6. data/lib/currency/core_extensions.rb +41 -0
  7. data/lib/currency/currency/factory.rb +228 -0
  8. data/lib/currency/currency.rb +175 -0
  9. data/lib/currency/currency_version.rb +6 -0
  10. data/lib/currency/exception.rb +119 -0
  11. data/lib/currency/exchange/rate/deriver.rb +157 -0
  12. data/lib/currency/exchange/rate/source/base.rb +166 -0
  13. data/lib/currency/exchange/rate/source/failover.rb +63 -0
  14. data/lib/currency/exchange/rate/source/federal_reserve.rb +160 -0
  15. data/lib/currency/exchange/rate/source/historical/rate.rb +184 -0
  16. data/lib/currency/exchange/rate/source/historical/rate_loader.rb +186 -0
  17. data/lib/currency/exchange/rate/source/historical/writer.rb +220 -0
  18. data/lib/currency/exchange/rate/source/historical.rb +79 -0
  19. data/lib/currency/exchange/rate/source/new_york_fed.rb +127 -0
  20. data/lib/currency/exchange/rate/source/provider.rb +120 -0
  21. data/lib/currency/exchange/rate/source/test.rb +50 -0
  22. data/lib/currency/exchange/rate/source/the_financials.rb +191 -0
  23. data/lib/currency/exchange/rate/source/timed_cache.rb +198 -0
  24. data/lib/currency/exchange/rate/source/xe.rb +165 -0
  25. data/lib/currency/exchange/rate/source.rb +89 -0
  26. data/lib/currency/exchange/rate.rb +214 -0
  27. data/lib/currency/exchange/time_quantitizer.rb +111 -0
  28. data/lib/currency/exchange.rb +50 -0
  29. data/lib/currency/formatter.rb +290 -0
  30. data/lib/currency/macro.rb +321 -0
  31. data/lib/currency/money.rb +295 -0
  32. data/lib/currency/money_helper.rb +13 -0
  33. data/lib/currency/parser.rb +151 -0
  34. data/lib/currency.rb +143 -0
  35. data/test/string_test.rb +54 -0
  36. data/test/test_base.rb +44 -0
  37. metadata +90 -0
@@ -0,0 +1,120 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'currency/exchange/rate/source'
5
+
6
+
7
+ # Base class for rate data providers.
8
+ # Assumes that rate sources provide more than one rate per query.
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
+
14
+ # The URI used to access the rate source.
15
+ attr_accessor :uri
16
+
17
+ # The URI path relative to uri used to access the rate source.
18
+ attr_accessor :uri_path
19
+
20
+ # The Time used to query the rate source.
21
+ # Typically set by #load_rates.
22
+ attr_accessor :date
23
+
24
+ # The name is the same as its #uri.
25
+ alias :name :uri
26
+
27
+ def initialize(*args)
28
+ super
29
+ @rates = { }
30
+ end
31
+
32
+ # Returns the date to query for rates.
33
+ # Defaults to yesterday.
34
+ def date
35
+ @date || (Time.now - 24 * 60 * 60) # yesterday.
36
+ end
37
+
38
+
39
+ # Returns year of query date.
40
+ def date_YYYY
41
+ '%04d' % date.year
42
+ end
43
+
44
+
45
+ # Return month of query date.
46
+ def date_MM
47
+ '%02d' % date.month
48
+ end
49
+
50
+
51
+ # Returns day of query date.
52
+ def date_DD
53
+ '%02d' % date.day
54
+ end
55
+
56
+
57
+ # Returns the URI string as evaluated with this object.
58
+ def get_uri
59
+ uri = self.uri
60
+ uri = "\"#{uri}\""
61
+ uri = instance_eval(uri)
62
+ $stderr.puts "#{self}: uri = #{uri.inspect}" if @verbose
63
+ uri
64
+ end
65
+
66
+
67
+ # Returns the URI content.
68
+ def get_page_content
69
+ data = open(get_uri) { |data| data.read }
70
+
71
+ data
72
+ end
73
+
74
+
75
+ # Clear cached rates from this source.
76
+ def clear_rates
77
+ @rates.clear
78
+ super
79
+ end
80
+
81
+
82
+ # Returns current base Rates or calls load_rates to load them from the source.
83
+ def rates(time = nil)
84
+ time = time && normalize_time(time)
85
+ @rates["#{time}"] ||= load_rates(time)
86
+ end
87
+
88
+
89
+ # Returns an array of base Rates from the rate source.
90
+ #
91
+ # Subclasses must define this method.
92
+ def load_rates(time = nil)
93
+ raise Currency::Exception::SubclassResponsibility, :load_rates
94
+ end
95
+
96
+
97
+ # Return a matching base rate.
98
+ def get_rate(c1, c2, time)
99
+ rates.each do | rate |
100
+ return rate if
101
+ rate.c1 == c1 &&
102
+ rate.c2 == c2 &&
103
+ (! time || normalize_time(rate.date) == time)
104
+ end
105
+
106
+ nil
107
+ end
108
+
109
+ alias :get_rate_base :get_rate
110
+
111
+
112
+ # Returns true if a rate provider is available.
113
+ def available?(time = nil)
114
+ true
115
+ end
116
+
117
+ end # class
118
+
119
+
120
+
@@ -0,0 +1,50 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'currency/exchange/rate/source/provider'
5
+
6
+ # This class is a test Rate Source.
7
+ # It can provide only fixed rates between USD, CAD and EUR.
8
+ # Used only for test purposes.
9
+ # DO NOT USE THESE RATES FOR A REAL APPLICATION.
10
+ class Currency::Exchange::Rate::Source::Test < Currency::Exchange::Rate::Source::Provider
11
+ @@instance = nil
12
+
13
+ # Returns a singleton instance.
14
+ def self.instance(*opts)
15
+ @@instance ||= self.new(*opts)
16
+ end
17
+
18
+
19
+ def initialize(*opts)
20
+ self.uri = 'none://localhost/Test'
21
+ super(*opts)
22
+ end
23
+
24
+
25
+ def name
26
+ 'Test'
27
+ end
28
+
29
+ # Test rate from :USD to :CAD.
30
+ def self.USD_CAD; 1.1708; end
31
+
32
+
33
+ # Test rate from :USD to :EUR.
34
+ def self.USD_EUR; 0.7737; end
35
+
36
+
37
+ # Test rate from :USD to :GBP.
38
+ def self.USD_GBP; 0.5098; end
39
+
40
+
41
+ # Returns test Rate for USD to [ CAD, EUR, GBP ].
42
+ def rates
43
+ [ new_rate(:USD, :CAD, self.class.USD_CAD),
44
+ new_rate(:USD, :EUR, self.class.USD_EUR),
45
+ new_rate(:USD, :GBP, self.class.USD_GBP) ]
46
+ end
47
+
48
+ end # class
49
+
50
+
@@ -0,0 +1,191 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'currency/exchange/rate/source/base'
5
+
6
+ require 'net/http'
7
+ require 'open-uri'
8
+ require 'rexml/document'
9
+
10
+
11
+ # Connects to http://www.thefinancials.com and parses XML.
12
+ #
13
+ # This is for demonstration purposes.
14
+ #
15
+ class Currency::Exchange::Rate::Source::TheFinancials < ::Currency::Exchange::Rate::Source::Provider
16
+ # Defines the pivot currency for http://thefinancials.com/.
17
+ PIVOT_CURRENCY = :USD
18
+
19
+ def initialize(*opt)
20
+ @raw_rates = nil
21
+ self.uri_path = 'syndicated/UNKNOWN/fxrates.xml'
22
+ super(*opt)
23
+ self.uri = "http://www.thefinancials.com/#{self.uri_path}"
24
+ end
25
+
26
+
27
+ # Returns 'thefinancials.com'.
28
+ def name
29
+ 'thefinancials.org'
30
+ end
31
+
32
+
33
+ # def get_page_content
34
+ # test_content
35
+ # end
36
+
37
+
38
+ def clear_rates
39
+ @raw_rates = nil
40
+ super
41
+ end
42
+
43
+
44
+ def raw_rates
45
+ rates
46
+ @raw_rates
47
+ end
48
+
49
+
50
+ # Parses XML for rates.
51
+ def parse_rates(data = nil)
52
+ data = get_page_content unless data
53
+
54
+ rates = [ ]
55
+
56
+ @raw_rates = { }
57
+
58
+ # $stderr.puts "parse_rates: data = #{data}"
59
+
60
+ doc = REXML::Document.new(data).root
61
+ doc.elements.to_a('//record').each do | record |
62
+ c1_c2 = record.elements.to_a('symbol')[0].text
63
+ md = /([A-Z][A-Z][A-Z]).*?([A-Z][A-Z][A-Z])/.match(c1_c2)
64
+ c1, c2 = md[1], md[2]
65
+
66
+ c1 = c1.upcase.intern
67
+ c2 = c2.upcase.intern
68
+
69
+ rate = record.elements.to_a('last')[0].text.to_f
70
+
71
+ date = record.elements.to_a('date')[0].text
72
+ date = Time.parse("#{date} 12:00:00 -05:00") # USA NY => EST
73
+
74
+ rates << new_rate(c1, c2, rate, date)
75
+
76
+ (@raw_rates[c1] ||= { })[c2] ||= rate
77
+ end
78
+
79
+ rates
80
+ end
81
+
82
+
83
+ # Return a list of known base rates.
84
+ def load_rates(time = nil)
85
+ self.date = time
86
+ parse_rates
87
+ end
88
+
89
+
90
+ def test_content
91
+ <<EOF
92
+ <?xml version="1.0" ?>
93
+ <TFCRecords>
94
+ <record>
95
+ <symbol>USD/EUR</symbol>
96
+ <date>10/25/2001</date>
97
+ <last>1.115822</last>
98
+ </record>
99
+ <record>
100
+ <symbol>USD/AUD</symbol>
101
+ <date>10/25/2001</date>
102
+ <last>1.975114</last>
103
+ </record>
104
+ <record>
105
+ <symbol>USD/CAD</symbol>
106
+ <date>10/25/2001</date>
107
+ <last>1.57775</last>
108
+ </record>
109
+ <record>
110
+ <symbol>USD/CNY</symbol>
111
+ <date>10/25/2001</date>
112
+ <last>8.2769</last>
113
+ </record>
114
+ <record>
115
+ <symbol>USD/ESP</symbol>
116
+ <date>10/25/2001</date>
117
+ <last>185.65725</last>
118
+ </record>
119
+ <record>
120
+ <symbol>USD/GBP</symbol>
121
+ <date>10/25/2001</date>
122
+ <last>0.698849867830019</last>
123
+ </record>
124
+ <record>
125
+ <symbol>USD/HKD</symbol>
126
+ <date>10/25/2001</date>
127
+ <last>7.7999</last>
128
+ </record>
129
+ <record>
130
+ <symbol>USD/IDR</symbol>
131
+ <date>10/25/2001</date>
132
+ <last>10265</last>
133
+ </record>
134
+ <record>
135
+ <symbol>USD/INR</symbol>
136
+ <date>10/25/2001</date>
137
+ <last>48.01</last>
138
+ </record>
139
+ <record>
140
+ <symbol>USD/JPY</symbol>
141
+ <date>10/25/2001</date>
142
+ <last>122.68</last>
143
+ </record>
144
+ <record>
145
+ <symbol>USD/KRW</symbol>
146
+ <date>10/25/2001</date>
147
+ <last>1293.5</last>
148
+ </record>
149
+ <record>
150
+ <symbol>USD/MYR</symbol>
151
+ <date>10/25/2001</date>
152
+ <last>3.8</last>
153
+ </record>
154
+ <record>
155
+ <symbol>USD/NZD</symbol>
156
+ <date>10/25/2001</date>
157
+ <last>2.41485</last>
158
+ </record>
159
+ <record>
160
+ <symbol>USD/PHP</symbol>
161
+ <date>10/25/2001</date>
162
+ <last>52.05</last>
163
+ </record>
164
+ <record>
165
+ <symbol>USD/PKR</symbol>
166
+ <date>10/25/2001</date>
167
+ <last>61.6</last>
168
+ </record>
169
+ <record>
170
+ <symbol>USD/SGD</symbol>
171
+ <date>10/25/2001</date>
172
+ <last>1.82615</last>
173
+ </record>
174
+ <record>
175
+ <symbol>USD/THB</symbol>
176
+ <date>10/25/2001</date>
177
+ <last>44.88</last>
178
+ </record>
179
+ <record>
180
+ <symbol>USD/TWD</symbol>
181
+ <date>10/25/2001</date>
182
+ <last>34.54</last>
183
+ </record>
184
+ </TFCRecords>
185
+ EOF
186
+ end
187
+
188
+ end # class
189
+
190
+
191
+
@@ -0,0 +1,198 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'currency/exchange/rate/source/base'
5
+
6
+ # A timed cache for rate sources.
7
+ #
8
+ # This class should be used at the top-level of a rate source change,
9
+ # to correctly check rate dates.
10
+ #
11
+ class Currency::Exchange::Rate::Source::TimedCache < ::Currency::Exchange::Rate::Source::Base
12
+ # The rate source.
13
+ attr_accessor :source
14
+
15
+ # Defines the number of seconds rates until rates
16
+ # become invalid, causing a request of new rates.
17
+ #
18
+ # Defaults to 600 seconds.
19
+ attr_accessor :time_to_live
20
+
21
+
22
+ # Defines the number of random seconds to add before
23
+ # rates become invalid.
24
+ #
25
+ # Defaults to 30 seconds.
26
+ attr_accessor :time_to_live_fudge
27
+
28
+
29
+ # Returns the time of the last load.
30
+ attr_reader :rate_load_time
31
+
32
+
33
+ # Returns the time of the next load.
34
+ attr_reader :rate_reload_time
35
+
36
+
37
+ # Returns source's name.
38
+ def name
39
+ source.name
40
+ end
41
+
42
+
43
+ def initialize(*opt)
44
+ self.time_to_live = 600
45
+ self.time_to_live_fudge = 30
46
+ @rate_load_time = nil
47
+ @rate_reload_time = nil
48
+ @processing_rates = false
49
+ @cached_rates = { }
50
+ @cached_rates_old = nil
51
+ super(*opt)
52
+ end
53
+
54
+
55
+ # Clears current rates.
56
+ def clear_rates
57
+ @cached_rates = { }
58
+ @source.clear_rates
59
+ super
60
+ end
61
+
62
+
63
+ # Returns true if the cache of Rates
64
+ # is expired.
65
+ def expired?
66
+ if @time_to_live &&
67
+ @rate_reload_time &&
68
+ (Time.now > @rate_reload_time)
69
+
70
+ if @cached_rates
71
+ $stderr.puts "#{self}: rates expired on #{@rate_reload_time}" if @verbose
72
+
73
+ @cached_rates_old = @cached_rates
74
+ end
75
+
76
+ clear_rates
77
+
78
+ true
79
+ else
80
+ false
81
+ end
82
+ end
83
+
84
+
85
+ # Check expired? before returning a Rate.
86
+ def rate(c1, c2, time)
87
+ if expired?
88
+ clear_rates
89
+ end
90
+ super(c1, c2, time)
91
+ end
92
+
93
+
94
+ def get_rate(c1, c2, time)
95
+ # STDERR.puts "get_rate #{c1} #{c2} #{time}"
96
+ rates = load_rates(time)
97
+ # STDERR.puts "rates = #{rates.inspect}"
98
+ rate = rates && (rates.select{|x| x.c1 == c1 && x.c2 == c2}[0])
99
+ # STDERR.puts "rate = #{rate.inspect}"
100
+ rate
101
+ end
102
+
103
+
104
+ # Returns an array of all the cached Rates.
105
+ def rates(time = nil)
106
+ load_rates(time)
107
+ end
108
+
109
+
110
+ # Returns an array of all the cached Rates.
111
+ def load_rates(time = nil)
112
+ # Check expiration.
113
+ expired?
114
+
115
+ # Return rates, if cached.
116
+ return rates if rates = @cached_rates["#{time}"]
117
+
118
+ # Force load of rates.
119
+ rates = @cached_rates["#{time}"] = _load_rates_from_source(time)
120
+
121
+ # Update expiration.
122
+ _calc_rate_reload_time
123
+
124
+ return nil unless rates
125
+
126
+ # Flush old rates.
127
+ @cached_rates_old = nil
128
+
129
+ rates
130
+ end
131
+
132
+
133
+ def time_to_live=(x)
134
+ @time_to_live = x
135
+ _calc_rate_reload_time
136
+ x
137
+ end
138
+
139
+
140
+ def time_to_live_fudge=(x)
141
+ @time_to_live_fudge = x
142
+ _calc_rate_reload_time
143
+ x
144
+ end
145
+
146
+
147
+ def _calc_rate_reload_time
148
+ if @time_to_live && @rate_load_time
149
+ @rate_reload_time = @rate_load_time + (@time_to_live + (@time_to_live_fudge || 0))
150
+ $stderr.puts "#{self}: rates expire on #{@rate_reload_time}" if @verbose
151
+ end
152
+
153
+ end
154
+
155
+
156
+
157
+ def _load_rates_from_source(time = nil) # :nodoc:
158
+ rates = nil
159
+
160
+ begin
161
+ # Do not allow re-entrancy
162
+ raise Currency::Exception::InvalidReentrancy, "Reentry!" if @processing_rates
163
+
164
+ # Begin processing new rate request.
165
+ @processing_rates = true
166
+
167
+ # Clear cached Rates.
168
+ clear_rates
169
+
170
+ # Load rates from the source.
171
+ rates = source.load_rates(time)
172
+
173
+ # Compute new rate timestamp.
174
+ @rate_load_time = Time.now
175
+
176
+ # STDERR.puts "rate_load_time = #{@rate_load_time}"
177
+ ensure
178
+ # End processsing new rate request.
179
+ @processing_rates = false
180
+
181
+ end
182
+
183
+ # STDERR.puts "_load_rates => #{rates.inspect}"
184
+
185
+ rates
186
+ end
187
+
188
+
189
+ # Returns true if the underlying rate provider is available.
190
+ def available?(time = nil)
191
+ source.available?(time)
192
+ end
193
+
194
+
195
+ end # class
196
+
197
+
198
+
@@ -0,0 +1,165 @@
1
+ # Copyright (C) 2006-2007 Kurt Stephens <ruby-currency(at)umleta.com>
2
+ # See LICENSE.txt for details.
3
+
4
+ require 'currency/exchange/rate/source/base'
5
+
6
+ require 'net/http'
7
+ require 'open-uri'
8
+ # Cant use REXML because of missing </form> tags -- 2007/03/11
9
+ # require 'rexml/document'
10
+
11
+ # Connects to http://xe.com and parses "XE.com Quick Cross Rates"
12
+ # from home page HTML.
13
+ #
14
+ class Currency::Exchange::Rate::Source::Xe < ::Currency::Exchange::Rate::Source::Provider
15
+
16
+ # Defines the pivot currency for http://xe.com/.
17
+ PIVOT_CURRENCY = :USD
18
+
19
+ def initialize(*opt)
20
+ self.uri = 'http://xe.com/'
21
+ self.pivot_currency = PIVOT_CURRENCY
22
+ @raw_rates = nil
23
+ super(*opt)
24
+ end
25
+
26
+
27
+ # Returns 'xe.com'.
28
+ def name
29
+ 'xe.com'
30
+ end
31
+
32
+
33
+ def clear_rates
34
+ @raw_rates = nil
35
+ super
36
+ end
37
+
38
+
39
+ # Returns a cached Hash of rates:
40
+ #
41
+ # xe.raw_rates[:USD][:CAD] => 1.0134
42
+ #
43
+ def raw_rates
44
+ # Force load of rates
45
+ @raw_rates ||= parse_page_rates
46
+ end
47
+
48
+
49
+
50
+ # Parses http://xe.com homepage HTML for
51
+ # quick rates of 10 currencies.
52
+ def parse_page_rates(data = nil)
53
+ data = get_page_content unless data
54
+
55
+ @lines = data = data.split(/\n/);
56
+
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
+
62
+ eat_lines_until /More currencies\.\.\.<\/a>/i
63
+ eat_lines_until /^\s*<tr>/i
64
+ eat_lines_until /^\s*<tr>/i
65
+
66
+ # Read first table row to get position for each currency
67
+ currency = [ ]
68
+ eat_lines_until /^\s*<\/tr>/i do
69
+ if md = /<td[^>]+?>.*?\/> ([A-Z][A-Z][A-Z])<\/td>/.match(@line)
70
+ cur = md[1].intern
71
+ cur_i = currency.size
72
+ currency.push(cur)
73
+ $stderr.puts "Found currency header: #{cur.inspect} at #{cur_i}" if @verbose
74
+ end
75
+ end
76
+ raise ParserError, "Currencies header not found" if currency.empty?
77
+
78
+
79
+ # Skip until "1 USD ="
80
+ eat_lines_until /^\s*<td[^>]+?> 1&nbsp;+USD&nbsp;=/
81
+
82
+ # Read first row of 1 USD = ...
83
+ rate = { }
84
+ cur_i = -1
85
+ eat_lines_until /^\s*<\/tr>/i do
86
+ # Grok:
87
+ #
88
+ # <td align="center" class="cur2 currencyA">114.676</td>\n
89
+ #
90
+ # AND
91
+ #
92
+ # <td align="center" class="cur2 currencyA"><div id="positionImage">0.9502\n
93
+ #
94
+ if md = /<td[^>]+?>\s*(<div[^>]+?>\s*)?(\d+\.\d+)\s*(<\/td>)?/i.match(@line)
95
+ usd_to_cur = md[2].to_f
96
+ cur_i = cur_i + 1
97
+ cur = currency[cur_i]
98
+ raise ParserError, "Currency not found at column #{cur_i}" unless cur
99
+ next if cur.to_s == PIVOT_CURRENCY.to_s
100
+ (rate[PIVOT_CURRENCY] ||= {})[cur] = usd_to_cur
101
+ (rate[cur] ||= { })[PIVOT_CURRENCY] ||= 1.0 / usd_to_cur
102
+ $stderr.puts "#{cur.inspect} => #{usd_to_cur}" if @verbose
103
+ end
104
+ end
105
+
106
+ raise ::Currency::Exception::UnavailableRates, "No rates found in #{get_uri.inspect}" if rate.keys.empty?
107
+
108
+ raise ParserError,
109
+ [
110
+ "Not all currencies found",
111
+ :expected_currences, currency,
112
+ :found_currencies, rate.keys,
113
+ :missing_currencies, currency - rate.keys,
114
+ ] if rate.keys.size != currency.size
115
+
116
+ @lines = @line = nil
117
+
118
+ raise ParserError, "Rate date not found" unless @rate_timestamp
119
+
120
+ rate
121
+ end
122
+
123
+
124
+ def eat_lines_until(rx)
125
+ until @lines.empty?
126
+ @line = @lines.shift
127
+ if md = rx.match(@line)
128
+ $stderr.puts "\nMATCHED #{@line.inspect} WITH #{rx.inspect} AT LINES:\n#{@lines[0..4].inspect}" if @verbose
129
+ return md
130
+ end
131
+ yield @line if block_given?
132
+ end
133
+
134
+ raise ParserError, [ 'eat_lines_until failed', :rx, rx ]
135
+
136
+ false
137
+ end
138
+
139
+
140
+ # Return a list of known base rates.
141
+ def load_rates(time = nil)
142
+ if time
143
+ $stderr.puts "#{self}: WARNING CANNOT SUPPLY HISTORICAL RATES" unless @time_warning
144
+ @time_warning = true
145
+ end
146
+
147
+ rates = raw_rates # Load rates
148
+ rates_pivot = rates[PIVOT_CURRENCY]
149
+ raise ::Currency::Exception::UnknownRate,
150
+ [
151
+ "Cannot get base rate #{PIVOT_CURRENCY.inspect}",
152
+ :pivot_currency, PIVOT_CURRENCY,
153
+ ] unless rates_pivot
154
+
155
+ result = rates_pivot.keys.collect do | c2 |
156
+ new_rate(PIVOT_CURRENCY, c2, rates_pivot[c2], @rate_timestamp)
157
+ end
158
+
159
+ result
160
+ end
161
+
162
+
163
+ end # class
164
+
165
+