money2 7.0.0.rc1
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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/.travis.yml +24 -0
- data/AUTHORS +126 -0
- data/CHANGELOG.md +619 -0
- data/CONTRIBUTING.md +17 -0
- data/Gemfile +16 -0
- data/LICENSE +23 -0
- data/README.md +438 -0
- data/Rakefile +17 -0
- data/config/currency_backwards_compatible.json +107 -0
- data/config/currency_iso.json +2449 -0
- data/config/currency_non_iso.json +66 -0
- data/lib/money.rb +390 -0
- data/lib/money/allocate.rb +86 -0
- data/lib/money/arithmetic.rb +233 -0
- data/lib/money/bank/base.rb +137 -0
- data/lib/money/bank/single_currency.rb +25 -0
- data/lib/money/bank/variable_exchange.rb +252 -0
- data/lib/money/class_attribute.rb +26 -0
- data/lib/money/currency.rb +402 -0
- data/lib/money/currency/heuristics.rb +150 -0
- data/lib/money/currency/loader.rb +29 -0
- data/lib/money/currency_methods.rb +139 -0
- data/lib/money/formatter.rb +404 -0
- data/lib/money/formatter/to_string.rb +9 -0
- data/lib/money/rates_store/memory.rb +120 -0
- data/lib/money/unaccent.rb +18 -0
- data/lib/money/v6_compatibility.rb +5 -0
- data/lib/money/v6_compatibility/arithmetic.rb +61 -0
- data/lib/money/v6_compatibility/bank_rounding_block.rb +38 -0
- data/lib/money/v6_compatibility/currency_id.rb +29 -0
- data/lib/money/v6_compatibility/format.rb +53 -0
- data/lib/money/v6_compatibility/fractional.rb +74 -0
- data/lib/money/version.rb +3 -0
- data/lib/money2.rb +1 -0
- data/money.gemspec +31 -0
- metadata +207 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"priority": 100,
|
4
|
+
"code": "BTC",
|
5
|
+
"name": "Bitcoin",
|
6
|
+
"symbol": "B⃦",
|
7
|
+
"subunit": "Satoshi",
|
8
|
+
"subunit_to_unit": 100000000,
|
9
|
+
"symbol_first": true,
|
10
|
+
"decimal_mark": ".",
|
11
|
+
"thousands_separator": ",",
|
12
|
+
"smallest_denomination": 1
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"priority": 100,
|
16
|
+
"code": "JEP",
|
17
|
+
"name": "Jersey Pound",
|
18
|
+
"symbol": "£",
|
19
|
+
"disambiguate_symbol": "JEP",
|
20
|
+
"subunit": "Penny",
|
21
|
+
"subunit_to_unit": 100,
|
22
|
+
"symbol_first": true,
|
23
|
+
"html_entity": "£",
|
24
|
+
"decimal_mark": ".",
|
25
|
+
"thousands_separator": ",",
|
26
|
+
"smallest_denomination": 1
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"priority": 100,
|
30
|
+
"code": "GGP",
|
31
|
+
"name": "Guernsey Pound",
|
32
|
+
"symbol": "£",
|
33
|
+
"disambiguate_symbol": "GGP",
|
34
|
+
"subunit": "Penny",
|
35
|
+
"subunit_to_unit": 100,
|
36
|
+
"symbol_first": true,
|
37
|
+
"html_entity": "£",
|
38
|
+
"decimal_mark": ".",
|
39
|
+
"thousands_separator": ",",
|
40
|
+
"smallest_denomination": 1
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"priority": 100,
|
44
|
+
"code": "IMP",
|
45
|
+
"name": "Isle of Man Pound",
|
46
|
+
"symbol": "£",
|
47
|
+
"disambiguate_symbol": "IMP",
|
48
|
+
"alternate_symbols": ["M£"],
|
49
|
+
"subunit": "Penny",
|
50
|
+
"subunit_to_unit": 100,
|
51
|
+
"symbol_first": true,
|
52
|
+
"html_entity": "£",
|
53
|
+
"decimal_mark": ".",
|
54
|
+
"thousands_separator": ",",
|
55
|
+
"smallest_denomination": 1
|
56
|
+
},
|
57
|
+
{
|
58
|
+
"priority": 100,
|
59
|
+
"code": "XFU",
|
60
|
+
"name": "UIC Franc",
|
61
|
+
"subunit_to_unit": 100,
|
62
|
+
"symbol_first": true,
|
63
|
+
"decimal_mark": ".",
|
64
|
+
"thousands_separator": ","
|
65
|
+
}
|
66
|
+
]
|
data/lib/money.rb
ADDED
@@ -0,0 +1,390 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "bigdecimal"
|
3
|
+
require "bigdecimal/util"
|
4
|
+
require "set"
|
5
|
+
require "i18n"
|
6
|
+
|
7
|
+
# "Money is any object or record that is generally accepted as payment for
|
8
|
+
# goods and services and repayment of debts in a given socio-economic context
|
9
|
+
# or country." -Wikipedia
|
10
|
+
#
|
11
|
+
# An instance of Money represents an amount of a specific currency.
|
12
|
+
#
|
13
|
+
# Money is a value object and should be treated as immutable.
|
14
|
+
#
|
15
|
+
# @see http://en.wikipedia.org/wiki/Money
|
16
|
+
class Money
|
17
|
+
require "money/class_attribute"
|
18
|
+
require "money/currency"
|
19
|
+
require "money/bank/variable_exchange"
|
20
|
+
require "money/bank/single_currency"
|
21
|
+
require "money/currency_methods"
|
22
|
+
require "money/allocate"
|
23
|
+
require "money/arithmetic"
|
24
|
+
require "money/formatter"
|
25
|
+
require "money/formatter/to_string"
|
26
|
+
autoload :V6Compatibility, "money/v6_compatibility"
|
27
|
+
|
28
|
+
extend ClassAttribute
|
29
|
+
include CurrencyMethods
|
30
|
+
include Comparable
|
31
|
+
include Allocate
|
32
|
+
include Arithmetic
|
33
|
+
|
34
|
+
# Raised when smallest denomination of a currency is not defined
|
35
|
+
class UndefinedSmallestDenomination < StandardError; end
|
36
|
+
|
37
|
+
# Class Methods
|
38
|
+
class << self
|
39
|
+
def default_currency=(val)
|
40
|
+
block =
|
41
|
+
if val.respond_to?(:call)
|
42
|
+
val
|
43
|
+
else
|
44
|
+
val = Currency.new(val)
|
45
|
+
-> { val }
|
46
|
+
end
|
47
|
+
define_singleton_method(:default_currency, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Use this to return the rounding mode. You may also pass a
|
51
|
+
# rounding mode and a block to temporarily change it. It will
|
52
|
+
# then return the results of the block instead.
|
53
|
+
#
|
54
|
+
# @param [BigDecimal::ROUND_MODE] mode
|
55
|
+
#
|
56
|
+
# @return [BigDecimal::ROUND_MODE,Yield] rounding mode or block results
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# fee = Money.rounding_mode(BigDecimal::ROUND_HALF_UP) do
|
60
|
+
# Money.new(1200) * BigDecimal.new('0.029')
|
61
|
+
# end
|
62
|
+
def rounding_mode(mode = nil)
|
63
|
+
if mode.nil?
|
64
|
+
Thread.current[:money_rounding_mode] || _rounding_mode
|
65
|
+
else
|
66
|
+
begin
|
67
|
+
prev = Thread.current[:money_rounding_mode]
|
68
|
+
Thread.current[:money_rounding_mode] = mode
|
69
|
+
yield
|
70
|
+
ensure
|
71
|
+
Thread.current[:money_rounding_mode] = prev
|
72
|
+
Thread.current[:money_rounding_mode] = nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Parses decimal and rounds it according to currency and default settings.
|
78
|
+
def prepare_d(value, currency)
|
79
|
+
value =
|
80
|
+
if value.respond_to?(:to_d)
|
81
|
+
value.is_a?(Rational) ? value.to_d(conversion_precision) : value.to_d
|
82
|
+
else
|
83
|
+
BigDecimal.new(value.to_s)
|
84
|
+
end
|
85
|
+
if infinite_precision
|
86
|
+
value
|
87
|
+
else
|
88
|
+
value.round(currency.decimal_places, rounding_mode)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Adds a new exchange rate to the default bank and return the rate.
|
93
|
+
#
|
94
|
+
# @param [Currency, String, Symbol] from_currency Currency to exchange from.
|
95
|
+
# @param [Currency, String, Symbol] to_currency Currency to exchange to.
|
96
|
+
# @param [Numeric] rate Rate to exchange with.
|
97
|
+
#
|
98
|
+
# @return [Numeric]
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# Money.add_rate("USD", "CAD", 1.25) #=> 1.25
|
102
|
+
def add_rate(from_currency, to_currency, rate)
|
103
|
+
default_bank.add_rate(from_currency, to_currency, rate)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Sets the default bank to be a SingleCurrency bank that raises on
|
107
|
+
# currency exchange. Useful when apps operate in a single currency at a time.
|
108
|
+
def disallow_currency_conversion!
|
109
|
+
self.default_bank = Bank::SingleCurrency.instance
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create a new money object with value 0.
|
113
|
+
#
|
114
|
+
# @param [Currency, String, Symbol] currency The currency to use.
|
115
|
+
#
|
116
|
+
# @return [Money]
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# Money.empty #=> #<Money @fractional=0>
|
120
|
+
def empty(currency = default_currency)
|
121
|
+
@empty ||= {}
|
122
|
+
@empty[currency] ||= new(0, currency).freeze
|
123
|
+
end
|
124
|
+
alias_method :zero, :empty
|
125
|
+
|
126
|
+
# Creates a new Money object of value given in the +subunit+ of the given
|
127
|
+
# +currency+.
|
128
|
+
#
|
129
|
+
# @param [Numeric] amount The numerical value of the money.
|
130
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
131
|
+
# @param [Money::Bank::*] bank The exchange bank to use.
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# Money.from_subunits(2345, "USD") # => #<Money amount:23.45 currency:USD>
|
135
|
+
# Money.from_subunits(2345, "JPY") # => #<Money amount:2345 currency:JPY>
|
136
|
+
#
|
137
|
+
# @return [Money]
|
138
|
+
#
|
139
|
+
# @see #initialize
|
140
|
+
def from_subunits(amount, currency = default_currency, bank = default_bank)
|
141
|
+
raise ArgumentError, '`amount` must have #to_d' unless amount.respond_to?(:to_d)
|
142
|
+
currency = Currency.wrap(currency)
|
143
|
+
value = amount.to_d / currency.subunit_to_unit
|
144
|
+
new(value, currency, bank)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# @!attribute [rw] default_bank
|
149
|
+
# @return [Money::Bank::Base] Each Money object is associated to a bank
|
150
|
+
# object, which is responsible for currency exchange. This property
|
151
|
+
# allows you to specify the default bank object. The default value for
|
152
|
+
# this property is an instance of +Bank::VariableExchange.+ It allows
|
153
|
+
# one to specify custom exchange rates.
|
154
|
+
class_attribute :default_bank
|
155
|
+
self.default_bank = Bank::VariableExchange.instance
|
156
|
+
|
157
|
+
# @!attribute default_currency
|
158
|
+
# @return [Money::Currency] The default currency, which is used when
|
159
|
+
# +Money.new+ is called without an explicit currency argument. The
|
160
|
+
# default value is Currency.new("USD"). The value must be a valid
|
161
|
+
# +Money::Currency+ instance.
|
162
|
+
# Set the default currency for creating new +Money+ object.
|
163
|
+
self.default_currency = -> { Currency.new(:USD) }
|
164
|
+
|
165
|
+
# @!attribute [rw] infinite_precision
|
166
|
+
# @return [Boolean] Use this to enable infinite precision cents
|
167
|
+
class_attribute :infinite_precision
|
168
|
+
self.infinite_precision = false
|
169
|
+
|
170
|
+
#
|
171
|
+
class_attribute :_rounding_mode
|
172
|
+
singleton_class.send :alias_method, :rounding_mode=, :_rounding_mode=
|
173
|
+
# Default to bankers rounding
|
174
|
+
self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
|
175
|
+
|
176
|
+
# @!attribute [rw] conversion_precision
|
177
|
+
# Default the conversion of Rationals precision to 16
|
178
|
+
#
|
179
|
+
# @return [Fixnum] Use this to specify precision for converting Rational
|
180
|
+
# to BigDecimal
|
181
|
+
class_attribute :conversion_precision
|
182
|
+
self.conversion_precision = 16
|
183
|
+
|
184
|
+
# @!attribute [rw] conversion_precision
|
185
|
+
# Default formatter to use in #fromat. By default +Money::Formatter+ is used.
|
186
|
+
class_attribute :formatter
|
187
|
+
self.formatter = Formatter
|
188
|
+
|
189
|
+
# Creates a new Money object of value given with amount of +units+ of
|
190
|
+
# the given +currency+.
|
191
|
+
#
|
192
|
+
# Alternatively you can use the convenience
|
193
|
+
# methods like {Money.ca_dollar} and {Money.us_dollar}.
|
194
|
+
#
|
195
|
+
# @param [Object] obj Either the fractional value of the money,
|
196
|
+
# a Money object, or a currency.
|
197
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
198
|
+
# @param [Money::Bank::*] bank The exchange bank to use.
|
199
|
+
#
|
200
|
+
# @return [Money]
|
201
|
+
#
|
202
|
+
# @example
|
203
|
+
# Money.new(100) #=> #<Money @fractional=100 @currency="USD">
|
204
|
+
# Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
|
205
|
+
# Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
|
206
|
+
#
|
207
|
+
def initialize(val, currency = nil, bank = nil)
|
208
|
+
@currency =
|
209
|
+
if currency
|
210
|
+
Currency.wrap(currency)
|
211
|
+
else
|
212
|
+
val.respond_to?(:currency) ? val.currency : self.class.default_currency
|
213
|
+
end
|
214
|
+
@amount = val.respond_to?(:amount) ? val.amount : self.class.prepare_d(val, @currency)
|
215
|
+
@bank = bank || (val.respond_to?(:bank) ? val.bank : self.class.default_bank)
|
216
|
+
end
|
217
|
+
|
218
|
+
def yaml_initialize(_tag, attrs)
|
219
|
+
initialize(attrs['amount'] || 0, attrs['currency'], attrs['bank'])
|
220
|
+
end
|
221
|
+
|
222
|
+
# The value of the monetary amount represented in the fractional or subunit
|
223
|
+
# of the currency.
|
224
|
+
#
|
225
|
+
# For example, in the US dollar currency the fractional unit is cents, and
|
226
|
+
# there are 100 cents in one US dollar. So given the Money representation of
|
227
|
+
# one US dollar, the fractional interpretation is 100.
|
228
|
+
#
|
229
|
+
# Another example is that of the Kuwaiti dinar. In this case the fractional
|
230
|
+
# unit is the fils and there 1000 fils to one Kuwaiti dinar. So given the
|
231
|
+
# Money representation of one Kuwaiti dinar, the fractional interpretation is
|
232
|
+
# 1000.
|
233
|
+
#
|
234
|
+
# @return [Integer] when infinite_precision is false
|
235
|
+
# @return [BigDecimal] when infinite_precision is true
|
236
|
+
#
|
237
|
+
# @see infinite_precision
|
238
|
+
def fractional
|
239
|
+
value = to_d * currency.subunit_to_unit
|
240
|
+
if self.class.infinite_precision
|
241
|
+
value
|
242
|
+
else
|
243
|
+
value.round(0, self.class.rounding_mode).to_i
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Round a given amount of money to the nearest possible amount in cash value. For
|
248
|
+
# example, in Swiss franc (CHF), the smallest possible amount of cash value is
|
249
|
+
# CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and CHF 0.08 to
|
250
|
+
# CHF 0.10.
|
251
|
+
#
|
252
|
+
# @return [Integer] when infinite_precision is false
|
253
|
+
# @return [BigDecimal] when infinite_precision is true
|
254
|
+
#
|
255
|
+
# @see infinite_precision
|
256
|
+
def round_to_nearest_cash_value
|
257
|
+
unless currency.smallest_denomination
|
258
|
+
raise UndefinedSmallestDenomination, 'Smallest denomination of this currency is not defined'
|
259
|
+
end
|
260
|
+
smallest_denomination = currency.smallest_denomination
|
261
|
+
value = (to_d / smallest_denomination).
|
262
|
+
round(currency.decimal_places, self.class.rounding_mode)
|
263
|
+
value * smallest_denomination
|
264
|
+
end
|
265
|
+
|
266
|
+
# @!attribute [r] currency
|
267
|
+
# @return [Currency] The money's currency.
|
268
|
+
# @!attribute [r] bank
|
269
|
+
# @return [Money::Bank::Base] The +Money::Bank+-based object which currency
|
270
|
+
# exchanges are performed with.
|
271
|
+
attr_reader :currency, :bank, :amount
|
272
|
+
alias_method :to_d, :amount
|
273
|
+
|
274
|
+
# Returns a Fixnum hash value based on the +fractional+ and +currency+ attributes
|
275
|
+
# in order to use functions like & (intersection), group_by, etc.
|
276
|
+
#
|
277
|
+
# @return [Fixnum]
|
278
|
+
#
|
279
|
+
# @example
|
280
|
+
# Money.new(100).hash #=> 908351
|
281
|
+
def hash
|
282
|
+
[to_d.hash, currency.hash].hash
|
283
|
+
end
|
284
|
+
|
285
|
+
# Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
|
286
|
+
#
|
287
|
+
# @return [String]
|
288
|
+
#
|
289
|
+
# @example
|
290
|
+
# Money.new(100, "USD").symbol #=> "$"
|
291
|
+
def symbol
|
292
|
+
currency.symbol || "¤"
|
293
|
+
end
|
294
|
+
|
295
|
+
# Common inspect function
|
296
|
+
#
|
297
|
+
# @return [String]
|
298
|
+
def inspect
|
299
|
+
"#<#{self.class.name} amount:#{to_d.to_s('F')} currency:#{currency}>"
|
300
|
+
end
|
301
|
+
|
302
|
+
# Return the amount of money as a Integer.
|
303
|
+
#
|
304
|
+
# @return [Integer]
|
305
|
+
#
|
306
|
+
# @example
|
307
|
+
# Money.us_dollar(1_00).to_i #=> 1
|
308
|
+
def to_i
|
309
|
+
to_d.to_i
|
310
|
+
end
|
311
|
+
|
312
|
+
# Conversation to +self+.
|
313
|
+
#
|
314
|
+
# @return [self]
|
315
|
+
def to_money(new_currency = nil)
|
316
|
+
new_currency ? exchange_to(new_currency) : self
|
317
|
+
end
|
318
|
+
|
319
|
+
# Receive the amount of this money object in another Currency.
|
320
|
+
#
|
321
|
+
# @param [Currency, String, Symbol] other_currency Currency to exchange to.
|
322
|
+
#
|
323
|
+
# @yield [n] Optional block to use when rounding after exchanging one currency
|
324
|
+
# for another.
|
325
|
+
# @yieldparam [Float] n The resulting float after exchanging one currency for
|
326
|
+
# another.
|
327
|
+
# @yieldreturn [Integer]
|
328
|
+
#
|
329
|
+
# @return [Money]
|
330
|
+
#
|
331
|
+
# @example
|
332
|
+
# Money.new(2000, "USD").exchange_to("EUR")
|
333
|
+
# Money.new(2000, "USD").exchange_to("EUR") {|x| x.round}
|
334
|
+
# Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
|
335
|
+
def exchange_to(other_currency, &rounding_method)
|
336
|
+
other_currency = Currency.wrap(other_currency)
|
337
|
+
if currency == other_currency
|
338
|
+
self
|
339
|
+
else
|
340
|
+
bank.exchange_with(self, other_currency, &rounding_method)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# Round the monetary amount to smallest unit of coinage.
|
345
|
+
#
|
346
|
+
# @note
|
347
|
+
# This method is only useful when operating with infinite_precision turned
|
348
|
+
# on. Without infinite_precision values are rounded to the smallest unit of
|
349
|
+
# coinage automatically.
|
350
|
+
#
|
351
|
+
# @return [Money]
|
352
|
+
#
|
353
|
+
# @example
|
354
|
+
# Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
|
355
|
+
#
|
356
|
+
# @see
|
357
|
+
# Money.infinite_precision
|
358
|
+
#
|
359
|
+
def round(rounding_mode = self.class.rounding_mode)
|
360
|
+
if self.class.infinite_precision
|
361
|
+
build_new(to_d.round(currency.decimal_places, rounding_mode), currency)
|
362
|
+
else
|
363
|
+
self
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Formats value using formatter. Default formatter is +Money.formatter+ which is
|
368
|
+
# +Money::Formatter+ by default.
|
369
|
+
def format(formatter = self.class.formatter, **options)
|
370
|
+
formatter.format(self, options)
|
371
|
+
end
|
372
|
+
|
373
|
+
# Returns the amount of money as a string.
|
374
|
+
#
|
375
|
+
# @return [String]
|
376
|
+
#
|
377
|
+
# @example
|
378
|
+
# Money.ca_dollar(100).to_s #=> "1.00"
|
379
|
+
def to_s
|
380
|
+
Formatter::ToString.format(self)
|
381
|
+
end
|
382
|
+
|
383
|
+
private
|
384
|
+
|
385
|
+
# Used only for v6 compatibility. Should be replaced with inline `self.class.new`
|
386
|
+
# after this compatibility is dropped.
|
387
|
+
def build_new(*args)
|
388
|
+
self.class.new(*args)
|
389
|
+
end
|
390
|
+
end
|