papercavalier-money 3.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,405 @@
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
+
46
+ # Use this to disable i18n even if it's used by other objects in your app.
47
+ #
48
+ # @return [true,false]
49
+ attr_accessor :use_i18n
50
+ end
51
+
52
+ # Set the default bank for creating new +Money+ objects.
53
+ self.default_bank = Bank::VariableExchange.instance
54
+
55
+ # Set the default currency for creating new +Money+ object.
56
+ self.default_currency = Currency.new("USD")
57
+
58
+ # Default to using i18n
59
+ self.use_i18n = true
60
+
61
+ # Create a new money object with value 0.
62
+ #
63
+ # @param [Currency, String, Symbol] currency The currency to use.
64
+ #
65
+ # @return [Money]
66
+ #
67
+ # @example
68
+ # Money.empty #=> #<Money @cents=0>
69
+ def self.empty(currency = default_currency)
70
+ Money.new(0, currency)
71
+ end
72
+
73
+ # Creates a new Money object of the given value, using the Canadian
74
+ # dollar currency.
75
+ #
76
+ # @param [Integer] cents The cents value.
77
+ #
78
+ # @return [Money]
79
+ #
80
+ # @example
81
+ # n = Money.ca_dollar(100)
82
+ # n.cents #=> 100
83
+ # n.currency #=> #<Money::Currency id: cad>
84
+ def self.ca_dollar(cents)
85
+ Money.new(cents, "CAD")
86
+ end
87
+
88
+ # Creates a new Money object of the given value, using the American dollar
89
+ # currency.
90
+ #
91
+ # @param [Integer] cents The cents value.
92
+ #
93
+ # @return [Money]
94
+ #
95
+ # @example
96
+ # n = Money.us_dollar(100)
97
+ # n.cents #=> 100
98
+ # n.currency #=> #<Money::Currency id: usd>
99
+ def self.us_dollar(cents)
100
+ Money.new(cents, "USD")
101
+ end
102
+
103
+ # Creates a new Money object of the given value, using the Euro currency.
104
+ #
105
+ # @param [Integer] cents The cents value.
106
+ #
107
+ # @return [Money]
108
+ #
109
+ # @example
110
+ # n = Money.euro(100)
111
+ # n.cents #=> 100
112
+ # n.currency #=> #<Money::Currency id: eur>
113
+ def self.euro(cents)
114
+ Money.new(cents, "EUR")
115
+ end
116
+
117
+
118
+ # Creates a new Money object of +amount+ value in dollars,
119
+ # with given +currency+.
120
+ #
121
+ # The amount value is expressed in +dollars+
122
+ # where the +dollar+ is the main monetary unit,
123
+ # opposite to the subunit-based representation
124
+ # used internally by this library called +cents+.
125
+ #
126
+ # @param [Numeric] amount The money amount, in dollars.
127
+ # @param [Currency, String, Symbol] currency The currency format.
128
+ # @param [Money::Bank::*] bank The exchange bank to use.
129
+ #
130
+ # @return [Money]
131
+ #
132
+ # @example
133
+ # Money.new_with_dollars(100)
134
+ # #=> #<Money @cents=10000 @currency="USD">
135
+ # Money.new_with_dollars(100, "USD")
136
+ # #=> #<Money @cents=10000 @currency="USD">
137
+ # Money.new_with_dollars(100, "EUR")
138
+ # #=> #<Money @cents=10000 @currency="EUR">
139
+ #
140
+ # @see Money.new
141
+ #
142
+ def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
143
+ money = from_numeric(amount, currency)
144
+ # Hack! You can't change a bank
145
+ money.instance_variable_set("@bank", bank)
146
+ money
147
+ end
148
+ # Adds a new exchange rate to the default bank and return the rate.
149
+ #
150
+ # @param [Currency, String, Symbol] from_currency Currency to exchange from.
151
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
152
+ # @param [Numeric] rate Rate to exchange with.
153
+ #
154
+ # @return [Numeric]
155
+ #
156
+ # @example
157
+ # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
158
+ def self.add_rate(from_currency, to_currency, rate)
159
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
160
+ end
161
+
162
+
163
+ # Creates a new Money object of +cents+ value in cents,
164
+ # with given +currency+.
165
+ #
166
+ # Alternatively you can use the convenience
167
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
168
+ #
169
+ # @param [Integer] cents The money amount, in cents.
170
+ # @param [Currency, String, Symbol] currency The currency format.
171
+ # @param [Money::Bank::*] bank The exchange bank to use.
172
+ #
173
+ # @return [Money]
174
+ #
175
+ # @example
176
+ # Money.new(100)
177
+ # #=> #<Money @cents=100 @currency="USD">
178
+ # Money.new(100, "USD")
179
+ # #=> #<Money @cents=100 @currency="USD">
180
+ # Money.new(100, "EUR")
181
+ # #=> #<Money @cents=100 @currency="EUR">
182
+ #
183
+ # @see Money.new_with_dollars
184
+ #
185
+ def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
186
+ @cents = cents.round.to_i
187
+ @currency = Currency.wrap(currency)
188
+ @bank = bank
189
+ end
190
+
191
+ # Returns the value of the money in dollars,
192
+ # instead of in cents.
193
+ #
194
+ # @return [Float]
195
+ #
196
+ # @example
197
+ # Money.new(100).dollars # => 1.0
198
+ # Money.new_with_dollars(1).dollar # => 1.0
199
+ #
200
+ # @see #to_f
201
+ # @see #cents
202
+ #
203
+ def dollars
204
+ to_f
205
+ end
206
+
207
+ # Return string representation of currency object
208
+ #
209
+ # @return [String]
210
+ #
211
+ # @example
212
+ # Money.new(100, :USD).currency_as_string #=> "USD"
213
+ def currency_as_string
214
+ self.currency.to_s
215
+ end
216
+
217
+ # Set currency object using a string
218
+ #
219
+ # @param [String] val The currency string.
220
+ #
221
+ # @return [Money::Currency]
222
+ #
223
+ # @example
224
+ # Money.new(100).currency_as_string("CAD") #=> #<Money::Currency id: cad>
225
+ def currency_as_string=(val)
226
+ @currency = Currency.wrap(val)
227
+ end
228
+
229
+ # Returns a Fixnum hash value based on the +cents+ and +currency+ attributes
230
+ # in order to use functions like & (intersection), group_by, etc.
231
+ #
232
+ # @return [Fixnum]
233
+ #
234
+ # @example
235
+ # Money.new(100).hash #=> 908351
236
+ def hash
237
+ [cents.hash, currency.hash].hash
238
+ end
239
+
240
+ # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
241
+ #
242
+ # @return [String]
243
+ #
244
+ # @example
245
+ # Money.new(100, "USD").symbol #=> "$"
246
+ def symbol
247
+ currency.symbol || "¤"
248
+ end
249
+
250
+ # Returns the amount of money as a string.
251
+ #
252
+ # @return [String]
253
+ #
254
+ # @example
255
+ # Money.ca_dollar(100).to_s #=> "1.00"
256
+ def to_s
257
+ unit, subunit = cents.abs.divmod(currency.subunit_to_unit).map{|o| o.to_s}
258
+ if currency.decimal_places == 0
259
+ return "-#{unit}" if cents < 0
260
+ return unit
261
+ end
262
+ subunit = (("0" * currency.decimal_places) + subunit)[(-1*currency.decimal_places)..-1]
263
+ return "-#{unit}#{decimal_mark}#{subunit}" if cents < 0
264
+ "#{unit}#{decimal_mark}#{subunit}"
265
+ end
266
+
267
+ # Return the amount of money as a BigDecimal.
268
+ #
269
+ # @return [BigDecimal]
270
+ #
271
+ # @example
272
+ # Money.us_dollar(100).to_d => BigDecimal.new("1.0")
273
+ def to_d
274
+ BigDecimal.new(cents.to_s) / BigDecimal.new(currency.subunit_to_unit.to_s)
275
+ end
276
+
277
+ # Return the amount of money as a float. Floating points cannot guarantee
278
+ # precision. Therefore, this function should only be used when you no longer
279
+ # need to represent currency or working with another system that requires
280
+ # decimals.
281
+ #
282
+ # @return [Float]
283
+ #
284
+ # @example
285
+ # Money.us_dollar(100).to_f => 1.0
286
+ def to_f
287
+ to_d.to_f
288
+ end
289
+
290
+ # Receive the amount of this money object in another Currency.
291
+ #
292
+ # @param [Currency, String, Symbol] other_currency Currency to exchange to.
293
+ #
294
+ # @return [Money]
295
+ #
296
+ # @example
297
+ # Money.new(2000, "USD").exchange_to("EUR")
298
+ # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
299
+ def exchange_to(other_currency)
300
+ other_currency = Currency.wrap(other_currency)
301
+ @bank.exchange_with(self, other_currency)
302
+ end
303
+
304
+ # Receive a money object with the same amount as the current Money object
305
+ # in american dollars.
306
+ #
307
+ # @return [Money]
308
+ #
309
+ # @example
310
+ # n = Money.new(100, "CAD").as_us_dollar
311
+ # n.currency #=> #<Money::Currency id: usd>
312
+ def as_us_dollar
313
+ exchange_to("USD")
314
+ end
315
+
316
+ # Receive a money object with the same amount as the current Money object
317
+ # in canadian dollar.
318
+ #
319
+ # @return [Money]
320
+ #
321
+ # @example
322
+ # n = Money.new(100, "USD").as_ca_dollar
323
+ # n.currency #=> #<Money::Currency id: cad>
324
+ def as_ca_dollar
325
+ exchange_to("CAD")
326
+ end
327
+
328
+ # Receive a money object with the same amount as the current Money object
329
+ # in euro.
330
+ #
331
+ # @return [Money]
332
+ #
333
+ # @example
334
+ # n = Money.new(100, "USD").as_euro
335
+ # n.currency #=> #<Money::Currency id: eur>
336
+ def as_euro
337
+ exchange_to("EUR")
338
+ end
339
+
340
+ # Conversation to +self+.
341
+ #
342
+ # @return [self]
343
+ def to_money
344
+ self
345
+ end
346
+
347
+ # Common inspect function
348
+ #
349
+ # @return [String]
350
+ def inspect
351
+ "#<Money cents:#{cents} currency:#{currency}>"
352
+ end
353
+
354
+ # Allocates money between different parties without loosing pennies.
355
+ # After the mathmatically split has been performed, left over pennies will
356
+ # be distributed round-robin amongst the parties. This means that parties
357
+ # listed first will likely recieve more pennies then ones that are listed later
358
+ #
359
+ # @param [0.50, 0.25, 0.25] to give 50% of the cash to party1, 25% ot party2, and 25% to party3.
360
+ #
361
+ # @return [Array<Money, Money, Money>]
362
+ #
363
+ # @example
364
+ # Money.new(5, "USD").allocate([0.3,0.7)) #=> [Money.new(2), Money.new(3)]
365
+ # Money.new(100, "USD").allocate([0.33,0.33,0.33]) #=> [Money.new(34), Money.new(33), Money.new(33)]
366
+ def allocate(splits)
367
+ allocations = splits.inject(0.0) {|sum, i| sum += i }
368
+ raise ArgumentError, "splits add to more then 100%" if (allocations - 1.0) > Float::EPSILON
369
+
370
+ left_over = cents
371
+
372
+ amounts = splits.collect do |ratio|
373
+ fraction = (cents * ratio / allocations).floor
374
+ left_over -= fraction
375
+ fraction
376
+ end
377
+
378
+ left_over.times { |i| amounts[i % amounts.length] += 1 }
379
+
380
+ return amounts.collect { |cents| Money.new(cents, currency) }
381
+ end
382
+
383
+ # Split money amongst parties evenly without loosing pennies.
384
+ #
385
+ # @param [2] number of parties.
386
+ #
387
+ # @return [Array<Money, Money, Money>]
388
+ #
389
+ # @example
390
+ # Money.new(100, "USD").split(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
391
+ def split(num)
392
+ raise ArgumentError, "need at least one party" if num < 1
393
+ low = Money.new(cents / num)
394
+ high = Money.new(low.cents + 1)
395
+
396
+ remainder = cents % num
397
+ result = []
398
+
399
+ num.times do |index|
400
+ result[index] = index < remainder ? high : low
401
+ end
402
+
403
+ return result
404
+ end
405
+ end
@@ -0,0 +1,246 @@
1
+ class Money
2
+ module Arithmetic
3
+
4
+ # Checks whether two money objects have the same currency and the same
5
+ # amount. Checks against money objects with a different currency and checks
6
+ # against objects that do not respond to #to_money will always return false.
7
+ #
8
+ # @param [Money] other_money Value to compare with.
9
+ #
10
+ # @return [Boolean]
11
+ #
12
+ # @example
13
+ # Money.new(100) == Money.new(101) #=> false
14
+ # Money.new(100) == Money.new(100) #=> true
15
+ def ==(other_money)
16
+ if other_money.respond_to?(:to_money)
17
+ other_money = other_money.to_money
18
+ cents == other_money.cents && self.currency == other_money.currency
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ # Synonymous with +#==+.
25
+ #
26
+ # @param [Money] other_money Value to compare with.
27
+ #
28
+ # @return [Money]
29
+ #
30
+ # @see #==
31
+ def eql?(other_money)
32
+ self == other_money
33
+ end
34
+
35
+ def <=>(other_money)
36
+ if other_money.respond_to?(:to_money)
37
+ other_money = other_money.to_money
38
+ if self.currency == other_money.currency
39
+ cents <=> other_money.cents
40
+ else
41
+ cents <=> other_money.exchange_to(currency).cents
42
+ end
43
+ else
44
+ raise ArgumentError, "Comparison of #{self.class} with #{other_money.inspect} failed"
45
+ end
46
+ end
47
+
48
+ # Returns a new Money object containing the sum of the two operands' monetary
49
+ # values. If +other_money+ has a different currency then its monetary value
50
+ # is automatically exchanged to this object's currency using +exchange_to+.
51
+ #
52
+ # @param [Money] other_money Other +Money+ object to add.
53
+ #
54
+ # @return [Money]
55
+ #
56
+ # @example
57
+ # Money.new(100) + Money.new(100) #=> #<Money @cents=200>
58
+ def +(other_money)
59
+ if currency == other_money.currency
60
+ Money.new(cents + other_money.cents, other_money.currency)
61
+ else
62
+ Money.new(cents + other_money.exchange_to(currency).cents, currency)
63
+ end
64
+ end
65
+
66
+ # Returns a new Money object containing the difference between the two
67
+ # operands' monetary values. If +other_money+ has a different currency then
68
+ # its monetary value is automatically exchanged to this object's currency
69
+ # using +exchange_to+.
70
+ #
71
+ # @param [Money] other_money Other +Money+ object to subtract.
72
+ #
73
+ # @return [Money]
74
+ #
75
+ # @example
76
+ # Money.new(100) - Money.new(99) #=> #<Money @cents=1>
77
+ def -(other_money)
78
+ if currency == other_money.currency
79
+ Money.new(cents - other_money.cents, other_money.currency)
80
+ else
81
+ Money.new(cents - other_money.exchange_to(currency).cents, currency)
82
+ end
83
+ end
84
+
85
+ # Multiplies the monetary value with the given number and returns a new
86
+ # +Money+ object with this monetary value and the same currency.
87
+ #
88
+ # Note that you can't multiply a Money object by an other +Money+ object.
89
+ #
90
+ # @param [Numeric] value Number to multiply by.
91
+ #
92
+ # @return [Money] The resulting money.
93
+ #
94
+ # @raise [ArgumentError] If +value+ is a Money instance.
95
+ #
96
+ # @example
97
+ # Money.new(100) * 2 #=> #<Money @cents=200>
98
+ #
99
+ def *(value)
100
+ if value.is_a?(Money)
101
+ raise ArgumentError, "Can't multiply a Money by a Money"
102
+ else
103
+ Money.new(cents * value, currency)
104
+ end
105
+ end
106
+
107
+ # Divides the monetary value with the given number and returns a new +Money+
108
+ # object with this monetary value and the same currency.
109
+ # Can also divide by another +Money+ object to get a ratio.
110
+ #
111
+ # +Money/Numeric+ returns +Money+. +Money/Money+ returns +Float+.
112
+ #
113
+ # @param [Money, Numeric] value Number to divide by.
114
+ #
115
+ # @return [Money] The resulting money if you divide Money by a number.
116
+ # @return [Float] The resulting number if you divide Money by a Money.
117
+ #
118
+ # @example
119
+ # Money.new(100) / 10 #=> #<Money @cents=10>
120
+ # Money.new(100) / Money.new(10) #=> 10.0
121
+ #
122
+ def /(value)
123
+ if value.is_a?(Money)
124
+ if currency == value.currency
125
+ (cents / BigDecimal.new(value.cents.to_s)).to_f
126
+ else
127
+ (cents / BigDecimal(value.exchange_to(currency).cents.to_s)).to_f
128
+ end
129
+ else
130
+ Money.new(cents / value, currency)
131
+ end
132
+ end
133
+
134
+ # Synonym for +#/+.
135
+ #
136
+ # @param [Money, Numeric] value Number to divide by.
137
+ #
138
+ # @return [Money] The resulting money if you divide Money by a number.
139
+ # @return [Float] The resulting number if you divide Money by a Money.
140
+ #
141
+ # @see #/
142
+ #
143
+ def div(value)
144
+ self / value
145
+ end
146
+
147
+ # Divide money by money or fixnum and return array containing quotient and
148
+ # modulus.
149
+ #
150
+ # @param [Money, Fixnum] val Number to divmod by.
151
+ #
152
+ # @return [Array<Money,Money>,Array<Fixnum,Money>]
153
+ #
154
+ # @example
155
+ # Money.new(100).divmod(9) #=> [#<Money @cents=11>, #<Money @cents=1>]
156
+ # Money.new(100).divmod(Money.new(9)) #=> [11, #<Money @cents=1>]
157
+ def divmod(val)
158
+ if val.is_a?(Money)
159
+ a = self.cents
160
+ b = self.currency == val.currency ? val.cents : val.exchange_to(self.currency).cents
161
+ q, m = a.divmod(b)
162
+ return [q, Money.new(m, self.currency)]
163
+ else
164
+ return [self.div(val), Money.new(self.cents.modulo(val), self.currency)]
165
+ end
166
+ end
167
+
168
+ # Equivalent to +self.divmod(val)[1]+
169
+ #
170
+ # @param [Money, Fixnum] val Number take modulo with.
171
+ #
172
+ # @return [Money]
173
+ #
174
+ # @example
175
+ # Money.new(100).modulo(9) #=> #<Money @cents=1>
176
+ # Money.new(100).modulo(Money.new(9)) #=> #<Money @cents=1>
177
+ def modulo(val)
178
+ self.divmod(val)[1]
179
+ end
180
+
181
+ # Synonym for +#modulo+.
182
+ #
183
+ # @param [Money, Fixnum] val Number take modulo with.
184
+ #
185
+ # @return [Money]
186
+ #
187
+ # @see #modulo
188
+ def %(val)
189
+ self.modulo(val)
190
+ end
191
+
192
+ # If different signs +self.modulo(val) - val+ otherwise +self.modulo(val)+
193
+ #
194
+ # @param [Money, Fixnum] val Number to rake remainder with.
195
+ #
196
+ # @return [Money]
197
+ #
198
+ # @example
199
+ # Money.new(100).remainder(9) #=> #<Money @cents=1>
200
+ def remainder(val)
201
+ a, b = self, val
202
+ b = b.exchange_to(a.currency) if b.is_a?(Money) and a.currency != b.currency
203
+
204
+ a_sign, b_sign = :pos, :pos
205
+ a_sign = :neg if a.cents < 0
206
+ b_sign = :neg if (b.is_a?(Money) and b.cents < 0) or (b < 0)
207
+
208
+ return a.modulo(b) if a_sign == b_sign
209
+ a.modulo(b) - (b.is_a?(Money) ? b : Money.new(b, a.currency))
210
+ end
211
+
212
+ # Return absolute value of self as a new Money object.
213
+ #
214
+ # @return [Money]
215
+ #
216
+ # @example
217
+ # Money.new(-100).abs #=> #<Money @cents=100>
218
+ def abs
219
+ Money.new(self.cents.abs, self.currency)
220
+ end
221
+
222
+ # Test if the money amount is zero.
223
+ #
224
+ # @return [Boolean]
225
+ #
226
+ # @example
227
+ # Money.new(100).zero? #=> false
228
+ # Money.new(0).zero? #=> true
229
+ def zero?
230
+ cents == 0
231
+ end
232
+
233
+ # Test if the money amount is non-zero. Returns this money object if it is
234
+ # non-zero, or nil otherwise, like +Numeric#nonzero?+.
235
+ #
236
+ # @return [Money, nil]
237
+ #
238
+ # @example
239
+ # Money.new(100).nonzero? #=> #<Money @cents=100>
240
+ # Money.new(0).nonzero? #=> nil
241
+ def nonzero?
242
+ cents != 0 ? self : nil
243
+ end
244
+
245
+ end
246
+ end