money 3.6.1 → 3.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/money/money.rb CHANGED
@@ -1,1210 +1,387 @@
1
- # encoding: utf-8
2
- require 'money/bank/variable_exchange'
3
-
4
- # Represents an amount of money in a given currency.
5
- class Money
6
- include Comparable
7
-
8
- # The value of the money in cents.
9
- #
10
- # @return [Integer]
11
- attr_reader :cents
12
-
13
- # The currency the money is in.
14
- #
15
- # @return [Currency]
16
- attr_reader :currency
17
-
18
- # The +Money::Bank+ based object used to perform currency exchanges with.
19
- #
20
- # @return [Money::Bank::*]
21
- attr_reader :bank
22
-
23
- # Class Methods
24
- class << self
25
- # Each Money object is associated to a bank object, which is responsible
26
- # for currency exchange. This property allows you to specify the default
27
- # bank object. The default value for this property is an instance if
28
- # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
29
- #
30
- # @return [Money::Bank::*]
31
- attr_accessor :default_bank
32
-
33
- # The default currency, which is used when +Money.new+ is called without an
34
- # explicit currency argument. The default value is Currency.new("USD"). The
35
- # value must be a valid +Money::Currency+ instance.
36
- #
37
- # @return [Money::Currency]
38
- attr_accessor :default_currency
39
- end
40
-
41
- # Set the default bank for creating new +Money+ objects.
42
- self.default_bank = Bank::VariableExchange.instance
43
-
44
- # Set the default currency for creating new +Money+ object.
45
- self.default_currency = Currency.new("USD")
46
-
47
- # Create a new money object with value 0.
48
- #
49
- # @param [Currency, String, Symbol] currency The currency to use.
50
- #
51
- # @return [Money]
52
- #
53
- # @example
54
- # Money.empty #=> #<Money @cents=0>
55
- def self.empty(currency = default_currency)
56
- Money.new(0, currency)
57
- end
58
-
59
- # Creates a new Money object of the given value, using the Canadian
60
- # dollar currency.
61
- #
62
- # @param [Integer] cents The cents value.
63
- #
64
- # @return [Money]
65
- #
66
- # @example
67
- # n = Money.ca_dollar(100)
68
- # n.cents #=> 100
69
- # n.currency #=> #<Money::Currency id: cad>
70
- def self.ca_dollar(cents)
71
- Money.new(cents, "CAD")
72
- end
73
-
74
- # Creates a new Money object of the given value, using the American dollar
75
- # currency.
76
- #
77
- # @param [Integer] cents The cents value.
78
- #
79
- # @return [Money]
80
- #
81
- # @example
82
- # n = Money.us_dollar(100)
83
- # n.cents #=> 100
84
- # n.currency #=> #<Money::Currency id: usd>
85
- def self.us_dollar(cents)
86
- Money.new(cents, "USD")
87
- end
88
-
89
- # Creates a new Money object of the given value, using the Euro currency.
90
- #
91
- # @param [Integer] cents The cents value.
92
- #
93
- # @return [Money]
94
- #
95
- # @example
96
- # n = Money.euro(100)
97
- # n.cents #=> 100
98
- # n.currency #=> #<Money::Currency id: eur>
99
- def self.euro(cents)
100
- Money.new(cents, "EUR")
101
- end
102
-
103
-
104
- # Creates a new Money object of +amount+ value in dollars,
105
- # with given +currency+.
106
- #
107
- # The amount value is expressed in +dollars+
108
- # where the +dollar+ is the main monetary unit,
109
- # opposite to the subunit-based representation
110
- # used internally by this library called +cents+.
111
- #
112
- # @param [Numeric] amount The money amount, in dollars.
113
- # @param [Currency, String, Symbol] currency The currency format.
114
- # @param [Money::Bank::*] bank The exchange bank to use.
115
- #
116
- # @return [Money]
117
- #
118
- # @example
119
- # Money.new_with_dollars(100)
120
- # #=> #<Money @cents=10000 @currency="USD">
121
- # Money.new_with_dollars(100, "USD")
122
- # #=> #<Money @cents=10000 @currency="USD">
123
- # Money.new_with_dollars(100, "EUR")
124
- # #=> #<Money @cents=10000 @currency="EUR">
125
- #
126
- # @see Money.new
127
- #
128
- def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
129
- money = from_numeric(amount, currency)
130
- # Hack! You can't change a bank
131
- money.instance_variable_set("@bank", bank)
132
- money
133
- end
134
-
135
-
136
- # Parses the current string and converts it to a +Money+ object.
137
- # Excess characters will be discarded.
138
- #
139
- # @param [String, #to_s] input The input to parse.
140
- # @param [Currency, String, Symbol] currency The currency format.
141
- # The currency to set the resulting +Money+ object to.
142
- #
143
- # @return [Money]
144
- #
145
- # @raise [ArgumentError] If any +currency+ is supplied and
146
- # given value doesn't match the one extracted from
147
- # the +input+ string.
148
- #
149
- # @example
150
- # '100'.to_money #=> #<Money @cents=10000>
151
- # '100.37'.to_money #=> #<Money @cents=10037>
152
- # '100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
153
- # 'USD 100'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
154
- # '$100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
155
- # 'hello 2000 world'.to_money #=> #<Money @cents=200000 @currency=#<Money::Currency id: usd>>
156
- #
157
- # @example Mismatching currencies
158
- # 'USD 2000'.to_money("EUR") #=> ArgumentError
159
- #
160
- # @see Money.from_string
161
- #
162
- def self.parse(input, currency = nil)
163
- i = input.to_s
164
-
165
- # Get the currency.
166
- m = i.scan /([A-Z]{2,3})/
167
- c = m[0] ? m[0][0] : nil
168
-
169
- # check that currency passed and embedded currency are the same,
170
- # and negotiate the final currency
171
- if currency.nil? and c.nil?
172
- currency = Money.default_currency
173
- elsif currency.nil?
174
- currency = c
175
- elsif c.nil?
176
- currency = currency
177
- elsif currency != c
178
- # TODO: ParseError
179
- raise ArgumentError, "Mismatching Currencies"
180
- end
181
- currency = Money::Currency.wrap(currency)
182
-
183
- cents = extract_cents(i, currency)
184
- Money.new(cents, currency)
185
- end
186
-
187
- # Converts a String into a Money object treating the +value+
188
- # as dollars and converting them to the corresponding cents value,
189
- # according to +currency+ subunit property,
190
- # before instantiating the Money object.
191
- #
192
- # Behind the scenes, this method relies on {Money.from_bigdecimal}
193
- # to avoid problems with string-to-numeric conversion.
194
- #
195
- # @param [String, #to_s] value The money amount, in dollars.
196
- # @param [Currency, String, Symbol] currency
197
- # The currency to set the resulting +Money+ object to.
198
- #
199
- # @return [Money]
200
- #
201
- # @example
202
- # Money.from_string("100")
203
- # #=> #<Money @cents=10000 @currency="USD">
204
- # Money.from_string("100", "USD")
205
- # #=> #<Money @cents=10000 @currency="USD">
206
- # Money.from_string("100", "EUR")
207
- # #=> #<Money @cents=10000 @currency="EUR">
208
- # Money.from_string("100", "BHD")
209
- # #=> #<Money @cents=100 @currency="BHD">
210
- #
211
- # @see String#to_money
212
- # @see Money.parse
213
- #
214
- def self.from_string(value, currency = Money.default_currency)
215
- from_bigdecimal(BigDecimal.new(value.to_s), currency)
216
- end
217
-
218
- # Converts a Fixnum into a Money object treating the +value+
219
- # as dollars and converting them to the corresponding cents value,
220
- # according to +currency+ subunit property,
221
- # before instantiating the Money object.
222
- #
223
- # @param [Fixnum] value The money amount, in dollars.
224
- # @param [Currency, String, Symbol] currency The currency format.
225
- #
226
- # @return [Money]
227
- #
228
- # @example
229
- # Money.from_fixnum(100)
230
- # #=> #<Money @cents=10000 @currency="USD">
231
- # Money.from_fixnum(100, "USD")
232
- # #=> #<Money @cents=10000 @currency="USD">
233
- # Money.from_fixnum(100, "EUR")
234
- # #=> #<Money @cents=10000 @currency="EUR">
235
- # Money.from_fixnum(100, "BHD")
236
- # #=> #<Money @cents=100 @currency="BHD">
237
- #
238
- # @see Fixnum#to_money
239
- # @see Money.from_numeric
240
- #
241
- def self.from_fixnum(value, currency = Money.default_currency)
242
- currency = Money::Currency.wrap(currency)
243
- amount = value * currency.subunit_to_unit
244
- Money.new(amount, currency)
245
- end
246
-
247
- # Converts a Float into a Money object treating the +value+
248
- # as dollars and converting them to the corresponding cents value,
249
- # according to +currency+ subunit property,
250
- # before instantiating the Money object.
251
- #
252
- # Behind the scenes, this method relies on Money.from_bigdecimal
253
- # to avoid problems with floating point precision.
254
- #
255
- # @param [Float] value The money amount, in dollars.
256
- # @param [Currency, String, Symbol] currency The currency format.
257
- #
258
- # @return [Money]
259
- #
260
- # @example
261
- # Money.from_float(100.0)
262
- # #=> #<Money @cents=10000 @currency="USD">
263
- # Money.from_float(100.0, "USD")
264
- # #=> #<Money @cents=10000 @currency="USD">
265
- # Money.from_float(100.0, "EUR")
266
- # #=> #<Money @cents=10000 @currency="EUR">
267
- # Money.from_float(100.0, "BHD")
268
- # #=> #<Money @cents=100 @currency="BHD">
269
- #
270
- # @see Float#to_money
271
- # @see Money.from_numeric
272
- #
273
- def self.from_float(value, currency = Money.default_currency)
274
- from_bigdecimal(BigDecimal.new(value.to_s), currency)
275
- end
276
-
277
- # Converts a BigDecimal into a Money object treating the +value+
278
- # as dollars and converting them to the corresponding cents value,
279
- # according to +currency+ subunit property,
280
- # before instantiating the Money object.
281
- #
282
- # @param [BigDecimal] value The money amount, in dollars.
283
- # @param [Currency, String, Symbol] currency The currency format.
284
- #
285
- # @return [Money]
286
- #
287
- # @example
288
- # Money.from_bigdecimal(BigDecimal.new("100")
289
- # #=> #<Money @cents=10000 @currency="USD">
290
- # Money.from_bigdecimal(BigDecimal.new("100", "USD")
291
- # #=> #<Money @cents=10000 @currency="USD">
292
- # Money.from_bigdecimal(BigDecimal.new("100", "EUR")
293
- # #=> #<Money @cents=10000 @currency="EUR">
294
- # Money.from_bigdecimal(BigDecimal.new("100", "BHD")
295
- # #=> #<Money @cents=100 @currency="BHD">
296
- #
297
- # @see BigDecimal#to_money
298
- # @see Money.from_numeric
299
- #
300
- def self.from_bigdecimal(value, currency = Money.default_currency)
301
- currency = Money::Currency.wrap(currency)
302
- amount = value * currency.subunit_to_unit
303
- Money.new(amount.fix, currency)
304
- end
305
-
306
- # Converts a Numeric value into a Money object treating the +value+
307
- # as dollars and converting them to the corresponding cents value,
308
- # according to +currency+ subunit property,
309
- # before instantiating the Money object.
310
- #
311
- # This method relies on various +Money.from_*+ methods
312
- # and tries to forwards the call to the most appropriate method
313
- # in order to reduce computation effort.
314
- # For instance, if +value+ is an Integer, this method calls
315
- # {Money.from_fixnum} instead of using the default
316
- # {Money.from_bigdecimal} which adds the overload to converts
317
- # the value into a slower BigDecimal instance.
318
- #
319
- # @param [Numeric] value The money amount, in dollars.
320
- # @param [Currency, String, Symbol] currency The currency format.
321
- #
322
- # @return [Money]
323
- #
324
- # @raise +ArgumentError+ Unless +value+ is a supported type.
325
- #
326
- # @example
327
- # Money.from_numeric(100)
328
- # #=> #<Money @cents=10000 @currency="USD">
329
- # Money.from_numeric(100.00)
330
- # #=> #<Money @cents=10000 @currency="USD">
331
- # Money.from_numeric("100")
332
- # #=> ArgumentError
333
- #
334
- # @see Numeric#to_money
335
- # @see Money.from_fixnum
336
- # @see Money.from_float
337
- # @see Money.from_bigdecimal
338
- #
339
- def self.from_numeric(value, currency = Money.default_currency)
340
- case value
341
- when Fixnum
342
- from_fixnum(value, currency)
343
- when Numeric
344
- from_bigdecimal(BigDecimal.new(value.to_s), currency)
345
- else
346
- raise ArgumentError, "`value' should be a Numeric object"
347
- end
348
- end
349
-
350
-
351
- # Adds a new exchange rate to the default bank and return the rate.
352
- #
353
- # @param [Currency, String, Symbol] from_currency Currency to exchange from.
354
- # @param [Currency, String, Symbol] to_currency Currency to exchange to.
355
- # @param [Numeric] rate Rate to exchange with.
356
- #
357
- # @return [Numeric]
358
- #
359
- # @example
360
- # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
361
- def self.add_rate(from_currency, to_currency, rate)
362
- Money.default_bank.add_rate(from_currency, to_currency, rate)
363
- end
364
-
365
-
366
- # Creates a new Money object of +cents+ value in cents,
367
- # with given +currency+.
368
- #
369
- # Alternatively you can use the convenience
370
- # methods like {Money.ca_dollar} and {Money.us_dollar}.
371
- #
372
- # @param [Integer] cents The money amount, in cents.
373
- # @param [Currency, String, Symbol] currency The currency format.
374
- # @param [Money::Bank::*] bank The exchange bank to use.
375
- #
376
- # @return [Money]
377
- #
378
- # @example
379
- # Money.new(100)
380
- # #=> #<Money @cents=100 @currency="USD">
381
- # Money.new(100, "USD")
382
- # #=> #<Money @cents=100 @currency="USD">
383
- # Money.new(100, "EUR")
384
- # #=> #<Money @cents=100 @currency="EUR">
385
- #
386
- # @see Money.new_with_dollars
387
- #
388
- def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
389
- @cents = cents.round.to_i
390
- @currency = Currency.wrap(currency)
391
- @bank = bank
392
- end
393
-
394
- # Returns the value of the money in dollars,
395
- # instead of in cents.
396
- #
397
- # @return [Float]
398
- #
399
- # @example
400
- # Money.new(100).dollars # => 1.0
401
- # Money.new_with_dollars(1).dollar # => 1.0
402
- #
403
- # @see #to_f
404
- # @see #cents
405
- #
406
- def dollars
407
- to_f
408
- end
409
-
410
- # Return string representation of currency object
411
- #
412
- # @return [String]
413
- #
414
- # @example
415
- # Money.new(100, :USD).currency_as_string #=> "USD"
416
- def currency_as_string
417
- self.currency.to_s
418
- end
419
-
420
- # Set currency object using a string
421
- #
422
- # @param [String] val The currency string.
423
- #
424
- # @return [Money::Currency]
425
- #
426
- # @example
427
- # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
428
- def currency_as_string=(val)
429
- @currency = Currency.wrap(val)
430
- end
431
-
432
- # Checks whether two money objects have the same currency and the same
433
- # amount. Checks against money objects with a different currency and checks
434
- # against objects that do not respond to #to_money will always return false.
435
- #
436
- # @param [Money] other_money Value to compare with.
437
- #
438
- # @return [Boolean]
439
- #
440
- # @example
441
- # Money.new(100) == Money.new(101) #=> false
442
- # Money.new(100) == Money.new(100) #=> true
443
- def ==(other_money)
444
- if other_money.respond_to?(:to_money)
445
- other_money = other_money.to_money
446
- cents == other_money.cents && self.currency == other_money.currency
447
- else
448
- false
449
- end
450
- end
451
-
452
- # Synonymous with +#==+.
453
- #
454
- # @param [Money] other_money Value to compare with.
455
- #
456
- # @return [Money]
457
- #
458
- # @see #==
459
- def eql?(other_money)
460
- self == other_money
461
- end
462
-
463
- # Returns a Fixnum hash value based on the +cents+ and +currency+ attributes
464
- # in order to use functions like & (intersection), group_by, etc.
465
- #
466
- # @return [Fixnum]
467
- #
468
- # @example
469
- # Money.new(100).hash #=> 908351
470
- def hash
471
- [cents.hash, currency.hash].hash
472
- end
473
-
474
- # Compares this money object against another object. +other_money+ must
475
- # respond to #to_money. Returns -1 when less than, 0 when equal and 1 when
476
- # greater than.
477
- #
478
- # If +other_money+ is a different currency, then +other_money+ will first be
479
- # converted into this money object's currency by calling +#exchange+ on
480
- # +other_money+.
481
- #
482
- # Comparisons against objects that do not respond to #to_money will cause an
483
- # +ArgumentError+ to be raised.
484
- #
485
- # @param [Money, #to_money] other_money Value to compare with.
486
- #
487
- # @return [-1, 0, 1]
488
- #
489
- # @raise [ArgumentError]
490
- #
491
- # @example
492
- # Money.new(100) <=> 99 #=> 1
493
- # Money.new(100) <=> Money.new(100) #=> 0
494
- # Money.new(100) <=> "$101.00" #=> -1
495
- def <=>(other_money)
496
- if other_money.respond_to?(:to_money)
497
- other_money = other_money.to_money
498
- if self.currency == other_money.currency
499
- cents <=> other_money.cents
500
- else
501
- cents <=> other_money.exchange_to(currency).cents
502
- end
503
- else
504
- raise ArgumentError, "Comparison of #{self.class} with #{other_money.inspect} failed"
505
- end
506
- end
507
-
508
- # Returns a new Money object containing the sum of the two operands' monetary
509
- # values. If +other_money+ has a different currency then its monetary value
510
- # is automatically exchanged to this object's currency using +exchange_to+.
511
- #
512
- # @param [Money] other_money Other +Money+ object to add.
513
- #
514
- # @return [Money]
515
- #
516
- # @example
517
- # Money.new(100) + Money.new(100) #=> #<Money @cents=200>
518
- def +(other_money)
519
- if currency == other_money.currency
520
- Money.new(cents + other_money.cents, other_money.currency)
521
- else
522
- Money.new(cents + other_money.exchange_to(currency).cents, currency)
523
- end
524
- end
525
-
526
- # Returns a new Money object containing the difference between the two
527
- # operands' monetary values. If +other_money+ has a different currency then
528
- # its monetary value is automatically exchanged to this object's currency
529
- # using +exchange_to+.
530
- #
531
- # @param [Money] other_money Other +Money+ object to subtract.
532
- #
533
- # @return [Money]
534
- #
535
- # @example
536
- # Money.new(100) - Money.new(99) #=> #<Money @cents=1>
537
- def -(other_money)
538
- if currency == other_money.currency
539
- Money.new(cents - other_money.cents, other_money.currency)
540
- else
541
- Money.new(cents - other_money.exchange_to(currency).cents, currency)
542
- end
543
- end
544
-
545
- # Multiplies the monetary value with the given number and returns a new
546
- # +Money+ object with this monetary value and the same currency.
547
- #
548
- # Note that you can't multiply a Money object by an other +Money+ object.
549
- #
550
- # @param [Numeric] value Number to multiply by.
551
- #
552
- # @return [Money] The resulting money.
553
- #
554
- # @raise [ArgumentError] If +value+ is a Money instance.
555
- #
556
- # @example
557
- # Money.new(100) * 2 #=> #<Money @cents=200>
558
- #
559
- def *(value)
560
- if value.is_a?(Money)
561
- raise ArgumentError, "Can't multiply a Money by a Money"
562
- else
563
- Money.new(cents * value, currency)
564
- end
565
- end
566
-
567
- # Divides the monetary value with the given number and returns a new +Money+
568
- # object with this monetary value and the same currency.
569
- # Can also divide by another +Money+ object to get a ratio.
570
- #
571
- # +Money/Numeric+ returns +Money+. +Money/Money+ returns +Float+.
572
- #
573
- # @param [Money, Numeric] value Number to divide by.
574
- #
575
- # @return [Money] The resulting money if you divide Money by a number.
576
- # @return [Float] The resulting number if you divide Money by a Money.
577
- #
578
- # @example
579
- # Money.new(100) / 10 #=> #<Money @cents=10>
580
- # Money.new(100) / Money.new(10) #=> 10.0
581
- #
582
- def /(value)
583
- if value.is_a?(Money)
584
- if currency == value.currency
585
- (cents / BigDecimal.new(value.cents.to_s)).to_f
586
- else
587
- (cents / BigDecimal(value.exchange_to(currency).cents.to_s)).to_f
588
- end
589
- else
590
- Money.new(cents / value, currency)
591
- end
592
- end
593
-
594
- # Synonym for +#/+.
595
- #
596
- # @param [Money, Numeric] value Number to divide by.
597
- #
598
- # @return [Money] The resulting money if you divide Money by a number.
599
- # @return [Float] The resulting number if you divide Money by a Money.
600
- #
601
- # @see #/
602
- #
603
- def div(value)
604
- self / value
605
- end
606
-
607
- # Divide money by money or fixnum and return array containing quotient and
608
- # modulus.
609
- #
610
- # @param [Money, Fixnum] val Number to divmod by.
611
- #
612
- # @return [Array<Money,Money>,Array<Fixnum,Money>]
613
- #
614
- # @example
615
- # Money.new(100).divmod(9) #=> [#<Money @cents=11>, #<Money @cents=1>]
616
- # Money.new(100).divmod(Money.new(9)) #=> [11, #<Money @cents=1>]
617
- def divmod(val)
618
- if val.is_a?(Money)
619
- a = self.cents
620
- b = self.currency == val.currency ? val.cents : val.exchange_to(self.currency).cents
621
- q, m = a.divmod(b)
622
- return [q, Money.new(m, self.currency)]
623
- else
624
- return [self.div(val), Money.new(self.cents.modulo(val), self.currency)]
625
- end
626
- end
627
-
628
- # Equivalent to +self.divmod(val)[1]+
629
- #
630
- # @param [Money, Fixnum] val Number take modulo with.
631
- #
632
- # @return [Money]
633
- #
634
- # @example
635
- # Money.new(100).modulo(9) #=> #<Money @cents=1>
636
- # Money.new(100).modulo(Money.new(9)) #=> #<Money @cents=1>
637
- def modulo(val)
638
- self.divmod(val)[1]
639
- end
640
-
641
- # Synonym for +#modulo+.
642
- #
643
- # @param [Money, Fixnum] val Number take modulo with.
644
- #
645
- # @return [Money]
646
- #
647
- # @see #modulo
648
- def %(val)
649
- self.modulo(val)
650
- end
651
-
652
- # If different signs +self.modulo(val) - val+ otherwise +self.modulo(val)+
653
- #
654
- # @param [Money, Fixnum] val Number to rake remainder with.
655
- #
656
- # @return [Money]
657
- #
658
- # @example
659
- # Money.new(100).remainder(9) #=> #<Money @cents=1>
660
- def remainder(val)
661
- a, b = self, val
662
- b = b.exchange_to(a.currency) if b.is_a?(Money) and a.currency != b.currency
663
-
664
- a_sign, b_sign = :pos, :pos
665
- a_sign = :neg if a.cents < 0
666
- b_sign = :neg if (b.is_a?(Money) and b.cents < 0) or (b < 0)
667
-
668
- return a.modulo(b) if a_sign == b_sign
669
- a.modulo(b) - (b.is_a?(Money) ? b : Money.new(b, a.currency))
670
- end
671
-
672
- # Return absolute value of self as a new Money object.
673
- #
674
- # @return [Money]
675
- #
676
- # @example
677
- # Money.new(-100).abs #=> #<Money @cents=100>
678
- def abs
679
- Money.new(self.cents.abs, self.currency)
680
- end
681
-
682
- # Test if the money amount is zero.
683
- #
684
- # @return [Boolean]
685
- #
686
- # @example
687
- # Money.new(100).zero? #=> false
688
- # Money.new(0).zero? #=> true
689
- def zero?
690
- cents == 0
691
- end
692
-
693
- # Test if the money amount is non-zero. Returns this money object if it is
694
- # non-zero, or nil otherwise, like +Numeric#nonzero?+.
695
- #
696
- # @return [Money, nil]
697
- #
698
- # @example
699
- # Money.new(100).nonzero? #=> #<Money @cents=100>
700
- # Money.new(0).nonzero? #=> nil
701
- def nonzero?
702
- cents != 0 ? self : nil
703
- end
704
-
705
- # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
706
- #
707
- # @return [String]
708
- #
709
- # @example
710
- # Money.new(100, "USD").symbol #=> "$"
711
- def symbol
712
- currency.symbol || "¤"
713
- end
714
-
715
- # If I18n is loaded, looks up key +:number.format.delimiter+.
716
- # Otherwise and as fallback it uses +Currency#thousands_separator+.
717
- # If +nil+ is returned, default to ",".
718
- #
719
- # @return [String]
720
- #
721
- # @example
722
- # Money.new(100, "USD").thousands_separator #=> ","
723
- if Object.const_defined?("I18n")
724
- def thousands_separator
725
- I18n.t(:"number.format.thousands_separator", :default => currency.thousands_separator || ",")
726
- end
727
- else
728
- def thousands_separator
729
- currency.thousands_separator || ","
730
- end
731
- end
732
- alias :delimiter :thousands_separator
733
-
734
- # If I18n is loaded, looks up key +:number.format.seperator+.
735
- # Otherwise and as fallback it uses +Currency#seperator+.
736
- # If +nil+ is returned, default to ",".
737
- #
738
- # @return [String]
739
- #
740
- # @example
741
- # Money.new(100, "USD").decimal_mark #=> "."
742
- if Object.const_defined?("I18n")
743
- def decimal_mark
744
- I18n.t(:"number.format.decimal_mark", :default => currency.decimal_mark || ".")
745
- end
746
- else
747
- def decimal_mark
748
- currency.decimal_mark || "."
749
- end
750
- end
751
- alias :separator :decimal_mark
752
-
753
- # Creates a formatted price string according to several rules.
754
- #
755
- # @param [Hash] *rules The options used to format the string.
756
- #
757
- # @return [String]
758
- #
759
- # @option *rules [Boolean, String] :display_free (false) Whether a zero
760
- # amount of money should be formatted of "free" or as the supplied string.
761
- #
762
- # @example
763
- # Money.us_dollar(0).format(:display_free => true) #=> "free"
764
- # Money.us_dollar(0).format(:display_free => "gratis") #=> "gratis"
765
- # Money.us_dollar(0).format #=> "$0.00"
766
- #
767
- # @option *rules [Boolean] :with_currency (false) Whether the currency name
768
- # should be appended to the result string.
769
- #
770
- # @example
771
- # Money.ca_dollar(100).format => "$1.00"
772
- # Money.ca_dollar(100).format(:with_currency => true) #=> "$1.00 CAD"
773
- # Money.us_dollar(85).format(:with_currency => true) #=> "$0.85 USD"
774
- #
775
- # @option *rules [Boolean] :no_cents (false) Whether cents should be omitted.
776
- #
777
- # @example
778
- # Money.ca_dollar(100).format(:no_cents => true) #=> "$1"
779
- # Money.ca_dollar(599).format(:no_cents => true) #=> "$5"
780
- #
781
- # @option *rules [Boolean, String, nil] :symbol (true) Whether a money symbol
782
- # should be prepended to the result string. The default is true. This method
783
- # attempts to pick a symbol that's suitable for the given currency.
784
- #
785
- # @example
786
- # Money.new(100, "USD") #=> "$1.00"
787
- # Money.new(100, "GBP") #=> "£1.00"
788
- # Money.new(100, "EUR") #=> "€1.00"
789
- #
790
- # # Same thing.
791
- # Money.new(100, "USD").format(:symbol => true) #=> "$1.00"
792
- # Money.new(100, "GBP").format(:symbol => true) #=> "£1.00"
793
- # Money.new(100, "EUR").format(:symbol => true) #=> "€1.00"
794
- #
795
- # # You can specify a false expression or an empty string to disable
796
- # # prepending a money symbol.
797
- # Money.new(100, "USD").format(:symbol => false) #=> "1.00"
798
- # Money.new(100, "GBP").format(:symbol => nil) #=> "1.00"
799
- # Money.new(100, "EUR").format(:symbol => "") #=> "1.00"
800
- #
801
- # # If the symbol for the given currency isn't known, then it will default
802
- # # to "¤" as symbol.
803
- # Money.new(100, "AWG").format(:symbol => true) #=> "¤1.00"
804
- #
805
- # # You can specify a string as value to enforce using a particular symbol.
806
- # Money.new(100, "AWG").format(:symbol => "ƒ") #=> "ƒ1.00"
807
- #
808
- # @option *rules [Boolean, String, nil] :decimal_mark (true) Whether the
809
- # currency should be separated by the specified character or '.'
810
- #
811
- # @example
812
- # # If a string is specified, it's value is used.
813
- # Money.new(100, "USD").format(:decimal_mark => ",") #=> "$1,00"
814
- #
815
- # # If the decimal_mark for a given currency isn't known, then it will default
816
- # # to "." as decimal_mark.
817
- # Money.new(100, "FOO").format #=> "$1.00"
818
- #
819
- # @option *rules [Boolean, String, nil] :thousands_separator (true) Whether
820
- # the currency should be delimited by the specified character or ','
821
- #
822
- # @example
823
- # # If false is specified, no thousands_separator is used.
824
- # Money.new(100000, "USD").format(:thousands_separator => false) #=> "1000.00"
825
- # Money.new(100000, "USD").format(:thousands_separator => nil) #=> "1000.00"
826
- # Money.new(100000, "USD").format(:thousands_separator => "") #=> "1000.00"
827
- #
828
- # # If a string is specified, it's value is used.
829
- # Money.new(100000, "USD").format(:thousands_separator => ".") #=> "$1.000.00"
830
- #
831
- # # If the thousands_separator for a given currency isn't known, then it will
832
- # # default to "," as thousands_separator.
833
- # Money.new(100000, "FOO").format #=> "$1,000.00"
834
- #
835
- # @option *rules [Boolean] :html (false) Whether the currency should be
836
- # HTML-formatted. Only useful in combination with +:with_currency+.
837
- #
838
- # @example
839
- # s = Money.ca_dollar(570).format(:html => true, :with_currency => true)
840
- # s #=> "$5.70 <span class=\"currency\">CAD</span>"
841
- def format(*rules)
842
- # support for old format parameters
843
- rules = normalize_formatting_rules(rules)
844
-
845
- if cents == 0
846
- if rules[:display_free].respond_to?(:to_str)
847
- return rules[:display_free]
848
- elsif rules[:display_free]
849
- return "free"
850
- end
851
- end
852
-
853
- symbol_value =
854
- if rules.has_key?(:symbol)
855
- if rules[:symbol] === true
856
- symbol
857
- elsif rules[:symbol]
858
- rules[:symbol]
859
- else
860
- ""
861
- end
862
- elsif rules[:html]
863
- currency.html_entity
864
- else
865
- symbol
866
- end
867
-
868
- formatted = case rules[:no_cents]
869
- when true
870
- "#{self.to_s.to_i}"
871
- else
872
- "#{self.to_s}"
873
- end
874
-
875
- symbol_position =
876
- if rules.has_key?(:symbol_position)
877
- rules[:symbol_position]
878
- elsif currency.symbol_first?
879
- :before
880
- else
881
- :after
882
- end
883
-
884
- if symbol_value && !symbol_value.empty?
885
- formatted = (symbol_position == :before ? "#{symbol_value}#{formatted}" : "#{formatted} #{symbol_value}")
886
- end
887
-
888
- if rules.has_key?(:decimal_mark) and rules[:decimal_mark] and
889
- rules[:decimal_mark] != decimal_mark
890
- formatted.sub!(decimal_mark, rules[:decimal_mark])
891
- end
892
-
893
- thousands_separator_value = thousands_separator
894
- # Determine thousands_separator
895
- if rules.has_key?(:thousands_separator)
896
- if rules[:thousands_separator] === false or rules[:thousands_separator].nil?
897
- thousands_separator_value = ""
898
- elsif rules[:thousands_separator]
899
- thousands_separator_value = rules[:thousands_separator]
900
- end
901
- end
902
-
903
- # Apply thousands_separator
904
- formatted.gsub!(/(\d)(?=(?:\d{3})+(?:[^\d]|$))/, "\\1#{thousands_separator_value}")
905
-
906
- if rules[:with_currency]
907
- formatted << " "
908
- formatted << '<span class="currency">' if rules[:html]
909
- formatted << currency.to_s
910
- formatted << '</span>' if rules[:html]
911
- end
912
- formatted
913
- end
914
-
915
- # Returns the amount of money as a string.
916
- #
917
- # @return [String]
918
- #
919
- # @example
920
- # Money.ca_dollar(100).to_s #=> "1.00"
921
- def to_s
922
- unit, subunit = cents.abs.divmod(currency.subunit_to_unit).map{|o| o.to_s}
923
- if currency.decimal_places == 0
924
- return "-#{unit}" if cents < 0
925
- return unit
926
- end
927
- subunit = (("0" * currency.decimal_places) + subunit)[(-1*currency.decimal_places)..-1]
928
- return "-#{unit}#{decimal_mark}#{subunit}" if cents < 0
929
- "#{unit}#{decimal_mark}#{subunit}"
930
- end
931
-
932
- # Return the amount of money as a float. Floating points cannot guarantee
933
- # precision. Therefore, this function should only be used when you no longer
934
- # need to represent currency or working with another system that requires
935
- # decimals.
936
- #
937
- # @return [Float]
938
- #
939
- # @example
940
- # Money.us_dollar(100).to_f => 1.0
941
- def to_f
942
- (BigDecimal.new(cents.to_s) / currency.subunit_to_unit).to_f
943
- end
944
-
945
- # Receive the amount of this money object in another Currency.
946
- #
947
- # @param [Currency, String, Symbol] other_currency Currency to exchange to.
948
- #
949
- # @return [Money]
950
- #
951
- # @example
952
- # Money.new(2000, "USD").exchange_to("EUR")
953
- # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
954
- def exchange_to(other_currency)
955
- other_currency = Currency.wrap(other_currency)
956
- @bank.exchange_with(self, other_currency)
957
- end
958
-
959
- # Receive a money object with the same amount as the current Money object
960
- # in american dollars.
961
- #
962
- # @return [Money]
963
- #
964
- # @example
965
- # n = Money.new(100, "CAD").as_us_dollar
966
- # n.currency #=> #<Money::Currency id: usd>
967
- def as_us_dollar
968
- exchange_to("USD")
969
- end
970
-
971
- # Receive a money object with the same amount as the current Money object
972
- # in canadian dollar.
973
- #
974
- # @return [Money]
975
- #
976
- # @example
977
- # n = Money.new(100, "USD").as_ca_dollar
978
- # n.currency #=> #<Money::Currency id: cad>
979
- def as_ca_dollar
980
- exchange_to("CAD")
981
- end
982
-
983
- # Receive a money object with the same amount as the current Money object
984
- # in euro.
985
- #
986
- # @return [Money]
987
- #
988
- # @example
989
- # n = Money.new(100, "USD").as_euro
990
- # n.currency #=> #<Money::Currency id: eur>
991
- def as_euro
992
- exchange_to("EUR")
993
- end
994
-
995
- # Conversation to +self+.
996
- #
997
- # @return [self]
998
- def to_money
999
- self
1000
- end
1001
-
1002
- # Common inspect function
1003
- #
1004
- # @return [String]
1005
- def inspect
1006
- "#<Money cents:#{cents} currency:#{currency}>"
1007
- end
1008
-
1009
- # Allocates money between different parties without loosing pennies.
1010
- # After the mathmatically split has been performed, left over pennies will
1011
- # be distributed round-robin amongst the parties. This means that parties
1012
- # listed first will likely recieve more pennies then ones that are listed later
1013
- #
1014
- # @param [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% ot party2, and 25% to party3.
1015
- #
1016
- # @return [Array<Money, Money, Money>]
1017
- #
1018
- # @example
1019
- # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
1020
- # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
1021
- def allocate(splits)
1022
- allocations = splits.inject(0.0) {|sum, i| sum += i }
1023
- raise ArgumentError, "splits add to more then 100%" if (allocations - 1.0) > Float::EPSILON
1024
-
1025
- left_over = cents
1026
-
1027
- amounts = splits.collect do |ratio|
1028
- fraction = (cents * ratio / allocations).floor
1029
- left_over -= fraction
1030
- fraction
1031
- end
1032
-
1033
- left_over.times { |i| amounts[i % amounts.length] += 1 }
1034
-
1035
- return amounts.collect { |cents| Money.new(cents, currency) }
1036
- end
1037
-
1038
- # Split money amongst parties evenly without loosing pennies.
1039
- #
1040
- # @param [2] number of parties.
1041
- #
1042
- # @return [Array<Money, Money, Money>]
1043
- #
1044
- # @example
1045
- # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
1046
- def split(num)
1047
- raise ArgumentError, "need at least one party" if num < 1
1048
- low = Money.new(cents / num)
1049
- high = Money.new(low.cents + 1)
1050
-
1051
- remainder = cents % num
1052
- result = []
1053
-
1054
- num.times do |index|
1055
- result[index] = index < remainder ? high : low
1056
- end
1057
-
1058
- return result
1059
- end
1060
-
1061
- private
1062
-
1063
- # Cleans up formatting rules.
1064
- #
1065
- # @param [Hash]
1066
- #
1067
- # @return [Hash]
1068
- def normalize_formatting_rules(rules)
1069
- if rules.size == 0
1070
- rules = {}
1071
- elsif rules.size == 1
1072
- rules = rules.pop
1073
- rules = { rules => true } if rules.is_a?(Symbol)
1074
- end
1075
- if not rules.include?(:decimal_mark) and rules.include?(:separator)
1076
- rules[:decimal_mark] = rules[:separator]
1077
- end
1078
- if not rules.include?(:thousands_separator) and rules.include?(:delimiter)
1079
- rules[:thousands_separator] = rules[:delimiter]
1080
- end
1081
- rules
1082
- end
1083
-
1084
- # Takes a number string and attempts to massage out the number.
1085
- #
1086
- # @param [String] input The string containing a potential number.
1087
- #
1088
- # @return [Integer]
1089
- #
1090
- def self.extract_cents(input, currency = Money.default_currency)
1091
- # remove anything that's not a number, potential thousands_separator, or minus sign
1092
- num = input.gsub(/[^\d|\.|,|\'|\-]/, '').strip
1093
-
1094
- # set a boolean flag for if the number is negative or not
1095
- negative = num.split(//).first == "-"
1096
-
1097
- # if negative, remove the minus sign from the number
1098
- # if it's not negative, the hyphen makes the value invalid
1099
- if negative
1100
- num = num.gsub(/^-/, '')
1101
- else
1102
- raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')
1103
- end
1104
-
1105
- #if the number ends with punctuation, just throw it out. If it means decimal,
1106
- #it won't hurt anything. If it means a literal period or comma, this will
1107
- #save it from being mis-interpreted as a decimal.
1108
- num.chop! if num.match /[\.|,]$/
1109
-
1110
- # gather all decimal_marks within the result number
1111
- used_decimal_marks = num.scan /[^\d]/
1112
-
1113
- # determine the number of unique decimal_marks within the number
1114
- #
1115
- # e.g.
1116
- # $1,234,567.89 would return 2 (, and .)
1117
- # $125,00 would return 1
1118
- # $199 would return 0
1119
- # $1 234,567.89 would raise an error (decimal_marks are space, comma, and period)
1120
- case used_decimal_marks.uniq.length
1121
- # no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
1122
- when 0 then major, minor = num, 0
1123
-
1124
- # two decimal_marks, so we know the last item in this array is the
1125
- # major/minor thousands_separator and the rest are decimal_marks
1126
- when 2
1127
- decimal_mark, thousands_separator = used_decimal_marks.uniq
1128
- # remove all decimal_marks, split on the thousands_separator
1129
- major, minor = num.gsub(decimal_mark, '').split(thousands_separator)
1130
- min = 0 unless min
1131
- when 1
1132
- # we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
1133
- # e.g.
1134
- # 1,00 - comma is a thousands_separator
1135
- # 1.000 - period is a thousands_separator
1136
- # 1,000 - comma is a decimal_mark
1137
- # 1,000,000 - comma is a decimal_mark
1138
- # 10000,00 - comma is a thousands_separator
1139
- # 1000,000 - comma is a thousands_separator
1140
-
1141
- # assign first decimal_mark for reusability
1142
- decimal_mark = used_decimal_marks.first
1143
-
1144
- # decimal_mark is used as a decimal_mark when there are multiple instances, always
1145
- if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
1146
- major, minor = num.gsub(decimal_mark, ''), 0
1147
- else
1148
- # ex: 1,000 - 1.0000 - 10001.000
1149
- # split number into possible major (dollars) and minor (cents) values
1150
- possible_major, possible_minor = num.split(decimal_mark)
1151
- possible_major ||= "0"
1152
- possible_minor ||= "00"
1153
-
1154
- # if the minor (cents) length isn't 3, assign major/minor from the possibles
1155
- # e.g.
1156
- # 1,00 => 1.00
1157
- # 1.0000 => 1.00
1158
- # 1.2 => 1.20
1159
- if possible_minor.length != 3 # thousands_separator
1160
- major, minor = possible_major, possible_minor
1161
- else
1162
- # minor length is three
1163
- # let's try to figure out intent of the thousands_separator
1164
-
1165
- # the major length is greater than three, which means
1166
- # the comma or period is used as a thousands_separator
1167
- # e.g.
1168
- # 1000,000
1169
- # 100000,000
1170
- if possible_major.length > 3
1171
- major, minor = possible_major, possible_minor
1172
- else
1173
- # number is in format ###{sep}### or ##{sep}### or #{sep}###
1174
- # handle as , is sep, . is thousands_separator
1175
- if decimal_mark == '.'
1176
- major, minor = possible_major, possible_minor
1177
- else
1178
- major, minor = "#{possible_major}#{possible_minor}", 0
1179
- end
1180
- end
1181
- end
1182
- end
1183
- else
1184
- # TODO: ParseError
1185
- raise ArgumentError, "Invalid currency amount"
1186
- end
1187
-
1188
- # build the string based on major/minor since decimal_mark/thousands_separator have been removed
1189
- # avoiding floating point arithmetic here to ensure accuracy
1190
- cents = (major.to_i * currency.subunit_to_unit)
1191
- # Because of an bug in JRuby, we can't just call #floor
1192
- minor = minor.to_s
1193
- minor = if minor.size < currency.decimal_places
1194
- (minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
1195
- elsif minor.size > currency.decimal_places
1196
- if minor[currency.decimal_places,1].to_i >= 5
1197
- minor[0,currency.decimal_places].to_i+1
1198
- else
1199
- minor[0,currency.decimal_places].to_i
1200
- end
1201
- else
1202
- minor.to_i
1203
- end
1204
- cents += minor
1205
-
1206
- # if negative, multiply by -1; otherwise, return positive cents
1207
- negative ? cents * -1 : cents
1208
- end
1209
-
1210
- end
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
+ # The value of the money in cents.
15
+ #
16
+ # @return [Integer]
17
+ attr_reader :cents
18
+
19
+ # The currency the money is in.
20
+ #
21
+ # @return [Currency]
22
+ attr_reader :currency
23
+
24
+ # The +Money::Bank+ based object used to perform currency exchanges with.
25
+ #
26
+ # @return [Money::Bank::*]
27
+ attr_reader :bank
28
+
29
+ # Class Methods
30
+ class << self
31
+ # Each Money object is associated to a bank object, which is responsible
32
+ # for currency exchange. This property allows you to specify the default
33
+ # bank object. The default value for this property is an instance if
34
+ # +Bank::VariableExchange.+ It allows one to specify custom exchange rates.
35
+ #
36
+ # @return [Money::Bank::*]
37
+ attr_accessor :default_bank
38
+
39
+ # The default currency, which is used when +Money.new+ is called without an
40
+ # explicit currency argument. The default value is Currency.new("USD"). The
41
+ # value must be a valid +Money::Currency+ instance.
42
+ #
43
+ # @return [Money::Currency]
44
+ attr_accessor :default_currency
45
+ end
46
+
47
+ # Set the default bank for creating new +Money+ objects.
48
+ self.default_bank = Bank::VariableExchange.instance
49
+
50
+ # Set the default currency for creating new +Money+ object.
51
+ self.default_currency = Currency.new("USD")
52
+
53
+ # Create a new money object with value 0.
54
+ #
55
+ # @param [Currency, String, Symbol] currency The currency to use.
56
+ #
57
+ # @return [Money]
58
+ #
59
+ # @example
60
+ # Money.empty #=> #<Money @cents=0>
61
+ def self.empty(currency = default_currency)
62
+ Money.new(0, currency)
63
+ end
64
+
65
+ # Creates a new Money object of the given value, using the Canadian
66
+ # dollar currency.
67
+ #
68
+ # @param [Integer] cents The cents value.
69
+ #
70
+ # @return [Money]
71
+ #
72
+ # @example
73
+ # n = Money.ca_dollar(100)
74
+ # n.cents #=> 100
75
+ # n.currency #=> #<Money::Currency id: cad>
76
+ def self.ca_dollar(cents)
77
+ Money.new(cents, "CAD")
78
+ end
79
+
80
+ # Creates a new Money object of the given value, using the American dollar
81
+ # currency.
82
+ #
83
+ # @param [Integer] cents The cents value.
84
+ #
85
+ # @return [Money]
86
+ #
87
+ # @example
88
+ # n = Money.us_dollar(100)
89
+ # n.cents #=> 100
90
+ # n.currency #=> #<Money::Currency id: usd>
91
+ def self.us_dollar(cents)
92
+ Money.new(cents, "USD")
93
+ end
94
+
95
+ # Creates a new Money object of the given value, using the Euro currency.
96
+ #
97
+ # @param [Integer] cents The cents value.
98
+ #
99
+ # @return [Money]
100
+ #
101
+ # @example
102
+ # n = Money.euro(100)
103
+ # n.cents #=> 100
104
+ # n.currency #=> #<Money::Currency id: eur>
105
+ def self.euro(cents)
106
+ Money.new(cents, "EUR")
107
+ end
108
+
109
+
110
+ # Creates a new Money object of +amount+ value in dollars,
111
+ # with given +currency+.
112
+ #
113
+ # The amount value is expressed in +dollars+
114
+ # where the +dollar+ is the main monetary unit,
115
+ # opposite to the subunit-based representation
116
+ # used internally by this library called +cents+.
117
+ #
118
+ # @param [Numeric] amount The money amount, in dollars.
119
+ # @param [Currency, String, Symbol] currency The currency format.
120
+ # @param [Money::Bank::*] bank The exchange bank to use.
121
+ #
122
+ # @return [Money]
123
+ #
124
+ # @example
125
+ # Money.new_with_dollars(100)
126
+ # #=> #<Money @cents=10000 @currency="USD">
127
+ # Money.new_with_dollars(100, "USD")
128
+ # #=> #<Money @cents=10000 @currency="USD">
129
+ # Money.new_with_dollars(100, "EUR")
130
+ # #=> #<Money @cents=10000 @currency="EUR">
131
+ #
132
+ # @see Money.new
133
+ #
134
+ def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
135
+ money = from_numeric(amount, currency)
136
+ # Hack! You can't change a bank
137
+ money.instance_variable_set("@bank", bank)
138
+ money
139
+ end
140
+ # Adds a new exchange rate to the default bank and return the rate.
141
+ #
142
+ # @param [Currency, String, Symbol] from_currency Currency to exchange from.
143
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
144
+ # @param [Numeric] rate Rate to exchange with.
145
+ #
146
+ # @return [Numeric]
147
+ #
148
+ # @example
149
+ # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
150
+ def self.add_rate(from_currency, to_currency, rate)
151
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
152
+ end
153
+
154
+
155
+ # Creates a new Money object of +cents+ value in cents,
156
+ # with given +currency+.
157
+ #
158
+ # Alternatively you can use the convenience
159
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
160
+ #
161
+ # @param [Integer] cents The money amount, in cents.
162
+ # @param [Currency, String, Symbol] currency The currency format.
163
+ # @param [Money::Bank::*] bank The exchange bank to use.
164
+ #
165
+ # @return [Money]
166
+ #
167
+ # @example
168
+ # Money.new(100)
169
+ # #=> #<Money @cents=100 @currency="USD">
170
+ # Money.new(100, "USD")
171
+ # #=> #<Money @cents=100 @currency="USD">
172
+ # Money.new(100, "EUR")
173
+ # #=> #<Money @cents=100 @currency="EUR">
174
+ #
175
+ # @see Money.new_with_dollars
176
+ #
177
+ def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
178
+ @cents = cents.round.to_i
179
+ @currency = Currency.wrap(currency)
180
+ @bank = bank
181
+ end
182
+
183
+ # Returns the value of the money in dollars,
184
+ # instead of in cents.
185
+ #
186
+ # @return [Float]
187
+ #
188
+ # @example
189
+ # Money.new(100).dollars # => 1.0
190
+ # Money.new_with_dollars(1).dollar # => 1.0
191
+ #
192
+ # @see #to_f
193
+ # @see #cents
194
+ #
195
+ def dollars
196
+ to_f
197
+ end
198
+
199
+ # Return string representation of currency object
200
+ #
201
+ # @return [String]
202
+ #
203
+ # @example
204
+ # Money.new(100, :USD).currency_as_string #=> "USD"
205
+ def currency_as_string
206
+ self.currency.to_s
207
+ end
208
+
209
+ # Set currency object using a string
210
+ #
211
+ # @param [String] val The currency string.
212
+ #
213
+ # @return [Money::Currency]
214
+ #
215
+ # @example
216
+ # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
217
+ def currency_as_string=(val)
218
+ @currency = Currency.wrap(val)
219
+ end
220
+
221
+ # Returns a Fixnum hash value based on the +cents+ and +currency+ attributes
222
+ # in order to use functions like & (intersection), group_by, etc.
223
+ #
224
+ # @return [Fixnum]
225
+ #
226
+ # @example
227
+ # Money.new(100).hash #=> 908351
228
+ def hash
229
+ [cents.hash, currency.hash].hash
230
+ end
231
+
232
+ # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
233
+ #
234
+ # @return [String]
235
+ #
236
+ # @example
237
+ # Money.new(100, "USD").symbol #=> "$"
238
+ def symbol
239
+ currency.symbol || "¤"
240
+ end
241
+
242
+ # Returns the amount of money as a string.
243
+ #
244
+ # @return [String]
245
+ #
246
+ # @example
247
+ # Money.ca_dollar(100).to_s #=> "1.00"
248
+ def to_s
249
+ unit, subunit = cents.abs.divmod(currency.subunit_to_unit).map{|o| o.to_s}
250
+ if currency.decimal_places == 0
251
+ return "-#{unit}" if cents < 0
252
+ return unit
253
+ end
254
+ subunit = (("0" * currency.decimal_places) + subunit)[(-1*currency.decimal_places)..-1]
255
+ return "-#{unit}#{decimal_mark}#{subunit}" if cents < 0
256
+ "#{unit}#{decimal_mark}#{subunit}"
257
+ end
258
+
259
+ # Return the amount of money as a float. Floating points cannot guarantee
260
+ # precision. Therefore, this function should only be used when you no longer
261
+ # need to represent currency or working with another system that requires
262
+ # decimals.
263
+ #
264
+ # @return [Float]
265
+ #
266
+ # @example
267
+ # Money.us_dollar(100).to_f => 1.0
268
+ def to_f
269
+ (BigDecimal.new(cents.to_s) / currency.subunit_to_unit).to_f
270
+ end
271
+
272
+ # Receive the amount of this money object in another Currency.
273
+ #
274
+ # @param [Currency, String, Symbol] other_currency Currency to exchange to.
275
+ #
276
+ # @return [Money]
277
+ #
278
+ # @example
279
+ # Money.new(2000, "USD").exchange_to("EUR")
280
+ # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
281
+ def exchange_to(other_currency)
282
+ other_currency = Currency.wrap(other_currency)
283
+ @bank.exchange_with(self, other_currency)
284
+ end
285
+
286
+ # Receive a money object with the same amount as the current Money object
287
+ # in american dollars.
288
+ #
289
+ # @return [Money]
290
+ #
291
+ # @example
292
+ # n = Money.new(100, "CAD").as_us_dollar
293
+ # n.currency #=> #<Money::Currency id: usd>
294
+ def as_us_dollar
295
+ exchange_to("USD")
296
+ end
297
+
298
+ # Receive a money object with the same amount as the current Money object
299
+ # in canadian dollar.
300
+ #
301
+ # @return [Money]
302
+ #
303
+ # @example
304
+ # n = Money.new(100, "USD").as_ca_dollar
305
+ # n.currency #=> #<Money::Currency id: cad>
306
+ def as_ca_dollar
307
+ exchange_to("CAD")
308
+ end
309
+
310
+ # Receive a money object with the same amount as the current Money object
311
+ # in euro.
312
+ #
313
+ # @return [Money]
314
+ #
315
+ # @example
316
+ # n = Money.new(100, "USD").as_euro
317
+ # n.currency #=> #<Money::Currency id: eur>
318
+ def as_euro
319
+ exchange_to("EUR")
320
+ end
321
+
322
+ # Conversation to +self+.
323
+ #
324
+ # @return [self]
325
+ def to_money
326
+ self
327
+ end
328
+
329
+ # Common inspect function
330
+ #
331
+ # @return [String]
332
+ def inspect
333
+ "#<Money cents:#{cents} currency:#{currency}>"
334
+ end
335
+
336
+ # Allocates money between different parties without loosing pennies.
337
+ # After the mathmatically split has been performed, left over pennies will
338
+ # be distributed round-robin amongst the parties. This means that parties
339
+ # listed first will likely recieve more pennies then ones that are listed later
340
+ #
341
+ # @param [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% ot party2, and 25% to party3.
342
+ #
343
+ # @return [Array<Money, Money, Money>]
344
+ #
345
+ # @example
346
+ # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
347
+ # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
348
+ def allocate(splits)
349
+ allocations = splits.inject(0.0) {|sum, i| sum += i }
350
+ raise ArgumentError, "splits add to more then 100%" if (allocations - 1.0) > Float::EPSILON
351
+
352
+ left_over = cents
353
+
354
+ amounts = splits.collect do |ratio|
355
+ fraction = (cents * ratio / allocations).floor
356
+ left_over -= fraction
357
+ fraction
358
+ end
359
+
360
+ left_over.times { |i| amounts[i % amounts.length] += 1 }
361
+
362
+ return amounts.collect { |cents| Money.new(cents, currency) }
363
+ end
364
+
365
+ # Split money amongst parties evenly without loosing pennies.
366
+ #
367
+ # @param [2] number of parties.
368
+ #
369
+ # @return [Array<Money, Money, Money>]
370
+ #
371
+ # @example
372
+ # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
373
+ def split(num)
374
+ raise ArgumentError, "need at least one party" if num < 1
375
+ low = Money.new(cents / num)
376
+ high = Money.new(low.cents + 1)
377
+
378
+ remainder = cents % num
379
+ result = []
380
+
381
+ num.times do |index|
382
+ result[index] = index < remainder ? high : low
383
+ end
384
+
385
+ return result
386
+ end
387
+ end