money 6.7.0 → 6.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -1
  3. data/.travis.yml +22 -5
  4. data/AUTHORS +5 -0
  5. data/CHANGELOG.md +109 -3
  6. data/Gemfile +13 -4
  7. data/LICENSE +2 -0
  8. data/README.md +69 -49
  9. data/config/currency_backwards_compatible.json +30 -0
  10. data/config/currency_iso.json +139 -62
  11. data/config/currency_non_iso.json +66 -2
  12. data/lib/money.rb +0 -13
  13. data/lib/money/bank/variable_exchange.rb +9 -22
  14. data/lib/money/currency.rb +35 -38
  15. data/lib/money/currency/heuristics.rb +1 -144
  16. data/lib/money/currency/loader.rb +1 -1
  17. data/lib/money/locale_backend/base.rb +7 -0
  18. data/lib/money/locale_backend/errors.rb +6 -0
  19. data/lib/money/locale_backend/i18n.rb +24 -0
  20. data/lib/money/locale_backend/legacy.rb +28 -0
  21. data/lib/money/money.rb +120 -151
  22. data/lib/money/money/allocation.rb +37 -0
  23. data/lib/money/money/arithmetic.rb +57 -52
  24. data/lib/money/money/constructors.rb +1 -2
  25. data/lib/money/money/formatter.rb +397 -0
  26. data/lib/money/money/formatting_rules.rb +120 -0
  27. data/lib/money/money/locale_backend.rb +20 -0
  28. data/lib/money/rates_store/memory.rb +1 -2
  29. data/lib/money/version.rb +1 -1
  30. data/money.gemspec +10 -16
  31. data/spec/bank/variable_exchange_spec.rb +7 -3
  32. data/spec/currency/heuristics_spec.rb +2 -153
  33. data/spec/currency_spec.rb +45 -4
  34. data/spec/locale_backend/i18n_spec.rb +62 -0
  35. data/spec/locale_backend/legacy_spec.rb +74 -0
  36. data/spec/money/allocation_spec.rb +130 -0
  37. data/spec/money/arithmetic_spec.rb +217 -104
  38. data/spec/money/constructors_spec.rb +0 -12
  39. data/spec/money/formatting_spec.rb +320 -179
  40. data/spec/money/locale_backend_spec.rb +14 -0
  41. data/spec/money_spec.rb +159 -26
  42. data/spec/rates_store/memory_spec.rb +13 -2
  43. data/spec/spec_helper.rb +2 -0
  44. data/spec/support/shared_examples/money_examples.rb +14 -0
  45. metadata +32 -41
  46. data/lib/money/money/formatting.rb +0 -417
@@ -1,417 +0,0 @@
1
- # encoding: UTF-8
2
- class Money
3
- module Formatting
4
- # Creates a formatted price string according to several rules.
5
- #
6
- # @param [Hash] rules The options used to format the string.
7
- #
8
- # @return [String]
9
- #
10
- # @option *rules [Boolean, String] :display_free (false) Whether a zero
11
- # amount of money should be formatted of "free" or as the supplied string.
12
- #
13
- # @example
14
- # Money.us_dollar(0).format(:display_free => true) #=> "free"
15
- # Money.us_dollar(0).format(:display_free => "gratis") #=> "gratis"
16
- # Money.us_dollar(0).format #=> "$0.00"
17
- #
18
- # @option *rules [Boolean] :with_currency (false) Whether the currency name
19
- # should be appended to the result string.
20
- #
21
- # @example
22
- # Money.ca_dollar(100).format #=> "$1.00"
23
- # Money.ca_dollar(100).format(:with_currency => true) #=> "$1.00 CAD"
24
- # Money.us_dollar(85).format(:with_currency => true) #=> "$0.85 USD"
25
- #
26
- # @option *rules [Boolean] :rounded_infinite_precision (false) Whether the
27
- # amount of money should be rounded when using infinite_precision
28
- #
29
- # @example
30
- # Money.us_dollar(100.1).format #=> "$1.001"
31
- # Money.us_dollar(100.1).format(:rounded_infinite_precision => true) #=> "$1"
32
- # Money.us_dollar(100.9).format(:rounded_infinite_precision => true) #=> "$1.01"
33
- #
34
- # @option *rules [Boolean] :no_cents (false) Whether cents should be omitted.
35
- #
36
- # @example
37
- # Money.ca_dollar(100).format(:no_cents => true) #=> "$1"
38
- # Money.ca_dollar(599).format(:no_cents => true) #=> "$5"
39
- #
40
- # @option *rules [Boolean] :no_cents_if_whole (false) Whether cents should be
41
- # omitted if the cent value is zero
42
- #
43
- # @example
44
- # Money.ca_dollar(10000).format(:no_cents_if_whole => true) #=> "$100"
45
- # Money.ca_dollar(10034).format(:no_cents_if_whole => true) #=> "$100.34"
46
- #
47
- # @option *rules [Boolean, String, nil] :symbol (true) Whether a money symbol
48
- # should be prepended to the result string. The default is true. This method
49
- # attempts to pick a symbol that's suitable for the given currency.
50
- #
51
- # @example
52
- # Money.new(100, "USD") #=> "$1.00"
53
- # Money.new(100, "GBP") #=> "£1.00"
54
- # Money.new(100, "EUR") #=> "€1.00"
55
- #
56
- # # Same thing.
57
- # Money.new(100, "USD").format(:symbol => true) #=> "$1.00"
58
- # Money.new(100, "GBP").format(:symbol => true) #=> "£1.00"
59
- # Money.new(100, "EUR").format(:symbol => true) #=> "€1.00"
60
- #
61
- # # You can specify a false expression or an empty string to disable
62
- # # prepending a money symbol.§
63
- # Money.new(100, "USD").format(:symbol => false) #=> "1.00"
64
- # Money.new(100, "GBP").format(:symbol => nil) #=> "1.00"
65
- # Money.new(100, "EUR").format(:symbol => "") #=> "1.00"
66
- #
67
- # # If the symbol for the given currency isn't known, then it will default
68
- # # to "¤" as symbol.
69
- # Money.new(100, "AWG").format(:symbol => true) #=> "¤1.00"
70
- #
71
- # # You can specify a string as value to enforce using a particular symbol.
72
- # Money.new(100, "AWG").format(:symbol => "ƒ") #=> "ƒ1.00"
73
- #
74
- # # You can specify a indian currency format
75
- # Money.new(10000000, "INR").format(:south_asian_number_formatting => true) #=> "1,00,000.00"
76
- # Money.new(10000000).format(:south_asian_number_formatting => true) #=> "$1,00,000.00"
77
- #
78
- # @option *rules [Boolean, nil] :symbol_before_without_space (true) Whether
79
- # a space between the money symbol and the amount should be inserted when
80
- # +:symbol_position+ is +:before+. The default is true (meaning no space). Ignored
81
- # if +:symbol+ is false or +:symbol_position+ is not +:before+.
82
- #
83
- # @example
84
- # # Default is to not insert a space.
85
- # Money.new(100, "USD").format #=> "$1.00"
86
- #
87
- # # Same thing.
88
- # Money.new(100, "USD").format(:symbol_before_without_space => true) #=> "$1.00"
89
- #
90
- # # If set to false, will insert a space.
91
- # Money.new(100, "USD").format(:symbol_before_without_space => false) #=> "$ 1.00"
92
- #
93
- # @option *rules [Boolean, nil] :symbol_after_without_space (false) Whether
94
- # a space between the the amount and the money symbol should be inserted when
95
- # +:symbol_position+ is +:after+. The default is false (meaning space). Ignored
96
- # if +:symbol+ is false or +:symbol_position+ is not +:after+.
97
- #
98
- # @example
99
- # # Default is to insert a space.
100
- # Money.new(100, "USD").format(:symbol_position => :after) #=> "1.00 $"
101
- #
102
- # # If set to true, will not insert a space.
103
- # Money.new(100, "USD").format(:symbol_position => :after, :symbol_after_without_space => true) #=> "1.00$"
104
- #
105
- # @option *rules [Boolean, String, nil] :decimal_mark (true) Whether the
106
- # currency should be separated by the specified character or '.'
107
- #
108
- # @example
109
- # # If a string is specified, it's value is used.
110
- # Money.new(100, "USD").format(:decimal_mark => ",") #=> "$1,00"
111
- #
112
- # # If the decimal_mark for a given currency isn't known, then it will default
113
- # # to "." as decimal_mark.
114
- # Money.new(100, "FOO").format #=> "$1.00"
115
- #
116
- # @option *rules [Boolean, String, nil] :thousands_separator (true) Whether
117
- # the currency should be delimited by the specified character or ','
118
- #
119
- # @example
120
- # # If false is specified, no thousands_separator is used.
121
- # Money.new(100000, "USD").format(:thousands_separator => false) #=> "1000.00"
122
- # Money.new(100000, "USD").format(:thousands_separator => nil) #=> "1000.00"
123
- # Money.new(100000, "USD").format(:thousands_separator => "") #=> "1000.00"
124
- #
125
- # # If a string is specified, it's value is used.
126
- # Money.new(100000, "USD").format(:thousands_separator => ".") #=> "$1.000.00"
127
- #
128
- # # If the thousands_separator for a given currency isn't known, then it will
129
- # # default to "," as thousands_separator.
130
- # Money.new(100000, "FOO").format #=> "$1,000.00"
131
- #
132
- # @option *rules [Boolean] :html (false) Whether the currency should be
133
- # HTML-formatted. Only useful in combination with +:with_currency+.
134
- #
135
- # @example
136
- # s = Money.ca_dollar(570).format(:html => true, :with_currency => true)
137
- # s #=> "$5.70 <span class=\"currency\">CAD</span>"
138
- #
139
- # @option *rules [Boolean] :sign_before_symbol (false) Whether the sign should be
140
- # before the currency symbol.
141
- #
142
- # @example
143
- # # You can specify to display the sign before the symbol for negative numbers
144
- # Money.new(-100, "GBP").format(:sign_before_symbol => true) #=> "-£1.00"
145
- # Money.new(-100, "GBP").format(:sign_before_symbol => false) #=> "£-1.00"
146
- # Money.new(-100, "GBP").format #=> "£-1.00"
147
- #
148
- # @option *rules [Boolean] :sign_positive (false) Whether positive numbers should be
149
- # signed, too.
150
- #
151
- # @example
152
- # # You can specify to display the sign with positive numbers
153
- # Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => true) #=> "+£1.00"
154
- # Money.new(100, "GBP").format(:sign_positive => true, :sign_before_symbol => false) #=> "£+1.00"
155
- # Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => true) #=> "£1.00"
156
- # Money.new(100, "GBP").format(:sign_positive => false, :sign_before_symbol => false) #=> "£1.00"
157
- # Money.new(100, "GBP").format #=> "£+1.00"
158
- #
159
- # @option *rules [Boolean] :disambiguate (false) Prevents the result from being ambiguous
160
- # due to equal symbols for different currencies. Uses the `disambiguate_symbol`.
161
- #
162
- # @example
163
- # Money.new(10000, "USD").format(:disambiguate => false) #=> "$100.00"
164
- # Money.new(10000, "CAD").format(:disambiguate => false) #=> "$100.00"
165
- # Money.new(10000, "USD").format(:disambiguate => true) #=> "$100.00"
166
- # Money.new(10000, "CAD").format(:disambiguate => true) #=> "C$100.00"
167
- #
168
- # @option *rules [Boolean] :html_wrap_symbol (false) Wraps the currency symbol
169
- # in a html <span> tag.
170
- #
171
- # @example
172
- # Money.new(10000, "USD").format(:disambiguate => false)
173
- # #=> "<span class=\"currency_symbol\">$100.00</span>
174
- #
175
- # @option *rules [Symbol] :symbol_position (:before) `:before` if the currency
176
- # symbol goes before the amount, `:after` if it goes after.
177
- #
178
- # @example
179
- # Money.new(10000, "USD").format(:symbol_position => :before) #=> "$100.00"
180
- # Money.new(10000, "USD").format(:symbol_position => :after) #=> "100.00 $"
181
- #
182
- # @option *rules [Boolean] :translate (true) `true` Checks for custom
183
- # symbol definitions using I18n.
184
- #
185
- # @example
186
- # # With the following entry in the translation files:
187
- # # en:
188
- # # number:
189
- # # currency:
190
- # # symbol:
191
- # # CAD: "CAD$"
192
- # Money.new(10000, "CAD").format(:translate => true) #=> "CAD$100.00"
193
- #
194
- # @example
195
- # Money.new(89000, :btc).format(:drop_trailing_zeros => true) #=> B⃦0.00089
196
- # Money.new(110, :usd).format(:drop_trailing_zeros => true) #=> $1.1
197
- #
198
- # Note that the default rules can be defined through +Money.default_formatting_rules+ hash.
199
- #
200
- # @see +Money.default_formatting_rules+ for more information.
201
- def format(*rules)
202
- # support for old format parameters
203
- rules = normalize_formatting_rules(rules)
204
-
205
- rules = default_formatting_rules.merge(rules)
206
- rules = localize_formatting_rules(rules)
207
- rules = translate_formatting_rules(rules) if rules[:translate]
208
-
209
- if fractional == 0
210
- if rules[:display_free].respond_to?(:to_str)
211
- return rules[:display_free]
212
- elsif rules[:display_free]
213
- return "free"
214
- end
215
- end
216
-
217
- symbol_value = symbol_value_from(rules)
218
-
219
- formatted = self.abs.to_s
220
-
221
- if rules[:rounded_infinite_precision]
222
- formatted.gsub!(/#{decimal_mark}/, '.') unless '.' == decimal_mark
223
- formatted = ((BigDecimal(formatted) * currency.subunit_to_unit).round / BigDecimal(currency.subunit_to_unit.to_s)).to_s("F")
224
- formatted.gsub!(/\..*/) do |decimal_part|
225
- decimal_part << '0' while decimal_part.length < (currency.decimal_places + 1)
226
- decimal_part
227
- end
228
- formatted.gsub!(/\./, decimal_mark) unless '.' == decimal_mark
229
- end
230
-
231
- sign = self.negative? ? '-' : ''
232
-
233
- if rules[:no_cents] || (rules[:no_cents_if_whole] && cents % currency.subunit_to_unit == 0)
234
- formatted = "#{formatted.to_i}"
235
- end
236
-
237
- # Inspiration: https://github.com/rails/rails/blob/16214d1108c31174c94503caced3855b0f6bad95/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb#L72-L79
238
- if rules[:drop_trailing_zeros]
239
- escaped_decimal_mark = Regexp.escape(decimal_mark)
240
- formatted = formatted.sub(/(#{escaped_decimal_mark})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_decimal_mark}\z/, '')
241
- end
242
-
243
- thousands_separator_value = thousands_separator
244
- # Determine thousands_separator
245
- if rules.has_key?(:thousands_separator)
246
- thousands_separator_value = rules[:thousands_separator] || ''
247
- end
248
-
249
- # Apply thousands_separator
250
- formatted.gsub!(regexp_format(formatted, rules, decimal_mark, symbol_value),
251
- "\\1#{thousands_separator_value}")
252
-
253
- symbol_position = symbol_position_from(rules)
254
-
255
- if rules[:sign_positive] == true && self.positive?
256
- sign = '+'
257
- end
258
-
259
- if rules[:sign_before_symbol] == true
260
- sign_before = sign
261
- sign = ''
262
- end
263
-
264
- if symbol_value && !symbol_value.empty?
265
- symbol_value = "<span class=\"currency_symbol\">#{symbol_value}</span>" if rules[:html_wrap_symbol]
266
-
267
- formatted = if symbol_position == :before
268
- symbol_space = rules[:symbol_before_without_space] === false ? " " : ""
269
- "#{sign_before}#{symbol_value}#{symbol_space}#{sign}#{formatted}"
270
- else
271
- symbol_space = rules[:symbol_after_without_space] ? "" : " "
272
- "#{sign_before}#{sign}#{formatted}#{symbol_space}#{symbol_value}"
273
- end
274
- else
275
- formatted="#{sign_before}#{sign}#{formatted}"
276
- end
277
-
278
- apply_decimal_mark_from_rules(formatted, rules)
279
-
280
- if rules[:with_currency]
281
- formatted << " "
282
- formatted << '<span class="currency">' if rules[:html]
283
- formatted << currency.to_s
284
- formatted << '</span>' if rules[:html]
285
- end
286
- formatted
287
- end
288
-
289
- def thousands_separator
290
- i18n_format_for(:thousands_separator, :delimiter, ",")
291
- end
292
-
293
- def decimal_mark
294
- i18n_format_for(:decimal_mark, :separator, ".")
295
- end
296
-
297
- alias_method :delimiter, :thousands_separator
298
- alias_method :separator, :decimal_mark
299
-
300
- private
301
-
302
- def i18n_format_for(method, name, character)
303
- if self.class.use_i18n
304
- begin
305
- I18n.t name, :scope => "number.currency.format", :raise => true
306
- rescue I18n::MissingTranslationData
307
- I18n.t name, :scope =>"number.format", :default => (currency.send(method) || character)
308
- end
309
- else
310
- currency.send(method) || character
311
- end
312
- end
313
-
314
- # Cleans up formatting rules.
315
- #
316
- # @param [Hash] rules
317
- #
318
- # @return [Hash]
319
- def normalize_formatting_rules(rules)
320
- if rules.size == 0
321
- rules = {}
322
- elsif rules.size == 1
323
- rules = rules.pop
324
- rules = { rules => true } if rules.is_a?(Symbol)
325
- end
326
- if !rules.include?(:decimal_mark) && rules.include?(:separator)
327
- rules[:decimal_mark] = rules[:separator]
328
- end
329
- if !rules.include?(:thousands_separator) && rules.include?(:delimiter)
330
- rules[:thousands_separator] = rules[:delimiter]
331
- end
332
- rules
333
- end
334
-
335
- # Applies decimal mark from rules to formatted
336
- #
337
- # @param [String] formatted
338
- # @param [Hash] rules
339
- def apply_decimal_mark_from_rules(formatted, rules)
340
- if rules.has_key?(:decimal_mark) && rules[:decimal_mark] &&
341
- rules[:decimal_mark] != decimal_mark
342
-
343
- regexp_decimal = Regexp.escape(decimal_mark)
344
- formatted.sub!(/(.*)(#{regexp_decimal})(.*)\Z/,
345
- "\\1#{rules[:decimal_mark]}\\3")
346
- end
347
- end
348
- end
349
-
350
- def default_formatting_rules
351
- self.class.default_formatting_rules || {}
352
- end
353
-
354
- def regexp_format(formatted, rules, decimal_mark, symbol_value)
355
- regexp_decimal = Regexp.escape(decimal_mark)
356
- if rules[:south_asian_number_formatting]
357
- /(\d+?)(?=(\d\d)+(\d)(?:\.))/
358
- else
359
- # Symbols may contain decimal marks (E.g "դր.")
360
- if formatted.sub(symbol_value.to_s, "") =~ /#{regexp_decimal}/
361
- /(\d)(?=(?:\d{3})+(?:#{regexp_decimal}))/
362
- else
363
- /(\d)(?=(?:\d{3})+(?:[^\d]{1}|$))/
364
- end
365
- end
366
- end
367
-
368
- def translate_formatting_rules(rules)
369
- begin
370
- rules[:symbol] = I18n.t currency.iso_code, :scope => "number.currency.symbol", :raise => true
371
- rescue I18n::MissingTranslationData
372
- # Do nothing
373
- end
374
- rules
375
- end
376
-
377
- def localize_formatting_rules(rules)
378
- if currency.iso_code == "JPY" && I18n.locale == :ja
379
- rules[:symbol] = "円" unless rules[:symbol] == false
380
- rules[:symbol_position] = :after
381
- rules[:symbol_after_without_space] = true
382
- end
383
- rules
384
- end
385
-
386
- def symbol_value_from(rules)
387
- if rules.has_key?(:symbol)
388
- if rules[:symbol] === true
389
- symbol
390
- elsif rules[:symbol]
391
- rules[:symbol]
392
- else
393
- ""
394
- end
395
- elsif rules[:html]
396
- currency.html_entity == '' ? currency.symbol : currency.html_entity
397
- elsif rules[:disambiguate] and currency.disambiguate_symbol
398
- currency.disambiguate_symbol
399
- else
400
- symbol
401
- end
402
- end
403
-
404
- def symbol_position_from(rules)
405
- if rules.has_key?(:symbol_position)
406
- if [:before, :after].include?(rules[:symbol_position])
407
- return rules[:symbol_position]
408
- else
409
- raise ArgumentError, ":symbol_position must be ':before' or ':after'"
410
- end
411
- elsif currency.symbol_first?
412
- :before
413
- else
414
- :after
415
- end
416
- end
417
- end