money-joshm1 5.1.2

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