money-joshm1 5.1.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.
@@ -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