money2 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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