money2 7.0.0.rc1

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,66 @@
1
+ [
2
+ {
3
+ "priority": 100,
4
+ "code": "BTC",
5
+ "name": "Bitcoin",
6
+ "symbol": "B⃦",
7
+ "subunit": "Satoshi",
8
+ "subunit_to_unit": 100000000,
9
+ "symbol_first": true,
10
+ "decimal_mark": ".",
11
+ "thousands_separator": ",",
12
+ "smallest_denomination": 1
13
+ },
14
+ {
15
+ "priority": 100,
16
+ "code": "JEP",
17
+ "name": "Jersey Pound",
18
+ "symbol": "£",
19
+ "disambiguate_symbol": "JEP",
20
+ "subunit": "Penny",
21
+ "subunit_to_unit": 100,
22
+ "symbol_first": true,
23
+ "html_entity": "£",
24
+ "decimal_mark": ".",
25
+ "thousands_separator": ",",
26
+ "smallest_denomination": 1
27
+ },
28
+ {
29
+ "priority": 100,
30
+ "code": "GGP",
31
+ "name": "Guernsey Pound",
32
+ "symbol": "£",
33
+ "disambiguate_symbol": "GGP",
34
+ "subunit": "Penny",
35
+ "subunit_to_unit": 100,
36
+ "symbol_first": true,
37
+ "html_entity": "£",
38
+ "decimal_mark": ".",
39
+ "thousands_separator": ",",
40
+ "smallest_denomination": 1
41
+ },
42
+ {
43
+ "priority": 100,
44
+ "code": "IMP",
45
+ "name": "Isle of Man Pound",
46
+ "symbol": "£",
47
+ "disambiguate_symbol": "IMP",
48
+ "alternate_symbols": ["M£"],
49
+ "subunit": "Penny",
50
+ "subunit_to_unit": 100,
51
+ "symbol_first": true,
52
+ "html_entity": "£",
53
+ "decimal_mark": ".",
54
+ "thousands_separator": ",",
55
+ "smallest_denomination": 1
56
+ },
57
+ {
58
+ "priority": 100,
59
+ "code": "XFU",
60
+ "name": "UIC Franc",
61
+ "subunit_to_unit": 100,
62
+ "symbol_first": true,
63
+ "decimal_mark": ".",
64
+ "thousands_separator": ","
65
+ }
66
+ ]
data/lib/money.rb ADDED
@@ -0,0 +1,390 @@
1
+ # encoding: utf-8
2
+ require "bigdecimal"
3
+ require "bigdecimal/util"
4
+ require "set"
5
+ require "i18n"
6
+
7
+ # "Money is any object or record that is generally accepted as payment for
8
+ # goods and services and repayment of debts in a given socio-economic context
9
+ # or country." -Wikipedia
10
+ #
11
+ # An instance of Money represents an amount of a specific currency.
12
+ #
13
+ # Money is a value object and should be treated as immutable.
14
+ #
15
+ # @see http://en.wikipedia.org/wiki/Money
16
+ class Money
17
+ require "money/class_attribute"
18
+ require "money/currency"
19
+ require "money/bank/variable_exchange"
20
+ require "money/bank/single_currency"
21
+ require "money/currency_methods"
22
+ require "money/allocate"
23
+ require "money/arithmetic"
24
+ require "money/formatter"
25
+ require "money/formatter/to_string"
26
+ autoload :V6Compatibility, "money/v6_compatibility"
27
+
28
+ extend ClassAttribute
29
+ include CurrencyMethods
30
+ include Comparable
31
+ include Allocate
32
+ include Arithmetic
33
+
34
+ # Raised when smallest denomination of a currency is not defined
35
+ class UndefinedSmallestDenomination < StandardError; end
36
+
37
+ # Class Methods
38
+ class << self
39
+ def default_currency=(val)
40
+ block =
41
+ if val.respond_to?(:call)
42
+ val
43
+ else
44
+ val = Currency.new(val)
45
+ -> { val }
46
+ end
47
+ define_singleton_method(:default_currency, &block)
48
+ end
49
+
50
+ # Use this to return the rounding mode. You may also pass a
51
+ # rounding mode and a block to temporarily change it. It will
52
+ # then return the results of the block instead.
53
+ #
54
+ # @param [BigDecimal::ROUND_MODE] mode
55
+ #
56
+ # @return [BigDecimal::ROUND_MODE,Yield] rounding mode or block results
57
+ #
58
+ # @example
59
+ # fee = Money.rounding_mode(BigDecimal::ROUND_HALF_UP) do
60
+ # Money.new(1200) * BigDecimal.new('0.029')
61
+ # end
62
+ def rounding_mode(mode = nil)
63
+ if mode.nil?
64
+ Thread.current[:money_rounding_mode] || _rounding_mode
65
+ else
66
+ begin
67
+ prev = Thread.current[:money_rounding_mode]
68
+ Thread.current[:money_rounding_mode] = mode
69
+ yield
70
+ ensure
71
+ Thread.current[:money_rounding_mode] = prev
72
+ Thread.current[:money_rounding_mode] = nil
73
+ end
74
+ end
75
+ end
76
+
77
+ # Parses decimal and rounds it according to currency and default settings.
78
+ def prepare_d(value, currency)
79
+ value =
80
+ if value.respond_to?(:to_d)
81
+ value.is_a?(Rational) ? value.to_d(conversion_precision) : value.to_d
82
+ else
83
+ BigDecimal.new(value.to_s)
84
+ end
85
+ if infinite_precision
86
+ value
87
+ else
88
+ value.round(currency.decimal_places, rounding_mode)
89
+ end
90
+ end
91
+
92
+ # Adds a new exchange rate to the default bank and return the rate.
93
+ #
94
+ # @param [Currency, String, Symbol] from_currency Currency to exchange from.
95
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
96
+ # @param [Numeric] rate Rate to exchange with.
97
+ #
98
+ # @return [Numeric]
99
+ #
100
+ # @example
101
+ # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
102
+ def add_rate(from_currency, to_currency, rate)
103
+ default_bank.add_rate(from_currency, to_currency, rate)
104
+ end
105
+
106
+ # Sets the default bank to be a SingleCurrency bank that raises on
107
+ # currency exchange. Useful when apps operate in a single currency at a time.
108
+ def disallow_currency_conversion!
109
+ self.default_bank = Bank::SingleCurrency.instance
110
+ end
111
+
112
+ # Create a new money object with value 0.
113
+ #
114
+ # @param [Currency, String, Symbol] currency The currency to use.
115
+ #
116
+ # @return [Money]
117
+ #
118
+ # @example
119
+ # Money.empty #=> #<Money @fractional=0>
120
+ def empty(currency = default_currency)
121
+ @empty ||= {}
122
+ @empty[currency] ||= new(0, currency).freeze
123
+ end
124
+ alias_method :zero, :empty
125
+
126
+ # Creates a new Money object of value given in the +subunit+ of the given
127
+ # +currency+.
128
+ #
129
+ # @param [Numeric] amount The numerical value of the money.
130
+ # @param [Currency, String, Symbol] currency The currency format.
131
+ # @param [Money::Bank::*] bank The exchange bank to use.
132
+ #
133
+ # @example
134
+ # Money.from_subunits(2345, "USD") # => #<Money amount:23.45 currency:USD>
135
+ # Money.from_subunits(2345, "JPY") # => #<Money amount:2345 currency:JPY>
136
+ #
137
+ # @return [Money]
138
+ #
139
+ # @see #initialize
140
+ def from_subunits(amount, currency = default_currency, bank = default_bank)
141
+ raise ArgumentError, '`amount` must have #to_d' unless amount.respond_to?(:to_d)
142
+ currency = Currency.wrap(currency)
143
+ value = amount.to_d / currency.subunit_to_unit
144
+ new(value, currency, bank)
145
+ end
146
+ end
147
+
148
+ # @!attribute [rw] default_bank
149
+ # @return [Money::Bank::Base] Each Money object is associated to a bank
150
+ # object, which is responsible for currency exchange. This property
151
+ # allows you to specify the default bank object. The default value for
152
+ # this property is an instance of +Bank::VariableExchange.+ It allows
153
+ # one to specify custom exchange rates.
154
+ class_attribute :default_bank
155
+ self.default_bank = Bank::VariableExchange.instance
156
+
157
+ # @!attribute default_currency
158
+ # @return [Money::Currency] The default currency, which is used when
159
+ # +Money.new+ is called without an explicit currency argument. The
160
+ # default value is Currency.new("USD"). The value must be a valid
161
+ # +Money::Currency+ instance.
162
+ # Set the default currency for creating new +Money+ object.
163
+ self.default_currency = -> { Currency.new(:USD) }
164
+
165
+ # @!attribute [rw] infinite_precision
166
+ # @return [Boolean] Use this to enable infinite precision cents
167
+ class_attribute :infinite_precision
168
+ self.infinite_precision = false
169
+
170
+ #
171
+ class_attribute :_rounding_mode
172
+ singleton_class.send :alias_method, :rounding_mode=, :_rounding_mode=
173
+ # Default to bankers rounding
174
+ self.rounding_mode = BigDecimal::ROUND_HALF_EVEN
175
+
176
+ # @!attribute [rw] conversion_precision
177
+ # Default the conversion of Rationals precision to 16
178
+ #
179
+ # @return [Fixnum] Use this to specify precision for converting Rational
180
+ # to BigDecimal
181
+ class_attribute :conversion_precision
182
+ self.conversion_precision = 16
183
+
184
+ # @!attribute [rw] conversion_precision
185
+ # Default formatter to use in #fromat. By default +Money::Formatter+ is used.
186
+ class_attribute :formatter
187
+ self.formatter = Formatter
188
+
189
+ # Creates a new Money object of value given with amount of +units+ of
190
+ # the given +currency+.
191
+ #
192
+ # Alternatively you can use the convenience
193
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
194
+ #
195
+ # @param [Object] obj Either the fractional value of the money,
196
+ # a Money object, or a currency.
197
+ # @param [Currency, String, Symbol] currency The currency format.
198
+ # @param [Money::Bank::*] bank The exchange bank to use.
199
+ #
200
+ # @return [Money]
201
+ #
202
+ # @example
203
+ # Money.new(100) #=> #<Money @fractional=100 @currency="USD">
204
+ # Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
205
+ # Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
206
+ #
207
+ def initialize(val, currency = nil, bank = nil)
208
+ @currency =
209
+ if currency
210
+ Currency.wrap(currency)
211
+ else
212
+ val.respond_to?(:currency) ? val.currency : self.class.default_currency
213
+ end
214
+ @amount = val.respond_to?(:amount) ? val.amount : self.class.prepare_d(val, @currency)
215
+ @bank = bank || (val.respond_to?(:bank) ? val.bank : self.class.default_bank)
216
+ end
217
+
218
+ def yaml_initialize(_tag, attrs)
219
+ initialize(attrs['amount'] || 0, attrs['currency'], attrs['bank'])
220
+ end
221
+
222
+ # The value of the monetary amount represented in the fractional or subunit
223
+ # of the currency.
224
+ #
225
+ # For example, in the US dollar currency the fractional unit is cents, and
226
+ # there are 100 cents in one US dollar. So given the Money representation of
227
+ # one US dollar, the fractional interpretation is 100.
228
+ #
229
+ # Another example is that of the Kuwaiti dinar. In this case the fractional
230
+ # unit is the fils and there 1000 fils to one Kuwaiti dinar. So given the
231
+ # Money representation of one Kuwaiti dinar, the fractional interpretation is
232
+ # 1000.
233
+ #
234
+ # @return [Integer] when infinite_precision is false
235
+ # @return [BigDecimal] when infinite_precision is true
236
+ #
237
+ # @see infinite_precision
238
+ def fractional
239
+ value = to_d * currency.subunit_to_unit
240
+ if self.class.infinite_precision
241
+ value
242
+ else
243
+ value.round(0, self.class.rounding_mode).to_i
244
+ end
245
+ end
246
+
247
+ # Round a given amount of money to the nearest possible amount in cash value. For
248
+ # example, in Swiss franc (CHF), the smallest possible amount of cash value is
249
+ # CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and CHF 0.08 to
250
+ # CHF 0.10.
251
+ #
252
+ # @return [Integer] when infinite_precision is false
253
+ # @return [BigDecimal] when infinite_precision is true
254
+ #
255
+ # @see infinite_precision
256
+ def round_to_nearest_cash_value
257
+ unless currency.smallest_denomination
258
+ raise UndefinedSmallestDenomination, 'Smallest denomination of this currency is not defined'
259
+ end
260
+ smallest_denomination = currency.smallest_denomination
261
+ value = (to_d / smallest_denomination).
262
+ round(currency.decimal_places, self.class.rounding_mode)
263
+ value * smallest_denomination
264
+ end
265
+
266
+ # @!attribute [r] currency
267
+ # @return [Currency] The money's currency.
268
+ # @!attribute [r] bank
269
+ # @return [Money::Bank::Base] The +Money::Bank+-based object which currency
270
+ # exchanges are performed with.
271
+ attr_reader :currency, :bank, :amount
272
+ alias_method :to_d, :amount
273
+
274
+ # Returns a Fixnum hash value based on the +fractional+ and +currency+ attributes
275
+ # in order to use functions like & (intersection), group_by, etc.
276
+ #
277
+ # @return [Fixnum]
278
+ #
279
+ # @example
280
+ # Money.new(100).hash #=> 908351
281
+ def hash
282
+ [to_d.hash, currency.hash].hash
283
+ end
284
+
285
+ # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
286
+ #
287
+ # @return [String]
288
+ #
289
+ # @example
290
+ # Money.new(100, "USD").symbol #=> "$"
291
+ def symbol
292
+ currency.symbol || "¤"
293
+ end
294
+
295
+ # Common inspect function
296
+ #
297
+ # @return [String]
298
+ def inspect
299
+ "#<#{self.class.name} amount:#{to_d.to_s('F')} currency:#{currency}>"
300
+ end
301
+
302
+ # Return the amount of money as a Integer.
303
+ #
304
+ # @return [Integer]
305
+ #
306
+ # @example
307
+ # Money.us_dollar(1_00).to_i #=> 1
308
+ def to_i
309
+ to_d.to_i
310
+ end
311
+
312
+ # Conversation to +self+.
313
+ #
314
+ # @return [self]
315
+ def to_money(new_currency = nil)
316
+ new_currency ? exchange_to(new_currency) : self
317
+ end
318
+
319
+ # Receive the amount of this money object in another Currency.
320
+ #
321
+ # @param [Currency, String, Symbol] other_currency Currency to exchange to.
322
+ #
323
+ # @yield [n] Optional block to use when rounding after exchanging one currency
324
+ # for another.
325
+ # @yieldparam [Float] n The resulting float after exchanging one currency for
326
+ # another.
327
+ # @yieldreturn [Integer]
328
+ #
329
+ # @return [Money]
330
+ #
331
+ # @example
332
+ # Money.new(2000, "USD").exchange_to("EUR")
333
+ # Money.new(2000, "USD").exchange_to("EUR") {|x| x.round}
334
+ # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
335
+ def exchange_to(other_currency, &rounding_method)
336
+ other_currency = Currency.wrap(other_currency)
337
+ if currency == other_currency
338
+ self
339
+ else
340
+ bank.exchange_with(self, other_currency, &rounding_method)
341
+ end
342
+ end
343
+
344
+ # Round the monetary amount to smallest unit of coinage.
345
+ #
346
+ # @note
347
+ # This method is only useful when operating with infinite_precision turned
348
+ # on. Without infinite_precision values are rounded to the smallest unit of
349
+ # coinage automatically.
350
+ #
351
+ # @return [Money]
352
+ #
353
+ # @example
354
+ # Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
355
+ #
356
+ # @see
357
+ # Money.infinite_precision
358
+ #
359
+ def round(rounding_mode = self.class.rounding_mode)
360
+ if self.class.infinite_precision
361
+ build_new(to_d.round(currency.decimal_places, rounding_mode), currency)
362
+ else
363
+ self
364
+ end
365
+ end
366
+
367
+ # Formats value using formatter. Default formatter is +Money.formatter+ which is
368
+ # +Money::Formatter+ by default.
369
+ def format(formatter = self.class.formatter, **options)
370
+ formatter.format(self, options)
371
+ end
372
+
373
+ # Returns the amount of money as a string.
374
+ #
375
+ # @return [String]
376
+ #
377
+ # @example
378
+ # Money.ca_dollar(100).to_s #=> "1.00"
379
+ def to_s
380
+ Formatter::ToString.format(self)
381
+ end
382
+
383
+ private
384
+
385
+ # Used only for v6 compatibility. Should be replaced with inline `self.class.new`
386
+ # after this compatibility is dropped.
387
+ def build_new(*args)
388
+ self.class.new(*args)
389
+ end
390
+ end