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/CHANGELOG.md +335 -319
- data/LICENSE +21 -21
- data/README.md +214 -209
- data/Rakefile +49 -49
- data/lib/money.rb +27 -27
- data/lib/money/bank/base.rb +131 -131
- data/lib/money/bank/variable_exchange.rb +252 -251
- data/lib/money/core_extensions.rb +82 -63
- data/lib/money/currency.rb +422 -415
- data/lib/money/money.rb +387 -1210
- data/lib/money/money/arithmetic.rb +246 -0
- data/lib/money/money/formatting.rb +234 -0
- data/lib/money/money/parsing.rb +350 -0
- data/money.gemspec +34 -27
- data/spec/bank/base_spec.rb +72 -72
- data/spec/bank/variable_exchange_spec.rb +238 -238
- data/spec/core_extensions_spec.rb +158 -142
- data/spec/currency_spec.rb +133 -128
- data/spec/money/arithmetic_spec.rb +479 -0
- data/spec/money/formatting_spec.rb +352 -0
- data/spec/money/parsing_spec.rb +197 -0
- data/spec/money_spec.rb +271 -1268
- data/spec/spec_helper.rb +28 -17
- metadata +33 -23
- data/lib/money.rbc +0 -170
- data/lib/money/bank/base.rbc +0 -800
- data/lib/money/bank/variable_exchange.rbc +0 -2496
- data/lib/money/core_extensions.rbc +0 -474
- data/lib/money/currency.rbc +0 -22600
- data/lib/money/money.rbc +0 -10070
- data/spec/bank/base_spec.rbc +0 -2409
- data/spec/bank/variable_exchange_spec.rbc +0 -7389
- data/spec/core_extensions_spec.rbc +0 -5215
- data/spec/currency_spec.rbc +0 -4341
- data/spec/money_spec.rbc +0 -50121
- data/spec/spec_helper.rbc +0 -346
data/lib/money/money.rb
CHANGED
@@ -1,1210 +1,387 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'money/bank/variable_exchange'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
# The default
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
#
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
#
|
90
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
# @
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
# @
|
127
|
-
#
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
# @
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
#
|
200
|
-
#
|
201
|
-
# @
|
202
|
-
#
|
203
|
-
#
|
204
|
-
# Money.
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
#
|
210
|
-
#
|
211
|
-
# @
|
212
|
-
#
|
213
|
-
#
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
#
|
222
|
-
#
|
223
|
-
#
|
224
|
-
# @
|
225
|
-
#
|
226
|
-
# @
|
227
|
-
#
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
#
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
#
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
#
|
260
|
-
#
|
261
|
-
#
|
262
|
-
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
#
|
267
|
-
# Money.
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
#
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
#
|
278
|
-
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
#
|
287
|
-
#
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
292
|
-
# Money.
|
293
|
-
# #=> #<Money
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
#
|
299
|
-
#
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
#
|
311
|
-
#
|
312
|
-
#
|
313
|
-
#
|
314
|
-
#
|
315
|
-
#
|
316
|
-
#
|
317
|
-
#
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
#
|
323
|
-
#
|
324
|
-
# @
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
#
|
330
|
-
#
|
331
|
-
#
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
Money.
|
363
|
-
end
|
364
|
-
|
365
|
-
|
366
|
-
#
|
367
|
-
#
|
368
|
-
#
|
369
|
-
#
|
370
|
-
#
|
371
|
-
#
|
372
|
-
#
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
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
|