money-joshm1 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,371 @@
1
+ #encoding: utf-8
2
+
3
+ class Money
4
+ module Parsing
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ # Parses the current string and converts it to a +Money+ object.
11
+ # Excess characters will be discarded.
12
+ #
13
+ # @param [String, #to_s] input The input to parse.
14
+ # @param [Currency, String, Symbol] currency The currency format.
15
+ # The currency to set the resulting +Money+ object to.
16
+ #
17
+ # @return [Money]
18
+ #
19
+ # @raise [ArgumentError] If any +currency+ is supplied and
20
+ # given value doesn't match the one extracted from
21
+ # the +input+ string.
22
+ #
23
+ # @example
24
+ # '100'.to_money #=> #<Money @fractional=10000>
25
+ # '100.37'.to_money #=> #<Money @fractional=10037>
26
+ # '100 USD'.to_money #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
27
+ # 'USD 100'.to_money #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
28
+ # '$100 USD'.to_money #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
29
+ # 'hello 2000 world'.to_money #=> #<Money @fractional=200000 @currency=#<Money::Currency id: usd>>
30
+ #
31
+ # @example Mismatching currencies
32
+ # 'USD 2000'.to_money("EUR") #=> ArgumentError
33
+ #
34
+ # @see #from_string
35
+ #
36
+ def parse(input, currency = nil)
37
+ i = input.to_s.strip
38
+
39
+ # raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect
40
+
41
+ # Check the first character for a currency symbol, alternatively get it
42
+ # from the stated currency string
43
+ c = if Money.assume_from_symbol && i =~ /^(\$|€|£)/
44
+ case i
45
+ when /^\$/ then "USD"
46
+ when /^€/ then "EUR"
47
+ when /^£/ then "GBP"
48
+ end
49
+ else
50
+ i[/[A-Z]{2,3}/]
51
+ end
52
+
53
+ # check that currency passed and embedded currency are the same,
54
+ # and negotiate the final currency
55
+ if currency.nil? and c.nil?
56
+ currency = Money.default_currency
57
+ elsif currency.nil?
58
+ currency = c
59
+ elsif c.nil?
60
+ currency = currency
61
+ elsif currency != c
62
+ # TODO: ParseError
63
+ raise ArgumentError, "Mismatching Currencies"
64
+ end
65
+ currency = Money::Currency.wrap(currency)
66
+
67
+ fractional = extract_cents(i, currency)
68
+ new(fractional, currency)
69
+ end
70
+
71
+ # Converts a String into a Money object treating the +value+
72
+ # as amount and converting to fractional unit,
73
+ # according to +currency+ subunit property,
74
+ # before instantiating the Money object.
75
+ #
76
+ # Behind the scenes, this method relies on {#from_bigdecimal}
77
+ # to avoid problems with string-to-numeric conversion.
78
+ #
79
+ # @param [String, #to_s] value The money amount, in dollars.
80
+ # @param [Currency, String, Symbol] currency
81
+ # The currency to set the resulting +Money+ object to.
82
+ #
83
+ # @return [Money]
84
+ #
85
+ # @example
86
+ # Money.from_string("100")
87
+ # #=> #<Money @fractional=10000 @currency="USD">
88
+ # Money.from_string("100", "USD")
89
+ # #=> #<Money @fractional=10000 @currency="USD">
90
+ # Money.from_string("100", "EUR")
91
+ # #=> #<Money @fractional=10000 @currency="EUR">
92
+ # Money.from_string("100", "BHD")
93
+ # #=> #<Money @fractional=100 @currency="BHD">
94
+ #
95
+ # @see String#to_money
96
+ # @see #parse
97
+ #
98
+ def from_string(value, currency = Money.default_currency)
99
+ from_bigdecimal(BigDecimal.new(value.to_s), currency)
100
+ end
101
+
102
+ # Converts a Fixnum into a Money object treating the +value+
103
+ # as amount and to corresponding fractional unit,
104
+ # according to +currency+ subunit property,
105
+ # before instantiating the Money object.
106
+ #
107
+ # @param [Fixnum] value The money amount, in dollars.
108
+ # @param [Currency, String, Symbol] currency The currency format.
109
+ #
110
+ # @return [Money]
111
+ #
112
+ # @example
113
+ # Money.from_fixnum(100)
114
+ # #=> #<Money @fractional=10000 @currency="USD">
115
+ # Money.from_fixnum(100, "USD")
116
+ # #=> #<Money @fractional=10000 @currency="USD">
117
+ # Money.from_fixnum(100, "EUR")
118
+ # #=> #<Money @fractional=10000 @currency="EUR">
119
+ # Money.from_fixnum(100, "BHD")
120
+ # #=> #<Money @fractional=100 @currency="BHD">
121
+ #
122
+ # @see Fixnum#to_money
123
+ # @see #from_numeric
124
+ #
125
+ def from_fixnum(value, currency = Money.default_currency)
126
+ currency = Money::Currency.wrap(currency)
127
+ amount = value * currency.subunit_to_unit
128
+ new(amount, currency)
129
+ end
130
+
131
+ # Converts a Float into a Money object treating the +value+
132
+ # as dollars and to corresponding fractional unit,
133
+ # according to +currency+ subunit property,
134
+ # before instantiating the Money object.
135
+ #
136
+ # Behind the scenes, this method relies on Money.from_bigdecimal
137
+ # to avoid problems with floating point precision.
138
+ #
139
+ # @param [Float] value The money amount, in dollars.
140
+ # @param [Currency, String, Symbol] currency The currency format.
141
+ #
142
+ # @return [Money]
143
+ #
144
+ # @example
145
+ # Money.from_float(100.0)
146
+ # #=> #<Money @fractional=10000 @currency="USD">
147
+ # Money.from_float(100.0, "USD")
148
+ # #=> #<Money @fractional=10000 @currency="USD">
149
+ # Money.from_float(100.0, "EUR")
150
+ # #=> #<Money @fractional=10000 @currency="EUR">
151
+ # Money.from_float(100.0, "BHD")
152
+ # #=> #<Money @fractional=100 @currency="BHD">
153
+ #
154
+ # @see Float#to_money
155
+ # @see #from_numeric
156
+ #
157
+ def from_float(value, currency = Money.default_currency)
158
+ from_bigdecimal(BigDecimal.new(value.to_s), currency)
159
+ end
160
+
161
+ # Converts a BigDecimal into a Money object treating the +value+
162
+ # as dollars and converting to corresponding fractional unit,
163
+ # according to +currency+ subunit property,
164
+ # before instantiating the Money object.
165
+ #
166
+ # @param [BigDecimal] value The money amount, in dollars.
167
+ # @param [Currency, String, Symbol] currency The currency format.
168
+ #
169
+ # @return [Money]
170
+ #
171
+ # @example
172
+ # Money.from_bigdecimal(BigDecimal.new("100")
173
+ # #=> #<Money @fractional=10000 @currency="USD">
174
+ # Money.from_bigdecimal(BigDecimal.new("100", "USD")
175
+ # #=> #<Money @fractional=10000 @currency="USD">
176
+ # Money.from_bigdecimal(BigDecimal.new("100", "EUR")
177
+ # #=> #<Money @fractional=10000 @currency="EUR">
178
+ # Money.from_bigdecimal(BigDecimal.new("100", "BHD")
179
+ # #=> #<Money @fractional=100 @currency="BHD">
180
+ #
181
+ # @see BigDecimal#to_money
182
+ # @see #from_numeric
183
+ #
184
+ def from_bigdecimal(value, currency = Money.default_currency)
185
+ currency = Money::Currency.wrap(currency)
186
+ amount = value * currency.subunit_to_unit
187
+ new(amount.round, currency)
188
+ end
189
+
190
+ # Converts a Numeric value into a Money object treating the +value+
191
+ # as dollars and converting to corresponding fractional unit,
192
+ # according to +currency+ subunit property,
193
+ # before instantiating the Money object.
194
+ #
195
+ # This method relies on various +Money.from_*+ methods
196
+ # and tries to forwards the call to the most appropriate method
197
+ # in order to reduce computation effort.
198
+ # For instance, if +value+ is an Integer, this method calls
199
+ # {#from_fixnum} instead of using the default
200
+ # {#from_bigdecimal} which adds the overload to converts
201
+ # the value into a slower BigDecimal instance.
202
+ #
203
+ # @param [Numeric] value The money amount, in dollars.
204
+ # @param [Currency, String, Symbol] currency The currency format.
205
+ #
206
+ # @return [Money]
207
+ #
208
+ # @raise +ArgumentError+ Unless +value+ is a supported type.
209
+ #
210
+ # @example
211
+ # Money.from_numeric(100)
212
+ # #=> #<Money @fractional=10000 @currency="USD">
213
+ # Money.from_numeric(100.00)
214
+ # #=> #<Money @fractional=10000 @currency="USD">
215
+ # Money.from_numeric("100")
216
+ # #=> ArgumentError
217
+ #
218
+ # @see Numeric#to_money
219
+ # @see #from_fixnum
220
+ # @see #from_float
221
+ # @see #from_bigdecimal
222
+ #
223
+ def from_numeric(value, currency = Money.default_currency)
224
+ case value
225
+ when Fixnum
226
+ from_fixnum(value, currency)
227
+ when Numeric
228
+ from_bigdecimal(BigDecimal.new(value.to_s), currency)
229
+ else
230
+ raise ArgumentError, "`value' should be a Numeric object"
231
+ end
232
+ end
233
+
234
+ # Takes a number string and attempts to massage out the number.
235
+ #
236
+ # @param [String] input The string containing a potential number.
237
+ #
238
+ # @return [Integer]
239
+ #
240
+ def extract_cents(input, currency = Money.default_currency)
241
+ # remove anything that's not a number, potential thousands_separator, or minus sign
242
+ num = input.gsub(/[^\d.,'-]/, '')
243
+
244
+ # set a boolean flag for if the number is negative or not
245
+ negative = num =~ /^-|-$/ ? true : false
246
+
247
+ # decimal mark character
248
+ decimal_char = currency.decimal_mark
249
+
250
+ # if negative, remove the minus sign from the number
251
+ # if it's not negative, the hyphen makes the value invalid
252
+ if negative
253
+ num = num.sub(/^-|-$/, '')
254
+ end
255
+
256
+ raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')
257
+
258
+ #if the number ends with punctuation, just throw it out. If it means decimal,
259
+ #it won't hurt anything. If it means a literal period or comma, this will
260
+ #save it from being mis-interpreted as a decimal.
261
+ num.chop! if num.match(/[\.|,]$/)
262
+
263
+ # gather all decimal_marks within the result number
264
+ used_delimiters = num.scan(/[^\d]/)
265
+
266
+ # determine the number of unique decimal_marks within the number
267
+ #
268
+ # e.g.
269
+ # $1,234,567.89 would return 2 (, and .)
270
+ # $125,00 would return 1
271
+ # $199 would return 0
272
+ # $1 234,567.89 would raise an error (decimal_marks are space, comma, and period)
273
+ case used_delimiters.uniq.length
274
+ # no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
275
+ when 0 then major, minor = num, 0
276
+
277
+ # two decimal_marks, so we know the last item in this array is the
278
+ # major/minor thousands_separator and the rest are decimal_marks
279
+ when 2
280
+ thousands_separator, decimal_mark = used_delimiters.uniq
281
+
282
+ # remove all thousands_separator, split on the decimal_mark
283
+ major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
284
+ min = 0 unless min
285
+ when 1
286
+ # we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
287
+ # e.g.
288
+ # 1,00 - comma is a thousands_separator
289
+ # 1.000 - period is a thousands_separator
290
+ # 1,000 - comma is a decimal_mark
291
+ # 1,000,000 - comma is a decimal_mark
292
+ # 10000,00 - comma is a thousands_separator
293
+ # 1000,000 - comma is a thousands_separator
294
+
295
+ # assign first decimal_mark for reusability
296
+ decimal_mark = used_delimiters.first
297
+
298
+ # When we have identified the decimal mark character
299
+ if decimal_char == decimal_mark
300
+ major, minor = num.split(decimal_char)
301
+
302
+ else
303
+ # decimal_mark is used as a decimal_mark when there are multiple instances, always
304
+ if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
305
+ major, minor = num.gsub(decimal_mark, ''), 0
306
+ else
307
+ # ex: 1,000 - 1.0000 - 10001.000
308
+ # split number into possible major (dollars) and minor (cents) values
309
+ possible_major, possible_minor = num.split(decimal_mark)
310
+ possible_major ||= "0"
311
+ possible_minor ||= "00"
312
+
313
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
314
+ # e.g.
315
+ # 1,00 => 1.00
316
+ # 1.0000 => 1.00
317
+ # 1.2 => 1.20
318
+ if possible_minor.length != 3 # thousands_separator
319
+ major, minor = possible_major, possible_minor
320
+ else
321
+ # minor length is three
322
+ # let's try to figure out intent of the thousands_separator
323
+
324
+ # the major length is greater than three, which means
325
+ # the comma or period is used as a thousands_separator
326
+ # e.g.
327
+ # 1000,000
328
+ # 100000,000
329
+ if possible_major.length > 3
330
+ major, minor = possible_major, possible_minor
331
+ else
332
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
333
+ # handle as , is sep, . is thousands_separator
334
+ if decimal_mark == '.'
335
+ major, minor = possible_major, possible_minor
336
+ else
337
+ major, minor = "#{possible_major}#{possible_minor}", 0
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
343
+ else
344
+ # TODO: ParseError
345
+ raise ArgumentError, "Invalid currency amount"
346
+ end
347
+
348
+ # build the string based on major/minor since decimal_mark/thousands_separator have been removed
349
+ # avoiding floating point arithmetic here to ensure accuracy
350
+ cents = (major.to_i * currency.subunit_to_unit)
351
+ # Because of an bug in JRuby, we can't just call #floor
352
+ minor = minor.to_s
353
+ minor = if minor.size < currency.decimal_places
354
+ (minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
355
+ elsif minor.size > currency.decimal_places
356
+ if minor[currency.decimal_places,1].to_i >= 5
357
+ minor[0,currency.decimal_places].to_i+1
358
+ else
359
+ minor[0,currency.decimal_places].to_i
360
+ end
361
+ else
362
+ minor.to_i
363
+ end
364
+ cents += minor
365
+
366
+ # if negative, multiply by -1; otherwise, return positive cents
367
+ negative ? cents * -1 : cents
368
+ end
369
+ end
370
+ end
371
+ end
data/money.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = "money-joshm1"
4
+ s.version = "5.1.2"
5
+ s.platform = Gem::Platform::RUBY
6
+ s.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin",
7
+ "Shane Emmons", "Simone Carletti"]
8
+ s.email = ["semmons99+RubyMoney@gmail.com"]
9
+ s.homepage = "http://rubymoney.github.com/money"
10
+ s.summary = "Money and currency exchange support library."
11
+ s.description = "This library aids one in handling money and different currencies."
12
+
13
+ s.required_ruby_version = ">= 1.9.2"
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+
16
+ s.add_dependency "i18n", "~> 0.5.0"
17
+
18
+ s.add_development_dependency "rspec", "~> 2.11.0"
19
+ s.add_development_dependency "yard", "~> 0.8.1"
20
+ s.add_development_dependency "kramdown", "~> 0.14.0"
21
+
22
+ s.license = "MIT"
23
+
24
+ s.files = Dir.glob("{config,lib,spec}/**/*")
25
+ s.files += %w(CHANGELOG.md LICENSE README.md)
26
+ s.files += %w(Rakefile money.gemspec)
27
+
28
+ s.require_path = "lib"
29
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Money::Bank::Base do
4
+
5
+ describe ".instance" do
6
+ it "is local to one class" do
7
+ klass = Money::Bank::Base
8
+ subclass = Class.new(Money::Bank::Base)
9
+ klass.instance.should_not == subclass.instance
10
+ end
11
+ end
12
+
13
+ describe "#initialize" do
14
+ it "accepts a block and stores @rounding_method" do
15
+ proc = Proc.new { |n| n.ceil }
16
+ bank = Money::Bank::Base.new(&proc)
17
+ bank.rounding_method.should == proc
18
+ end
19
+ end
20
+
21
+ describe "#setup" do
22
+ it "calls #setup after #initialize" do
23
+ class MyBank < Money::Bank::Base
24
+ attr_reader :setup_called
25
+
26
+ def setup
27
+ @setup_called = true
28
+ end
29
+ end
30
+
31
+ bank = MyBank.new
32
+ bank.setup_called.should == true
33
+ end
34
+ end
35
+
36
+ describe "#exchange_with" do
37
+ it "is not implemented" do
38
+ expect { subject.exchange_with(Money.new(100, 'USD'), 'EUR') }.to raise_exception(NotImplementedError)
39
+ end
40
+ end
41
+
42
+ describe "#same_currency?" do
43
+ it "accepts str/str" do
44
+ expect { subject.send(:same_currency?, 'USD', 'EUR') }.to_not raise_exception
45
+ end
46
+
47
+ it "accepts currency/str" do
48
+ expect { subject.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR') }.to_not raise_exception
49
+ end
50
+
51
+ it "accepts str/currency" do
52
+ expect { subject.send(:same_currency?, 'USD', Money::Currency.wrap('EUR')) }.to_not raise_exception
53
+ end
54
+
55
+ it "accepts currency/currency" do
56
+ expect { subject.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR')) }.to_not raise_exception
57
+ end
58
+
59
+ it "returns true when currencies match" do
60
+ subject.send(:same_currency?, 'USD', 'USD').should be_true
61
+ subject.send(:same_currency?, Money::Currency.wrap('USD'), 'USD').should be_true
62
+ subject.send(:same_currency?, 'USD', Money::Currency.wrap('USD')).should be_true
63
+ subject.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('USD')).should be_true
64
+ end
65
+
66
+ it "returns false when currencies do not match" do
67
+ subject.send(:same_currency?, 'USD', 'EUR').should be_false
68
+ subject.send(:same_currency?, Money::Currency.wrap('USD'), 'EUR').should be_false
69
+ subject.send(:same_currency?, 'USD', Money::Currency.wrap('EUR')).should be_false
70
+ subject.send(:same_currency?, Money::Currency.wrap('USD'), Money::Currency.wrap('EUR')).should be_false
71
+ end
72
+
73
+ it "raises an UnknownCurrency exception when an unknown currency is passed" do
74
+ expect { subject.send(:same_currency?, 'AAA', 'BBB') }.to raise_exception(Money::Currency::UnknownCurrency)
75
+ end
76
+ end
77
+ end