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)


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

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|\.|,|\'|\-]/, '').strip

  # set a boolean flag for if the number is negative or not
  negative = num.split(//).first == "-"

  # if negative, remove the minus sign from the number
  # if it's not negative, the hyphen makes the value invalid
  if negative
    num = num.gsub(/^-/, '')
  else
    raise ArgumentError, "Invalid currency amount (hyphen)" if num.include?('-')
  end

  #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_decimal_marks = 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_decimal_marks.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
      decimal_mark, thousands_separator = used_decimal_marks.uniq
      # remove all decimal_marks, split on the thousands_separator
      major, minor = num.gsub(decimal_mark, '').split(thousands_separator)
      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_decimal_marks.first

      # 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
    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 them to the corresponding cents value, according to currency subunit property, before instantiating the Money object.

Examples:

Money.from_bigdecimal(BigDecimal.new("100")
#=> #<Money @cents=10000 @currency="USD">
Money.from_bigdecimal(BigDecimal.new("100", "USD")
#=> #<Money @cents=10000 @currency="USD">
Money.from_bigdecimal(BigDecimal.new("100", "EUR")
#=> #<Money @cents=10000 @currency="EUR">
Money.from_bigdecimal(BigDecimal.new("100", "BHD")
#=> #<Money @cents=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:

  • BigDecimal#to_money
  • Money.from_numeric


172
173
174
175
176
# File 'lib/money/money/parsing.rb', line 172

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

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

 Converts a Fixnum into a Money object treating the value as dollars and converting them to the corresponding cents value, according to currency subunit property, before instantiating the Money object.

Examples:

Money.from_fixnum(100)
#=> #<Money @cents=10000 @currency="USD">
Money.from_fixnum(100, "USD")
#=> #<Money @cents=10000 @currency="USD">
Money.from_fixnum(100, "EUR")
#=> #<Money @cents=10000 @currency="EUR">
Money.from_fixnum(100, "BHD")
#=> #<Money @cents=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:

  • Fixnum#to_money
  • Money.from_numeric


113
114
115
116
117
# File 'lib/money/money/parsing.rb', line 113

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 converting them to the corresponding cents value, 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 @cents=10000 @currency="USD">
Money.from_float(100.0, "USD")
#=> #<Money @cents=10000 @currency="USD">
Money.from_float(100.0, "EUR")
#=> #<Money @cents=10000 @currency="EUR">
Money.from_float(100.0, "BHD")
#=> #<Money @cents=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:

  • Float#to_money
  • Money.from_numeric


145
146
147
# File 'lib/money/money/parsing.rb', line 145

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 them to the corresponding cents value, 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 Money.from_fixnum instead of using the default Money.from_bigdecimal which adds the overload to converts the value into a slower BigDecimal instance.

Examples:

Money.from_numeric(100)
#=> #<Money @cents=10000 @currency="USD">
Money.from_numeric(100.00)
#=> #<Money @cents=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:



211
212
213
214
215
216
217
218
219
220
# File 'lib/money/money/parsing.rb', line 211

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 dollars and converting them to the corresponding cents value, according to currency subunit property, before instantiating the Money object.

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

Examples:

Money.from_string("100")
#=> #<Money @cents=10000 @currency="USD">
Money.from_string("100", "USD")
#=> #<Money @cents=10000 @currency="USD">
Money.from_string("100", "EUR")
#=> #<Money @cents=10000 @currency="EUR">
Money.from_string("100", "BHD")
#=> #<Money @cents=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:



86
87
88
# File 'lib/money/money/parsing.rb', line 86

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 @cents=10000>
'100.37'.to_money             #=> #<Money @cents=10037>
'100 USD'.to_money            #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
'USD 100'.to_money            #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
'$100 USD'.to_money           #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
'hello 2000 world'.to_money   #=> #<Money @cents=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:

  • Money.from_string


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/money/money/parsing.rb', line 34

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

  # Get the currency.
  m = i.scan /([A-Z]{2,3})/
    c = m[0] ? m[0][0] : nil

  # 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)

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