money-joshm1 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ module Money::Currency::Loader
2
+ extend self
3
+
4
+ DATA_PATH = File.expand_path("../../../../config", __FILE__)
5
+
6
+ # Loads and returns the currencies stored in JSON files in the config directory.
7
+ #
8
+ # @return [Hash]
9
+ def load_currencies
10
+ currencies = parse_currency_file("currency_iso.json")
11
+ currencies.merge! parse_currency_file("currency_non_iso.json")
12
+ currencies.merge! parse_currency_file("currency_backwards_compatible.json")
13
+ end
14
+
15
+ private
16
+
17
+ def parse_currency_file(filename)
18
+ json = File.read("#{DATA_PATH}/#{filename}")
19
+ json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
20
+ JSON.parse(json, :symbolize_names => true)
21
+ end
22
+ end
@@ -0,0 +1,536 @@
1
+ # encoding: utf-8
2
+ require "money/bank/variable_exchange"
3
+ require "money/money/arithmetic"
4
+ require "money/money/parsing"
5
+ require "money/money/formatting"
6
+
7
+ # Represents an amount of money in a given currency.
8
+ class Money
9
+ include Comparable
10
+ include Arithmetic
11
+ include Formatting
12
+ include Parsing
13
+
14
+ # Convenience method for fractional part of the amount. Synonym of #fractional
15
+ #
16
+ # @return [Integer]
17
+ def cents
18
+ fractional
19
+ end
20
+
21
+ # The value of the amount represented in the fractional unit of the currency. Example: USD, 1 dollar (amount) == 100 cents (fractional unit).
22
+ #
23
+ # @return [Integer]
24
+ def fractional
25
+ if self.class.infinite_precision
26
+ @fractional
27
+ else
28
+ # If the Money object is created from a serialized YAML string,
29
+ # @fractional can end up being set to a Float. We need to ensure
30
+ # it is BigDecimal before calling #round with two paramers.
31
+ # Float class only provides #round with 0 or 1 parameter.
32
+ BigDecimal.new(@fractional.to_s, 0).round(0, self.class.rounding_mode).to_i
33
+ end
34
+ end
35
+
36
+ # The currency the money is in.
37
+ #
38
+ # @return [Currency]
39
+ attr_reader :currency
40
+
41
+ # The +Money::Bank+ based object used to perform currency exchanges with.
42
+ #
43
+ # @return [Money::Bank::*]
44
+ attr_reader :bank
45
+
46
+ # Class Methods
47
+ class << self
48
+ # Each Money object is associated to a bank object, which is responsible
49
+ # for currency exchange. This property allows you to specify the default
50
+ # bank object. The default value for this property is an instance of
51
+ # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
52
+ #
53
+ # @return [Money::Bank::*]
54
+ attr_accessor :default_bank
55
+
56
+ # The default currency, which is used when +Money.new+ is called without an
57
+ # explicit currency argument. The default value is Currency.new("USD"). The
58
+ # value must be a valid +Money::Currency+ instance.
59
+ #
60
+ # @return [Money::Currency]
61
+ attr_accessor :default_currency
62
+
63
+ # Use this to disable i18n even if it's used by other objects in your app.
64
+ #
65
+ # @return [true,false]
66
+ attr_accessor :use_i18n
67
+
68
+ # Use this to enable the ability to assume the currency from a passed symbol
69
+ #
70
+ # @return [true,false]
71
+ attr_accessor :assume_from_symbol
72
+
73
+ # Use this to enable infinite precision cents
74
+ #
75
+ # @return [true,false]
76
+ attr_accessor :infinite_precision
77
+
78
+ # Use this to specify the rounding mode
79
+ #
80
+ # @return [BigDecimal::ROUND_MODE]
81
+ attr_accessor :rounding_mode
82
+
83
+ # Use this to specify precision for converting Rational to BigDecimal
84
+ #
85
+ # @return [Integer]
86
+ attr_accessor :conversion_precision
87
+ end
88
+
89
+ # Set the default bank for creating new +Money+ objects.
90
+ self.default_bank = Bank::VariableExchange.instance
91
+
92
+ # Set the default currency for creating new +Money+ object.
93
+ self.default_currency = Currency.new("USD")
94
+
95
+ # Default to using i18n
96
+ self.use_i18n = true
97
+
98
+ # Default to not using currency symbol assumptions when parsing
99
+ self.assume_from_symbol = false
100
+
101
+ # Default to not using infinite precision cents
102
+ self.infinite_precision = false
103
+
104
+ # Default to bankers rounding
105
+ self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
106
+
107
+ # Default the conversion of Rationals precision to 16
108
+ self.conversion_precision = 16
109
+
110
+ # Create a new money object with value 0.
111
+ #
112
+ # @param [Currency, String, Symbol] currency The currency to use.
113
+ #
114
+ # @return [Money]
115
+ #
116
+ # @example
117
+ # Money.empty #=> #<Money @fractional=0>
118
+ def self.empty(currency = default_currency)
119
+ Money.new(0, currency)
120
+ end
121
+
122
+ # Creates a new Money object of the given value, using the Canadian
123
+ # dollar currency.
124
+ #
125
+ # @param [Integer] cents The cents value.
126
+ #
127
+ # @return [Money]
128
+ #
129
+ # @example
130
+ # n = Money.ca_dollar(100)
131
+ # n.cents #=> 100
132
+ # n.currency #=> #<Money::Currency id: cad>
133
+ def self.ca_dollar(cents)
134
+ Money.new(cents, "CAD")
135
+ end
136
+
137
+ # Creates a new Money object of the given value, using the American dollar
138
+ # currency.
139
+ #
140
+ # @param [Integer] cents The cents value.
141
+ #
142
+ # @return [Money]
143
+ #
144
+ # @example
145
+ # n = Money.us_dollar(100)
146
+ # n.cents #=> 100
147
+ # n.currency #=> #<Money::Currency id: usd>
148
+ def self.us_dollar(cents)
149
+ Money.new(cents, "USD")
150
+ end
151
+
152
+ # Creates a new Money object of the given value, using the Euro currency.
153
+ #
154
+ # @param [Integer] cents The cents value.
155
+ #
156
+ # @return [Money]
157
+ #
158
+ # @example
159
+ # n = Money.euro(100)
160
+ # n.cents #=> 100
161
+ # n.currency #=> #<Money::Currency id: eur>
162
+ def self.euro(cents)
163
+ Money.new(cents, "EUR")
164
+ end
165
+
166
+ # Creates a new Money object of +amount+ value ,
167
+ # with given +currency+.
168
+ #
169
+ # The amount value is expressed in the main monetary unit,
170
+ # opposite to the subunit-based representation
171
+ # used internally by this library called +cents+.
172
+ #
173
+ # @param [Numeric] amount The money amount, in main monetary unit.
174
+ # @param [Currency, String, Symbol] currency The currency format.
175
+ # @param [Money::Bank::*] bank The exchange bank to use.
176
+ #
177
+ # @return [Money]
178
+ #
179
+ # @example
180
+ # Money.new_with_amount(100)
181
+ # #=> #<Money @fractional=10000 @currency="USD">
182
+ # Money.new_with_amount(100, "USD")
183
+ # #=> #<Money @fractional=10000 @currency="USD">
184
+ # Money.new_with_amount(100, "EUR")
185
+ # #=> #<Money @fractional=10000 @currency="EUR">
186
+ #
187
+ # @see Money.new
188
+ #
189
+ def self.new_with_amount(amount, currency = Money.default_currency, bank = Money.default_bank)
190
+ money = from_numeric(amount, currency)
191
+ # Hack! You can't change a bank
192
+ money.instance_variable_set("@bank", bank)
193
+ money
194
+ end
195
+
196
+ # Synonym of #new_with_amount
197
+ #
198
+ # @see Money.new_with_amount
199
+ def self.new_with_dollars(*args)
200
+ self.new_with_amount(*args)
201
+ end
202
+
203
+ # Adds a new exchange rate to the default bank and return the rate.
204
+ #
205
+ # @param [Currency, String, Symbol] from_currency Currency to exchange from.
206
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
207
+ # @param [Numeric] rate Rate to exchange with.
208
+ #
209
+ # @return [Numeric]
210
+ #
211
+ # @example
212
+ # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
213
+ def self.add_rate(from_currency, to_currency, rate)
214
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
215
+ end
216
+
217
+
218
+ # Creates a new Money object of value given in the
219
+ # +fractional unit+ of the given +currency+.
220
+ #
221
+ # Alternatively you can use the convenience
222
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
223
+ #
224
+ # @param [Numeric] fractional The value given in the fractional unit.
225
+ # @param [Currency, String, Symbol] currency The currency format.
226
+ # @param [Money::Bank::*] bank The exchange bank to use.
227
+ #
228
+ # @return [Money]
229
+ #
230
+ # @example
231
+ # Money.new(100)
232
+ # #=> #<Money @fractional=100 @currency="USD">
233
+ # Money.new(100, "USD")
234
+ # #=> #<Money @fractional=100 @currency="USD">
235
+ # Money.new(100, "EUR")
236
+ # #=> #<Money @fractional=100 @currency="EUR">
237
+ #
238
+ # @see Money.new_with_dollars
239
+ #
240
+ def initialize(fractional, currency = Money.default_currency, bank = Money.default_bank)
241
+ @fractional = if fractional.is_a?(Rational)
242
+ fractional.to_d(self.class.conversion_precision)
243
+ elsif fractional.respond_to?(:to_d)
244
+ fractional.to_d
245
+ else
246
+ BigDecimal.new(fractional.to_s)
247
+ end
248
+ @currency = Currency.wrap(currency)
249
+ @bank = bank
250
+ end
251
+
252
+ # Assuming using a currency using dollars:
253
+ # Returns the value of the money in dollars,
254
+ # instead of in the fractional unit cents.
255
+ #
256
+ # Synonym of #amount
257
+ #
258
+ # @return [Float]
259
+ #
260
+ # @example
261
+ # Money.new(100).dollars # => 1.0
262
+ # Money.new_with_dollars(1).dollar # => 1.0
263
+ #
264
+ # @see #amount
265
+ # @see #to_f
266
+ # @see #cents
267
+ #
268
+ def dollars
269
+ amount
270
+ end
271
+
272
+ # Returns the numerical value of the money
273
+ #
274
+ # @return [Float]
275
+ #
276
+ # @example
277
+ # Money.new(100).amount # => 1.0
278
+ # Money.new_with_amount(1).amount # => 1.0
279
+ #
280
+ # @see #to_f
281
+ # @see #fractional
282
+ #
283
+ def amount
284
+ to_f
285
+ end
286
+
287
+ # Return string representation of currency object
288
+ #
289
+ # @return [String]
290
+ #
291
+ # @example
292
+ # Money.new(100, :USD).currency_as_string #=> "USD"
293
+ def currency_as_string
294
+ currency.to_s
295
+ end
296
+
297
+ # Set currency object using a string
298
+ #
299
+ # @param [String] val The currency string.
300
+ #
301
+ # @return [Money::Currency]
302
+ #
303
+ # @example
304
+ # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
305
+ def currency_as_string=(val)
306
+ @currency = Currency.wrap(val)
307
+ end
308
+
309
+ # Returns a Fixnum hash value based on the +fractional+ and +currency+ attributes
310
+ # in order to use functions like & (intersection), group_by, etc.
311
+ #
312
+ # @return [Fixnum]
313
+ #
314
+ # @example
315
+ # Money.new(100).hash #=> 908351
316
+ def hash
317
+ [fractional.hash, currency.hash].hash
318
+ end
319
+
320
+ # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
321
+ #
322
+ # @return [String]
323
+ #
324
+ # @example
325
+ # Money.new(100, "USD").symbol #=> "$"
326
+ def symbol
327
+ currency.symbol || "¤"
328
+ end
329
+
330
+ # Common inspect function
331
+ #
332
+ # @return [String]
333
+ def inspect
334
+ "#<Money fractional:#{fractional} currency:#{currency}>"
335
+ end
336
+
337
+ # Returns the amount of money as a string.
338
+ #
339
+ # @return [String]
340
+ #
341
+ # @example
342
+ # Money.ca_dollar(100).to_s #=> "1.00"
343
+ def to_s
344
+ unit, subunit = fractional().abs.divmod(currency.subunit_to_unit)
345
+
346
+ unit_str = ""
347
+ subunit_str = ""
348
+ fraction_str = ""
349
+
350
+ if self.class.infinite_precision
351
+ subunit, fraction = subunit.divmod(BigDecimal("1"))
352
+
353
+ unit_str = unit.to_i.to_s
354
+ subunit_str = subunit.to_i.to_s
355
+ fraction_str = fraction.to_s("F")[2..-1] # want fractional part "0.xxx"
356
+
357
+ fraction_str = "" if fraction_str =~ /^0+$/
358
+ else
359
+ unit_str, subunit_str = unit.to_s, subunit.to_s
360
+ end
361
+
362
+ absolute_str = if currency.decimal_places == 0
363
+ if fraction_str == ""
364
+ unit_str
365
+ else
366
+ "#{unit_str}#{decimal_mark}#{fraction_str}"
367
+ end
368
+ else
369
+ # need to pad subunit to right position,
370
+ # for example 1 usd 3 cents should be 1.03 not 1.3
371
+ subunit_str.insert(0, '0') while subunit_str.length < currency.decimal_places
372
+
373
+ "#{unit_str}#{decimal_mark}#{subunit_str}#{fraction_str}"
374
+ end
375
+
376
+ absolute_str.tap do |str|
377
+ str.insert(0, "-") if fractional() < 0
378
+ end
379
+ end
380
+
381
+ # Return the amount of money as a BigDecimal.
382
+ #
383
+ # @return [BigDecimal]
384
+ #
385
+ # @example
386
+ # Money.us_dollar(100).to_d => BigDecimal.new("1.0")
387
+ def to_d
388
+ BigDecimal.new(fractional.to_s) / BigDecimal.new(currency.subunit_to_unit.to_s)
389
+ end
390
+
391
+ # Return the amount of money as a float. Floating points cannot guarantee
392
+ # precision. Therefore, this function should only be used when you no longer
393
+ # need to represent currency or working with another system that requires
394
+ # decimals.
395
+ #
396
+ # @return [Float]
397
+ #
398
+ # @example
399
+ # Money.us_dollar(100).to_f => 1.0
400
+ def to_f
401
+ to_d.to_f
402
+ end
403
+
404
+ # Conversation to +self+.
405
+ #
406
+ # @return [self]
407
+ def to_money(given_currency = nil)
408
+ given_currency = Currency.wrap(given_currency) if given_currency
409
+ if given_currency.nil? || self.currency == given_currency
410
+ self
411
+ else
412
+ exchange_to(given_currency)
413
+ end
414
+ end
415
+
416
+ # Receive the amount of this money object in another Currency.
417
+ #
418
+ # @param [Currency, String, Symbol] other_currency Currency to exchange to.
419
+ #
420
+ # @return [Money]
421
+ #
422
+ # @example
423
+ # Money.new(2000, "USD").exchange_to("EUR")
424
+ # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
425
+ def exchange_to(other_currency)
426
+ other_currency = Currency.wrap(other_currency)
427
+ @bank.exchange_with(self, other_currency)
428
+ end
429
+
430
+ # Receive a money object with the same amount as the current Money object
431
+ # in american dollars.
432
+ #
433
+ # @return [Money]
434
+ #
435
+ # @example
436
+ # n = Money.new(100, "CAD").as_us_dollar
437
+ # n.currency #=> #<Money::Currency id: usd>
438
+ def as_us_dollar
439
+ exchange_to("USD")
440
+ end
441
+
442
+ # Receive a money object with the same amount as the current Money object
443
+ # in canadian dollar.
444
+ #
445
+ # @return [Money]
446
+ #
447
+ # @example
448
+ # n = Money.new(100, "USD").as_ca_dollar
449
+ # n.currency #=> #<Money::Currency id: cad>
450
+ def as_ca_dollar
451
+ exchange_to("CAD")
452
+ end
453
+
454
+ # Receive a money object with the same amount as the current Money object
455
+ # in euro.
456
+ #
457
+ # @return [Money]
458
+ #
459
+ # @example
460
+ # n = Money.new(100, "USD").as_euro
461
+ # n.currency #=> #<Money::Currency id: eur>
462
+ def as_euro
463
+ exchange_to("EUR")
464
+ end
465
+
466
+ # Allocates money between different parties without loosing pennies.
467
+ # After the mathmatically split has been performed, left over pennies will
468
+ # be distributed round-robin amongst the parties. This means that parties
469
+ # listed first will likely recieve more pennies then ones that are listed later
470
+ #
471
+ # @param [Array<Float, Float, Float>] splits [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% to party2, and 25% to party3.
472
+ #
473
+ # @return [Array<Money, Money, Money>]
474
+ #
475
+ # @example
476
+ # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
477
+ # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
478
+ def allocate(splits)
479
+ allocations = splits.inject(BigDecimal("0")) do |sum, n|
480
+ n = BigDecimal(n.to_s) unless n.is_a?(BigDecimal)
481
+ sum + n
482
+ end
483
+
484
+ if (allocations - BigDecimal("1")) > Float::EPSILON
485
+ raise ArgumentError, "splits add to more then 100%"
486
+ end
487
+
488
+ left_over = fractional
489
+
490
+ amounts = splits.map do |ratio|
491
+ if self.class.infinite_precision
492
+ fraction = fractional * ratio
493
+ else
494
+ fraction = (fractional * ratio / allocations).floor
495
+ left_over -= fraction
496
+ fraction
497
+ end
498
+ end
499
+
500
+ unless self.class.infinite_precision
501
+ left_over.to_i.times { |i| amounts[i % amounts.length] += 1 }
502
+ end
503
+
504
+ amounts.collect { |fractional| Money.new(fractional, currency) }
505
+ end
506
+
507
+ # Split money amongst parties evenly without loosing pennies.
508
+ #
509
+ # @param [Numeric] num number of parties.
510
+ #
511
+ # @return [Array<Money, Money, Money>]
512
+ #
513
+ # @example
514
+ # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
515
+ def split(num)
516
+ raise ArgumentError, "need at least one party" if num < 1
517
+
518
+ if self.class.infinite_precision
519
+ amt = self.div(BigDecimal(num.to_s))
520
+ return 1.upto(num).map{amt}
521
+ end
522
+
523
+ low = Money.new(fractional / num, self.currency)
524
+ high = Money.new(low.fractional + 1, self.currency)
525
+
526
+ remainder = fractional % num
527
+ result = []
528
+
529
+ num.times do |index|
530
+ result[index] = index < remainder ? high : low
531
+ end
532
+
533
+ result
534
+ end
535
+
536
+ end