Module: Money::Parsing::ClassMethods

Defined in:
lib/money/money/parsing.rb

Instance Method Summary collapse

Instance Method Details

#extract_cents(input, currency = Money.default_currency) ⇒ Integer

Takes a number string and attempts to massage out the number.

Parameters:

  • input (String)

    The string containing a potential number.

Returns:

  • (Integer)

Raises:

  • (ArgumentError)


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
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
293
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
363
364
365
366
367
368
# File 'lib/money/money/parsing.rb', line 240

def extract_cents(input, currency = Money.default_currency)
  # remove anything that's not a number, potential thousands_separator, or minus sign
  num = input.gsub(/[^\d.,'-]/, '')

  # set a boolean flag for if the number is negative or not
  negative = num =~ /^-|-$/ ? true : false

  # decimal mark character
  decimal_char = currency.decimal_mark

  # if negative, remove the minus sign from the number
  # if it's not negative, the hyphen makes the value invalid
  if negative
    num = num.sub(/^-|-$/, '')
  end

  raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')

  #if the number ends with punctuation, just throw it out.  If it means decimal,
  #it won't hurt anything.  If it means a literal period or comma, this will
  #save it from being mis-interpreted as a decimal.
  num.chop! if num.match(/[\.|,]$/)

  # gather all decimal_marks within the result number
  used_delimiters = num.scan(/[^\d]/)

  # determine the number of unique decimal_marks within the number
  #
  # e.g.
  # $1,234,567.89 would return 2 (, and .)
  # $125,00 would return 1
  # $199 would return 0
  # $1 234,567.89 would raise an error (decimal_marks are space, comma, and period)
  case used_delimiters.uniq.length
    # no decimal_mark or thousands_separator; major (dollars) is the number, and minor (cents) is 0
  when 0 then major, minor = num, 0

    # two decimal_marks, so we know the last item in this array is the
    # major/minor thousands_separator and the rest are decimal_marks
  when 2
    thousands_separator, decimal_mark = used_delimiters.uniq

    # remove all thousands_separator, split on the decimal_mark
    major, minor = num.gsub(thousands_separator, '').split(decimal_mark)
    min = 0 unless min
  when 1
    # we can't determine if the comma or period is supposed to be a decimal_mark or a thousands_separator
    # e.g.
    # 1,00 - comma is a thousands_separator
    # 1.000 - period is a thousands_separator
    # 1,000 - comma is a decimal_mark
    # 1,000,000 - comma is a decimal_mark
    # 10000,00 - comma is a thousands_separator
    # 1000,000 - comma is a thousands_separator

    # assign first decimal_mark for reusability
    decimal_mark = used_delimiters.first

    # When we have identified the decimal mark character
    if decimal_char == decimal_mark
      major, minor = num.split(decimal_char)

    else
      # decimal_mark is used as a decimal_mark when there are multiple instances, always
      if num.scan(decimal_mark).length > 1 # multiple matches; treat as decimal_mark
        major, minor = num.gsub(decimal_mark, ''), 0
      else
        # ex: 1,000 - 1.0000 - 10001.000
        # split number into possible major (dollars) and minor (cents) values
        possible_major, possible_minor = num.split(decimal_mark)
        possible_major ||= "0"
        possible_minor ||= "00"

        # if the minor (cents) length isn't 3, assign major/minor from the possibles
        # e.g.
        #   1,00 => 1.00
        #   1.0000 => 1.00
        #   1.2 => 1.20
        if possible_minor.length != 3 # thousands_separator
          major, minor = possible_major, possible_minor
        else
          # minor length is three
          # let's try to figure out intent of the thousands_separator

          # the major length is greater than three, which means
          # the comma or period is used as a thousands_separator
          # e.g.
          #   1000,000
          #   100000,000
          if possible_major.length > 3
            major, minor = possible_major, possible_minor
          else
            # number is in format ###{sep}### or ##{sep}### or #{sep}###
            # handle as , is sep, . is thousands_separator
            if decimal_mark == '.'
              major, minor = possible_major, possible_minor
            else
              major, minor = "#{possible_major}#{possible_minor}", 0
            end
          end
        end
      end
    end
  else
    # TODO: ParseError
    raise ArgumentError, "Invalid currency amount"
  end

  # build the string based on major/minor since decimal_mark/thousands_separator have been removed
  # avoiding floating point arithmetic here to ensure accuracy
  cents = (major.to_i * currency.subunit_to_unit)
  # Because of an bug in JRuby, we can't just call #floor
  minor = minor.to_s
  minor = if minor.size < currency.decimal_places
            (minor + ("0" * currency.decimal_places))[0,currency.decimal_places].to_i
          elsif minor.size > currency.decimal_places
            if minor[currency.decimal_places,1].to_i >= 5
              minor[0,currency.decimal_places].to_i+1
            else
              minor[0,currency.decimal_places].to_i
            end
          else
            minor.to_i
          end
  cents += minor

  # if negative, multiply by -1; otherwise, return positive cents
  negative ? cents * -1 : cents
end

#from_bigdecimal(value, currency = Money.default_currency) ⇒ Money

 Converts a BigDecimal into a Money object treating the value as dollars and converting to corresponding fractional unit, according to currency subunit property, before instantiating the Money object.

Examples:

Money.from_bigdecimal(BigDecimal.new("100")
#=> #<Money @fractional=10000 @currency="USD">
Money.from_bigdecimal(BigDecimal.new("100", "USD")
#=> #<Money @fractional=10000 @currency="USD">
Money.from_bigdecimal(BigDecimal.new("100", "EUR")
#=> #<Money @fractional=10000 @currency="EUR">
Money.from_bigdecimal(BigDecimal.new("100", "BHD")
#=> #<Money @fractional=100 @currency="BHD">

Parameters:

  • value (BigDecimal)

    The money amount, in dollars.

  • currency (Currency, String, Symbol) (defaults to: Money.default_currency)

    The currency format.

Returns:

See Also:



184
185
186
187
188
# File 'lib/money/money/parsing.rb', line 184

def from_bigdecimal(value, currency = Money.default_currency)
  currency = Money::Currency.wrap(currency)
  amount   = value * currency.subunit_to_unit
  new(amount.round, currency)
end

#from_fixnum(value, currency = Money.default_currency) ⇒ Money

 Converts a Fixnum into a Money object treating the value as amount and to corresponding fractional unit, according to currency subunit property, before instantiating the Money object.

Examples:

Money.from_fixnum(100)
#=> #<Money @fractional=10000 @currency="USD">
Money.from_fixnum(100, "USD")
#=> #<Money @fractional=10000 @currency="USD">
Money.from_fixnum(100, "EUR")
#=> #<Money @fractional=10000 @currency="EUR">
Money.from_fixnum(100, "BHD")
#=> #<Money @fractional=100 @currency="BHD">

Parameters:

  • value (Fixnum)

    The money amount, in dollars.

  • currency (Currency, String, Symbol) (defaults to: Money.default_currency)

    The currency format.

Returns:

See Also:



125
126
127
128
129
# File 'lib/money/money/parsing.rb', line 125

def from_fixnum(value, currency = Money.default_currency)
  currency = Money::Currency.wrap(currency)
  amount   = value * currency.subunit_to_unit
  new(amount, currency)
end

#from_float(value, currency = Money.default_currency) ⇒ Money

 Converts a Float into a Money object treating the value as dollars and to corresponding fractional unit, according to currency subunit property, before instantiating the Money object.

Behind the scenes, this method relies on Money.from_bigdecimal to avoid problems with floating point precision.

Examples:

Money.from_float(100.0)
#=> #<Money @fractional=10000 @currency="USD">
Money.from_float(100.0, "USD")
#=> #<Money @fractional=10000 @currency="USD">
Money.from_float(100.0, "EUR")
#=> #<Money @fractional=10000 @currency="EUR">
Money.from_float(100.0, "BHD")
#=> #<Money @fractional=100 @currency="BHD">

Parameters:

  • value (Float)

    The money amount, in dollars.

  • currency (Currency, String, Symbol) (defaults to: Money.default_currency)

    The currency format.

Returns:

See Also:



157
158
159
# File 'lib/money/money/parsing.rb', line 157

def from_float(value, currency = Money.default_currency)
  from_bigdecimal(BigDecimal.new(value.to_s), currency)
end

#from_numeric(value, currency = Money.default_currency) ⇒ Money

 Converts a Numeric value into a Money object treating the value as dollars and converting to corresponding fractional unit, according to currency subunit property, before instantiating the Money object.

This method relies on various Money.from_* methods and tries to forwards the call to the most appropriate method in order to reduce computation effort. For instance, if value is an Integer, this method calls #from_fixnum instead of using the default #from_bigdecimal which adds the overload to converts the value into a slower BigDecimal instance.

Examples:

Money.from_numeric(100)
#=> #<Money @fractional=10000 @currency="USD">
Money.from_numeric(100.00)
#=> #<Money @fractional=10000 @currency="USD">
Money.from_numeric("100")
#=> ArgumentError

Parameters:

  • value (Numeric)

    The money amount, in dollars.

  • currency (Currency, String, Symbol) (defaults to: Money.default_currency)

    The currency format.

Returns:

Raises:

  • ArgumentError Unless value is a supported type.

See Also:



223
224
225
226
227
228
229
230
231
232
# File 'lib/money/money/parsing.rb', line 223

def from_numeric(value, currency = Money.default_currency)
  case value
  when Fixnum
    from_fixnum(value, currency)
  when Numeric
    from_bigdecimal(BigDecimal.new(value.to_s), currency)
  else
    raise ArgumentError, "`value' should be a Numeric object"
  end
end

#from_string(value, currency = Money.default_currency) ⇒ Money

 Converts a String into a Money object treating the value as amount and converting to fractional unit, according to currency subunit property, before instantiating the Money object.

Behind the scenes, this method relies on #from_bigdecimal to avoid problems with string-to-numeric conversion.

Examples:

Money.from_string("100")
#=> #<Money @fractional=10000 @currency="USD">
Money.from_string("100", "USD")
#=> #<Money @fractional=10000 @currency="USD">
Money.from_string("100", "EUR")
#=> #<Money @fractional=10000 @currency="EUR">
Money.from_string("100", "BHD")
#=> #<Money @fractional=100 @currency="BHD">

Parameters:

  • value (String, #to_s)

    The money amount, in dollars.

  • currency (Currency, String, Symbol) (defaults to: Money.default_currency)

    The currency to set the resulting Money object to.

Returns:

See Also:



98
99
100
# File 'lib/money/money/parsing.rb', line 98

def from_string(value, currency = Money.default_currency)
  from_bigdecimal(BigDecimal.new(value.to_s), currency)
end

#parse(input, currency = nil) ⇒ Money

Parses the current string and converts it to a Money object. Excess characters will be discarded.

Examples:

'100'.to_money                #=> #<Money @fractional=10000>
'100.37'.to_money             #=> #<Money @fractional=10037>
'100 USD'.to_money            #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
'USD 100'.to_money            #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
'$100 USD'.to_money           #=> #<Money @fractional=10000, @currency=#<Money::Currency id: usd>>
'hello 2000 world'.to_money   #=> #<Money @fractional=200000 @currency=#<Money::Currency id: usd>>

Mismatching currencies

'USD 2000'.to_money("EUR")    #=> ArgumentError

Parameters:

  • input (String, #to_s)

    The input to parse.

  • currency (Currency, String, Symbol) (defaults to: nil)

    The currency format. The currency to set the resulting Money object to.

Returns:

Raises:

  • (ArgumentError)

    If any currency is supplied and given value doesn’t match the one extracted from the input string.

See Also:



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
# File 'lib/money/money/parsing.rb', line 36

def parse(input, currency = nil)
  i = input.to_s.strip

  # raise Money::Currency.table.collect{|c| c[1][:symbol]}.inspect

  # Check the first character for a currency symbol, alternatively get it
  # from the stated currency string
  c = if Money.assume_from_symbol && i =~ /^(\$|€|£)/
    case i
    when /^\$/ then "USD"
    when /^€/ then "EUR"
    when // then "GBP"
    end
  else
    i[/[A-Z]{2,3}/]
  end

  # check that currency passed and embedded currency are the same,
  # and negotiate the final currency
  if currency.nil? and c.nil?
    currency = Money.default_currency
  elsif currency.nil?
    currency = c
  elsif c.nil?
    currency = currency
  elsif currency != c
    # TODO: ParseError
    raise ArgumentError, "Mismatching Currencies"
  end
  currency = Money::Currency.wrap(currency)

  fractional = extract_cents(i, currency)
  new(fractional, currency)
end