money 3.6.1 → 3.6.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.
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