money 3.1.0 → 3.1.5
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.
- data/CHANGELOG.md +27 -0
- data/lib/money.rb +2 -1
- data/lib/money/bank/variable_exchange.rb +4 -4
- data/lib/money/core_extensions.rb +24 -149
- data/lib/money/money.rb +445 -39
- metadata +7 -7
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
|
+
Money 3.1.5
|
2
|
+
===========
|
3
|
+
|
4
|
+
Features
|
5
|
+
--------
|
6
|
+
- Added support for creating objects with the main monetary unit instead of
|
7
|
+
cents.
|
8
|
+
([#issue/25](http://github.com/RubyMoney/money/issues/25))
|
9
|
+
- Deprecated `Money#format` with separate params instead of Hash. Deprecation
|
10
|
+
target set to Money 3.5.0.
|
11
|
+
([#issue/31](http://github.com/RubyMoney/money/issues/31))
|
12
|
+
- Deprecated `Money#new(0, :currency => "EUR")` in favor of
|
13
|
+
`Money#new(0, "EUR")`. Deprecation target set to Money 3.5.0.
|
14
|
+
([#issue/31](http://github.com/RubyMoney/money/issues/31))
|
15
|
+
- Throw ArgumentError when trying to multiply two Money objects together.
|
16
|
+
([#issue/29](http://github.com/RubyMoney/money/issues/29))
|
17
|
+
- Update Money#parse to use :subunit_to_unit
|
18
|
+
([#issue/30](http://github.com/RubyMoney/money/issues/30))
|
19
|
+
|
20
|
+
Bugfixes
|
21
|
+
--------
|
22
|
+
- Downgraded required_rubygems_version to >= 1.3.6.
|
23
|
+
([#issue/26](http://github.com/RubyMoney/money/issues/26))
|
24
|
+
- Use BigDecimal when floating point calculations are needed.
|
25
|
+
- Ruby 1.9.2 compatibility enhancements.
|
26
|
+
|
27
|
+
|
1
28
|
Money 3.1.0
|
2
29
|
===========
|
3
30
|
|
data/lib/money.rb
CHANGED
@@ -20,7 +20,8 @@
|
|
20
20
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
|
23
|
-
|
23
|
+
require 'bigdecimal'
|
24
|
+
require 'money/currency'
|
24
25
|
require 'money/money'
|
25
26
|
require 'money/defaults'
|
26
27
|
require 'money/core_extensions'
|
@@ -81,9 +81,10 @@ class Money
|
|
81
81
|
end
|
82
82
|
_to_currency_ = Currency.wrap(to_currency)
|
83
83
|
|
84
|
-
cents = from.cents / (from.currency.subunit_to_unit.
|
84
|
+
cents = from.cents / (BigDecimal.new(from.currency.subunit_to_unit.to_s) / BigDecimal.new(_to_currency_.subunit_to_unit.to_s))
|
85
85
|
|
86
|
-
ex = cents * rate
|
86
|
+
ex = cents * BigDecimal.new(rate.to_s)
|
87
|
+
ex = ex.to_f
|
87
88
|
ex = if block_given?
|
88
89
|
block.call(ex)
|
89
90
|
elsif @rounding_method
|
@@ -151,8 +152,7 @@ class Money
|
|
151
152
|
# Available formats are +:json+, +:ruby+ and +:yaml+.
|
152
153
|
#
|
153
154
|
# @param [Symbol] format Request format for the resulting string.
|
154
|
-
# @param [
|
155
|
-
# rates to.
|
155
|
+
# @param [String] file Optional file location to write the rates to.
|
156
156
|
#
|
157
157
|
# @return [String]
|
158
158
|
#
|
@@ -1,37 +1,34 @@
|
|
1
1
|
# Open +Numeric+ to add new method.
|
2
2
|
class Numeric
|
3
|
-
|
3
|
+
|
4
|
+
# Converts this numeric into a +Money+ object in the given +currency+.
|
4
5
|
#
|
5
|
-
# @param [
|
6
|
-
#
|
6
|
+
# @param [Currency, String, Symbol] currency
|
7
|
+
# The currency to set the resulting +Money+ object to.
|
7
8
|
#
|
8
9
|
# @return [Money]
|
9
10
|
#
|
10
11
|
# @example
|
11
12
|
# 100.to_money #=> #<Money @cents=10000>
|
12
13
|
# 100.37.to_money #=> #<Money @cents=10037>
|
13
|
-
# require 'bigdecimal'
|
14
14
|
# BigDecimal.new('100').to_money #=> #<Money @cents=10000>
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
amt.to_s('F')
|
21
|
-
else
|
22
|
-
amt.to_s
|
23
|
-
end
|
24
|
-
Money.new(amt.to_i, currency)
|
15
|
+
#
|
16
|
+
# @see Money.from_numeric
|
17
|
+
#
|
18
|
+
def to_money(currency = nil)
|
19
|
+
Money.from_numeric(self, currency || Money.default_currency)
|
25
20
|
end
|
21
|
+
|
26
22
|
end
|
27
23
|
|
28
24
|
# Open +String+ to add new methods.
|
29
25
|
class String
|
30
|
-
|
31
|
-
#
|
26
|
+
|
27
|
+
# Parses the current string and converts it to a +Money+ object.
|
28
|
+
# Excess characters will be discarded.
|
32
29
|
#
|
33
|
-
# @param [
|
34
|
-
#
|
30
|
+
# @param [Currency, String, Symbol] currency
|
31
|
+
# The currency to set the resulting +Money+ object to.
|
35
32
|
#
|
36
33
|
# @return [Money]
|
37
34
|
#
|
@@ -42,147 +39,25 @@ class String
|
|
42
39
|
# 'USD 100'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
|
43
40
|
# '$100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
|
44
41
|
# 'hello 2000 world'.to_money #=> #<Money @cents=200000 @currency=#<Money::Currency id: usd>>
|
42
|
+
#
|
43
|
+
# @see Money.from_string
|
44
|
+
#
|
45
45
|
def to_money(currency = nil)
|
46
|
-
|
47
|
-
matches = scan /([A-Z]{2,3})/
|
48
|
-
_currency_ = matches[0] ? matches[0][0] : nil
|
49
|
-
|
50
|
-
# check that currency passed and embedded currency are the same, or only
|
51
|
-
# one or the other is present.
|
52
|
-
if currency.nil? and _currency_.nil?
|
53
|
-
currency = Money.default_currency
|
54
|
-
elsif currency.nil?
|
55
|
-
currency = _currency_
|
56
|
-
elsif _currency_.nil?
|
57
|
-
currency = currency
|
58
|
-
elsif currency != _currency_
|
59
|
-
raise "mismatching currencies"
|
60
|
-
end
|
61
|
-
|
62
|
-
cents = calculate_cents(self)
|
63
|
-
Money.new(cents, currency)
|
46
|
+
Money.parse(self, currency)
|
64
47
|
end
|
65
48
|
|
66
|
-
#
|
49
|
+
# Converts the current string into a +Currency+ object.
|
67
50
|
#
|
68
51
|
# @return [Money::Currency]
|
69
52
|
#
|
53
|
+
# @raise [Money::Currency::UnknownCurrency]
|
54
|
+
# If this String reference an unknown currency.
|
55
|
+
#
|
70
56
|
# @example
|
71
57
|
# "USD".to_currency #=> #<Money::Currency id: usd>
|
58
|
+
#
|
72
59
|
def to_currency
|
73
60
|
Money::Currency.new(self)
|
74
61
|
end
|
75
62
|
|
76
|
-
private
|
77
|
-
|
78
|
-
# Takes a number string and attempts to massage out the number.
|
79
|
-
#
|
80
|
-
# @param [String] number The string containing a potential number.
|
81
|
-
#
|
82
|
-
# @return [Integer]
|
83
|
-
def calculate_cents(number)
|
84
|
-
# remove anything that's not a number, potential delimiter, or minus sign
|
85
|
-
num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
|
86
|
-
|
87
|
-
# set a boolean flag for if the number is negative or not
|
88
|
-
negative = num.split(//).first == "-"
|
89
|
-
|
90
|
-
# if negative, remove the minus sign from the number
|
91
|
-
num = num.gsub(/^-/, '') if negative
|
92
|
-
|
93
|
-
# gather all separators within the result number
|
94
|
-
used_separators = num.scan /[^\d]/
|
95
|
-
|
96
|
-
# determine the number of unique separators within the number
|
97
|
-
#
|
98
|
-
# e.g.
|
99
|
-
# $1,234,567.89 would return 2 (, and .)
|
100
|
-
# $125,00 would return 1
|
101
|
-
# $199 would return 0
|
102
|
-
# $1 234,567.89 would raise an error (separators are space, comma, and period)
|
103
|
-
case used_separators.uniq.length
|
104
|
-
# no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
|
105
|
-
when 0 then major, minor = num, 0
|
106
|
-
|
107
|
-
# two separators, so we know the last item in this array is the
|
108
|
-
# major/minor delimiter and the rest are separators
|
109
|
-
when 2
|
110
|
-
separator, delimiter = used_separators.uniq
|
111
|
-
# remove all separators, split on the delimiter
|
112
|
-
major, minor = num.gsub(separator, '').split(delimiter)
|
113
|
-
min = 0 unless min
|
114
|
-
when 1
|
115
|
-
# we can't determine if the comma or period is supposed to be a separator or a delimiter
|
116
|
-
# e.g.
|
117
|
-
# 1,00 - comma is a delimiter
|
118
|
-
# 1.000 - period is a delimiter
|
119
|
-
# 1,000 - comma is a separator
|
120
|
-
# 1,000,000 - comma is a separator
|
121
|
-
# 10000,00 - comma is a delimiter
|
122
|
-
# 1000,000 - comma is a delimiter
|
123
|
-
|
124
|
-
# assign first separator for reusability
|
125
|
-
separator = used_separators.first
|
126
|
-
|
127
|
-
# separator is used as a separator when there are multiple instances, always
|
128
|
-
if num.scan(separator).length > 1 # multiple matches; treat as separator
|
129
|
-
major, minor = num.gsub(separator, ''), 0
|
130
|
-
else
|
131
|
-
# ex: 1,000 - 1.0000 - 10001.000
|
132
|
-
# split number into possible major (dollars) and minor (cents) values
|
133
|
-
possible_major, possible_minor = num.split(separator)
|
134
|
-
possible_major ||= "0"
|
135
|
-
possible_minor ||= "00"
|
136
|
-
|
137
|
-
# if the minor (cents) length isn't 3, assign major/minor from the possibles
|
138
|
-
# e.g.
|
139
|
-
# 1,00 => 1.00
|
140
|
-
# 1.0000 => 1.00
|
141
|
-
# 1.2 => 1.20
|
142
|
-
if possible_minor.length != 3 # delimiter
|
143
|
-
major, minor = possible_major, possible_minor
|
144
|
-
else
|
145
|
-
# minor length is three
|
146
|
-
# let's try to figure out intent of the delimiter
|
147
|
-
|
148
|
-
# the major length is greater than three, which means
|
149
|
-
# the comma or period is used as a delimiter
|
150
|
-
# e.g.
|
151
|
-
# 1000,000
|
152
|
-
# 100000,000
|
153
|
-
if possible_major.length > 3
|
154
|
-
major, minor = possible_major, possible_minor
|
155
|
-
else
|
156
|
-
# number is in format ###{sep}### or ##{sep}### or #{sep}###
|
157
|
-
# handle as , is sep, . is delimiter
|
158
|
-
if separator == '.'
|
159
|
-
major, minor = possible_major, possible_minor
|
160
|
-
else
|
161
|
-
major, minor = "#{possible_major}#{possible_minor}", 0
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
else
|
167
|
-
raise ArgumentError, "Invalid currency amount"
|
168
|
-
end
|
169
|
-
|
170
|
-
# build the string based on major/minor since separator/delimiters have been removed
|
171
|
-
# avoiding floating point arithmetic here to ensure accuracy
|
172
|
-
cents = (major.to_i * 100)
|
173
|
-
# add the minor number as well. this may have any number of digits,
|
174
|
-
# so we treat minor as a string and truncate or right-fill it with zeroes
|
175
|
-
# until it becomes a two-digit number string, which we add to cents.
|
176
|
-
minor = minor.to_s
|
177
|
-
truncated_minor = minor[0..1]
|
178
|
-
truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
|
179
|
-
cents += truncated_minor.to_i
|
180
|
-
# respect rounding rules
|
181
|
-
if minor.size >= 3 && minor[2..2].to_i >= 5
|
182
|
-
cents += 1
|
183
|
-
end
|
184
|
-
|
185
|
-
# if negative, multiply by -1; otherwise, return positive cents
|
186
|
-
negative ? cents * -1 : cents
|
187
|
-
end
|
188
63
|
end
|
data/lib/money/money.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require 'money/currency'
|
3
2
|
require 'money/bank/variable_exchange'
|
4
3
|
|
5
4
|
# Represents an amount of money in a given currency.
|
6
5
|
class Money
|
7
6
|
include Comparable
|
8
7
|
|
9
|
-
# The value of the money
|
8
|
+
# The value of the money in cents.
|
10
9
|
#
|
11
10
|
# @return [Integer]
|
12
11
|
attr_reader :cents
|
@@ -101,10 +100,258 @@ class Money
|
|
101
100
|
Money.new(cents, "EUR")
|
102
101
|
end
|
103
102
|
|
103
|
+
|
104
|
+
# Creates a new Money object of +amount+ value in dollars,
|
105
|
+
# with given +currency+.
|
106
|
+
#
|
107
|
+
# The amount value is expressed in +dollars+
|
108
|
+
# where the +dollar+ is the main monetary unit,
|
109
|
+
# opposite to the subunit-based representation
|
110
|
+
# used internally by this library called +cents+.
|
111
|
+
#
|
112
|
+
# @param [Numeric] amount The money amount, in dollars.
|
113
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
114
|
+
# @param [Money::Bank::*] bank The exchange bank to use.
|
115
|
+
#
|
116
|
+
# @return [Money]
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# Money.new_with_dollars(100)
|
120
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
121
|
+
# Money.new_with_dollars(100, "USD")
|
122
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
123
|
+
# Money.new_with_dollars(100, "EUR")
|
124
|
+
# #=> #<Money @cents=10000 @currency="EUR">
|
125
|
+
#
|
126
|
+
# @see Money.new
|
127
|
+
#
|
128
|
+
def self.new_with_dollars(amount, currency = Money.default_currency, bank = Money.default_bank)
|
129
|
+
money = from_numeric(amount, currency)
|
130
|
+
# Hack! You can't change a bank
|
131
|
+
money.instance_variable_set("@bank", bank)
|
132
|
+
money
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# Parses the current string and converts it to a +Money+ object.
|
137
|
+
# Excess characters will be discarded.
|
138
|
+
#
|
139
|
+
# @param [String, #to_s] input The input to parse.
|
140
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
141
|
+
# The currency to set the resulting +Money+ object to.
|
142
|
+
#
|
143
|
+
# @return [Money]
|
144
|
+
#
|
145
|
+
# @raise [ArgumentError] If any +currency+ is supplied and
|
146
|
+
# given value doesn't match the one extracted from
|
147
|
+
# the +input+ string.
|
148
|
+
#
|
149
|
+
# @example
|
150
|
+
# '100'.to_money #=> #<Money @cents=10000>
|
151
|
+
# '100.37'.to_money #=> #<Money @cents=10037>
|
152
|
+
# '100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
|
153
|
+
# 'USD 100'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
|
154
|
+
# '$100 USD'.to_money #=> #<Money @cents=10000, @currency=#<Money::Currency id: usd>>
|
155
|
+
# 'hello 2000 world'.to_money #=> #<Money @cents=200000 @currency=#<Money::Currency id: usd>>
|
156
|
+
#
|
157
|
+
# @example Mismatching currencies
|
158
|
+
# 'USD 2000'.to_money("EUR") #=> ArgumentError
|
159
|
+
#
|
160
|
+
# @see Money.from_string
|
161
|
+
#
|
162
|
+
def self.parse(input, currency = nil)
|
163
|
+
i = input.to_s
|
164
|
+
|
165
|
+
# Get the currency.
|
166
|
+
m = i.scan /([A-Z]{2,3})/
|
167
|
+
c = m[0] ? m[0][0] : nil
|
168
|
+
|
169
|
+
# check that currency passed and embedded currency are the same,
|
170
|
+
# and negotiate the final currency
|
171
|
+
if currency.nil? and c.nil?
|
172
|
+
currency = Money.default_currency
|
173
|
+
elsif currency.nil?
|
174
|
+
currency = c
|
175
|
+
elsif c.nil?
|
176
|
+
currency = currency
|
177
|
+
elsif currency != c
|
178
|
+
# TODO: ParseError
|
179
|
+
raise ArgumentError, "Mismatching Currencies"
|
180
|
+
end
|
181
|
+
currency = Money::Currency.wrap(currency)
|
182
|
+
|
183
|
+
cents = extract_cents(i, currency)
|
184
|
+
Money.new(cents, currency)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Converts a String into a Money object treating the +value+
|
188
|
+
# as dollars and converting them to the corresponding cents value,
|
189
|
+
# according to +currency+ subunit property,
|
190
|
+
# before instantiating the Money object.
|
191
|
+
#
|
192
|
+
# Behind the scenes, this method relies on {Money.from_bigdecimal}
|
193
|
+
# to avoid problems with string-to-numeric conversion.
|
194
|
+
#
|
195
|
+
# @param [String, #to_s] value The money amount, in dollars.
|
196
|
+
# @param [Currency, String, Symbol] currency
|
197
|
+
# The currency to set the resulting +Money+ object to.
|
198
|
+
#
|
199
|
+
# @return [Money]
|
200
|
+
#
|
201
|
+
# @example
|
202
|
+
# Money.from_string("100")
|
203
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
204
|
+
# Money.from_string("100", "USD")
|
205
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
206
|
+
# Money.from_string("100", "EUR")
|
207
|
+
# #=> #<Money @cents=10000 @currency="EUR">
|
208
|
+
# Money.from_string("100", "BHD")
|
209
|
+
# #=> #<Money @cents=100 @currency="BHD">
|
210
|
+
#
|
211
|
+
# @see String#to_money
|
212
|
+
# @see Money.parse
|
213
|
+
#
|
214
|
+
def self.from_string(value, currency = Money.default_currency)
|
215
|
+
from_bigdecimal(BigDecimal.new(value.to_s), currency)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Converts a Fixnum into a Money object treating the +value+
|
219
|
+
# as dollars and converting them to the corresponding cents value,
|
220
|
+
# according to +currency+ subunit property,
|
221
|
+
# before instantiating the Money object.
|
222
|
+
#
|
223
|
+
# @param [Fixnum] value The money amount, in dollars.
|
224
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
225
|
+
#
|
226
|
+
# @return [Money]
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
# Money.from_fixnum(100)
|
230
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
231
|
+
# Money.from_fixnum(100, "USD")
|
232
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
233
|
+
# Money.from_fixnum(100, "EUR")
|
234
|
+
# #=> #<Money @cents=10000 @currency="EUR">
|
235
|
+
# Money.from_fixnum(100, "BHD")
|
236
|
+
# #=> #<Money @cents=100 @currency="BHD">
|
237
|
+
#
|
238
|
+
# @see Fixnum#to_money
|
239
|
+
# @see Money.from_numeric
|
240
|
+
#
|
241
|
+
def self.from_fixnum(value, currency = Money.default_currency)
|
242
|
+
currency = Money::Currency.wrap(currency)
|
243
|
+
amount = value * currency.subunit_to_unit
|
244
|
+
Money.new(amount, currency)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Converts a Float into a Money object treating the +value+
|
248
|
+
# as dollars and converting them to the corresponding cents value,
|
249
|
+
# according to +currency+ subunit property,
|
250
|
+
# before instantiating the Money object.
|
251
|
+
#
|
252
|
+
# Behind the scenes, this method relies on Money.from_bigdecimal
|
253
|
+
# to avoid problems with floating point precision.
|
254
|
+
#
|
255
|
+
# @param [Float] value The money amount, in dollars.
|
256
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
257
|
+
#
|
258
|
+
# @return [Money]
|
259
|
+
#
|
260
|
+
# @example
|
261
|
+
# Money.from_float(100.0)
|
262
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
263
|
+
# Money.from_float(100.0, "USD")
|
264
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
265
|
+
# Money.from_float(100.0, "EUR")
|
266
|
+
# #=> #<Money @cents=10000 @currency="EUR">
|
267
|
+
# Money.from_float(100.0, "BHD")
|
268
|
+
# #=> #<Money @cents=100 @currency="BHD">
|
269
|
+
#
|
270
|
+
# @see Float#to_money
|
271
|
+
# @see Money.from_numeric
|
272
|
+
#
|
273
|
+
def self.from_float(value, currency = Money.default_currency)
|
274
|
+
from_bigdecimal(BigDecimal.new(value.to_s), currency)
|
275
|
+
end
|
276
|
+
|
277
|
+
# Converts a BigDecimal into a Money object treating the +value+
|
278
|
+
# as dollars and converting them to the corresponding cents value,
|
279
|
+
# according to +currency+ subunit property,
|
280
|
+
# before instantiating the Money object.
|
281
|
+
#
|
282
|
+
# @param [BigDecimal] value The money amount, in dollars.
|
283
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
284
|
+
#
|
285
|
+
# @return [Money]
|
286
|
+
#
|
287
|
+
# @example
|
288
|
+
# Money.from_bigdecimal(BigDecimal.new("100")
|
289
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
290
|
+
# Money.from_bigdecimal(BigDecimal.new("100", "USD")
|
291
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
292
|
+
# Money.from_bigdecimal(BigDecimal.new("100", "EUR")
|
293
|
+
# #=> #<Money @cents=10000 @currency="EUR">
|
294
|
+
# Money.from_bigdecimal(BigDecimal.new("100", "BHD")
|
295
|
+
# #=> #<Money @cents=100 @currency="BHD">
|
296
|
+
#
|
297
|
+
# @see BigDecimal#to_money
|
298
|
+
# @see Money.from_numeric
|
299
|
+
#
|
300
|
+
def self.from_bigdecimal(value, currency = Money.default_currency)
|
301
|
+
currency = Money::Currency.wrap(currency)
|
302
|
+
amount = value * currency.subunit_to_unit
|
303
|
+
Money.new(amount.fix, currency)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Converts a Numeric value into a Money object treating the +value+
|
307
|
+
# as dollars and converting them to the corresponding cents value,
|
308
|
+
# according to +currency+ subunit property,
|
309
|
+
# before instantiating the Money object.
|
310
|
+
#
|
311
|
+
# This method relies on various +Money.from_*+ methods
|
312
|
+
# and tries to forwards the call to the most appropriate method
|
313
|
+
# in order to reduce computation effort.
|
314
|
+
# For instance, if +value+ is an Integer, this method calls
|
315
|
+
# {Money.from_fixnum} instead of using the default
|
316
|
+
# {Money.from_bigdecimal} which adds the overload to converts
|
317
|
+
# the value into a slower BigDecimal instance.
|
318
|
+
#
|
319
|
+
# @param [Numeric] value The money amount, in dollars.
|
320
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
321
|
+
#
|
322
|
+
# @return [Money]
|
323
|
+
#
|
324
|
+
# @raise +ArgumentError+ Unless +value+ is a supported type.
|
325
|
+
#
|
326
|
+
# @example
|
327
|
+
# Money.from_numeric(100)
|
328
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
329
|
+
# Money.from_numeric(100.00)
|
330
|
+
# #=> #<Money @cents=10000 @currency="USD">
|
331
|
+
# Money.from_numeric("100")
|
332
|
+
# #=> ArgumentError
|
333
|
+
#
|
334
|
+
# @see Numeric#to_money
|
335
|
+
# @see Money.from_fixnum
|
336
|
+
# @see Money.from_float
|
337
|
+
# @see Money.from_bigdecimal
|
338
|
+
#
|
339
|
+
def self.from_numeric(value, currency = Money.default_currency)
|
340
|
+
case value
|
341
|
+
when Fixnum
|
342
|
+
from_fixnum(value, currency)
|
343
|
+
when Numeric
|
344
|
+
from_bigdecimal(BigDecimal.new(value.to_s), currency)
|
345
|
+
else
|
346
|
+
raise ArgumentError, "`value' should be a Numeric object"
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
|
104
351
|
# Adds a new exchange rate to the default bank and return the rate.
|
105
352
|
#
|
106
353
|
# @param [Currency, String, Symbol] from_currency Currency to exchange from.
|
107
|
-
# @param [Currency, String, Symbol
|
354
|
+
# @param [Currency, String, Symbol] to_currency Currency to exchange to.
|
108
355
|
# @param [Numeric] rate Rate to exchange with.
|
109
356
|
#
|
110
357
|
# @return [Numeric]
|
@@ -115,19 +362,30 @@ class Money
|
|
115
362
|
Money.default_bank.add_rate(from_currency, to_currency, rate)
|
116
363
|
end
|
117
364
|
|
118
|
-
|
119
|
-
#
|
365
|
+
|
366
|
+
# Creates a new Money object of +cents+ value in cents,
|
367
|
+
# with given +currency+.
|
120
368
|
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
369
|
+
# Alternatively you can use the convenience
|
370
|
+
# methods like {Money.ca_dollar} and {Money.us_dollar}.
|
371
|
+
#
|
372
|
+
# @param [Integer] cents The money amount, in cents.
|
373
|
+
# @param [Currency, String, Symbol] currency The currency format.
|
374
|
+
# @param [Money::Bank::*] bank The exchange bank to use.
|
124
375
|
#
|
125
376
|
# @return [Money]
|
126
377
|
#
|
127
378
|
# @example
|
128
|
-
#
|
129
|
-
|
130
|
-
|
379
|
+
# Money.new(100)
|
380
|
+
# #=> #<Money @cents=100 @currency="USD">
|
381
|
+
# Money.new(100, "USD")
|
382
|
+
# #=> #<Money @cents=100 @currency="USD">
|
383
|
+
# Money.new(100, "EUR")
|
384
|
+
# #=> #<Money @cents=100 @currency="EUR">
|
385
|
+
#
|
386
|
+
# @see Money.new_with_dollars
|
387
|
+
#
|
388
|
+
def initialize(cents, currency = Money.default_currency, bank = Money.default_bank)
|
131
389
|
@cents = cents.round
|
132
390
|
if currency.is_a?(Hash)
|
133
391
|
# Earlier versions of Money wrongly documented the constructor as being able
|
@@ -136,6 +394,8 @@ class Money
|
|
136
394
|
# Money.new(50, :currency => "USD")
|
137
395
|
#
|
138
396
|
# We retain compatibility here.
|
397
|
+
Money.deprecate "Passing :currency as option is deprecated and will be removed in v3.5.0. " +
|
398
|
+
"Please use `Money.new('#{cents}'#{currency[:currency].nil? ? "" : ", '#{currency[:currency]}'"})'"
|
139
399
|
@currency = Currency.wrap(currency[:currency] || Money.default_currency)
|
140
400
|
else
|
141
401
|
@currency = Currency.wrap(currency)
|
@@ -143,6 +403,22 @@ class Money
|
|
143
403
|
@bank = bank
|
144
404
|
end
|
145
405
|
|
406
|
+
# Returns the value of the money in dollars,
|
407
|
+
# instead of in cents.
|
408
|
+
#
|
409
|
+
# @return [Float]
|
410
|
+
#
|
411
|
+
# @example
|
412
|
+
# Money.new(100).dollars # => 1.0
|
413
|
+
# Money.new_with_dollars(1).dollar # => 1.0
|
414
|
+
#
|
415
|
+
# @see #to_f
|
416
|
+
# @see #cents
|
417
|
+
#
|
418
|
+
def dollars
|
419
|
+
to_f
|
420
|
+
end
|
421
|
+
|
146
422
|
# Return string representation of currency object
|
147
423
|
#
|
148
424
|
# @return [String]
|
@@ -222,7 +498,7 @@ class Money
|
|
222
498
|
#
|
223
499
|
# @return [-1, 0, 1]
|
224
500
|
#
|
225
|
-
# @raise
|
501
|
+
# @raise [ArgumentError]
|
226
502
|
#
|
227
503
|
# @example
|
228
504
|
# Money.new(100) <=> 99 #=> 1
|
@@ -237,7 +513,7 @@ class Money
|
|
237
513
|
cents <=> other_money.exchange_to(currency).cents
|
238
514
|
end
|
239
515
|
else
|
240
|
-
raise ArgumentError, "
|
516
|
+
raise ArgumentError, "Comparison of #{self.class} with #{other_money.inspect} failed"
|
241
517
|
end
|
242
518
|
end
|
243
519
|
|
@@ -281,51 +557,63 @@ class Money
|
|
281
557
|
# Multiplies the monetary value with the given number and returns a new
|
282
558
|
# +Money+ object with this monetary value and the same currency.
|
283
559
|
#
|
284
|
-
#
|
560
|
+
# Note that you can't multiply a Money object by an other +Money+ object.
|
285
561
|
#
|
286
|
-
# @param [
|
562
|
+
# @param [Numeric] value Number to multiply by.
|
287
563
|
#
|
288
|
-
# @return [Money]
|
564
|
+
# @return [Money] The resulting money.
|
565
|
+
#
|
566
|
+
# @raise [ArgumentError] If +value+ is a Money instance.
|
289
567
|
#
|
290
568
|
# @example
|
291
569
|
# Money.new(100) * 2 #=> #<Money @cents=200>
|
292
|
-
|
293
|
-
|
570
|
+
#
|
571
|
+
def *(value)
|
572
|
+
if value.is_a?(Money)
|
573
|
+
raise ArgumentError, "Can't multiply a Money by a Money"
|
574
|
+
else
|
575
|
+
Money.new(cents * value, currency)
|
576
|
+
end
|
294
577
|
end
|
295
578
|
|
296
579
|
# Divides the monetary value with the given number and returns a new +Money+
|
297
|
-
# object with this monetary value and the same currency.
|
298
|
-
# another +Money+ object to get a ratio.
|
299
|
-
#
|
580
|
+
# object with this monetary value and the same currency.
|
581
|
+
# Can also divide by another +Money+ object to get a ratio.
|
582
|
+
#
|
583
|
+
# +Money/Numeric+ returns +Money+. +Money/Money+ returns +Float+.
|
300
584
|
#
|
301
|
-
# @param [Money, Numeric]
|
585
|
+
# @param [Money, Numeric] value Number to divide by.
|
302
586
|
#
|
303
|
-
# @return [Money
|
587
|
+
# @return [Money] The resulting money if you divide Money by a number.
|
588
|
+
# @return [Float] The resulting number if you divide Money by a Money.
|
304
589
|
#
|
305
590
|
# @example
|
306
591
|
# Money.new(100) / 10 #=> #<Money @cents=10>
|
307
592
|
# Money.new(100) / Money.new(10) #=> 10.0
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
593
|
+
#
|
594
|
+
def /(value)
|
595
|
+
if value.is_a?(Money)
|
596
|
+
if currency == value.currency
|
597
|
+
(cents / BigDecimal.new(value.cents.to_s)).to_f
|
312
598
|
else
|
313
|
-
cents /
|
599
|
+
(cents / BigDecimal(value.exchange_to(currency).cents.to_s)).to_f
|
314
600
|
end
|
315
601
|
else
|
316
|
-
Money.new(cents /
|
602
|
+
Money.new(cents / value, currency)
|
317
603
|
end
|
318
604
|
end
|
319
605
|
|
320
606
|
# Synonym for +#/+.
|
321
607
|
#
|
322
|
-
# @param [Money, Numeric]
|
608
|
+
# @param [Money, Numeric] value Number to divide by.
|
323
609
|
#
|
324
|
-
# @return [Money
|
610
|
+
# @return [Money] The resulting money if you divide Money by a number.
|
611
|
+
# @return [Float] The resulting number if you divide Money by a Money.
|
325
612
|
#
|
326
613
|
# @see #/
|
327
|
-
|
328
|
-
|
614
|
+
#
|
615
|
+
def div(value)
|
616
|
+
self / value
|
329
617
|
end
|
330
618
|
|
331
619
|
# Divide money by money or fixnum and return array containing quotient and
|
@@ -569,9 +857,9 @@ class Money
|
|
569
857
|
end
|
570
858
|
|
571
859
|
if rules[:no_cents] or currency.subunit_to_unit == 1
|
572
|
-
formatted = sprintf("#{symbol_value}%d",
|
860
|
+
formatted = sprintf("#{symbol_value}%d", self.to_f)
|
573
861
|
else
|
574
|
-
formatted = sprintf("#{symbol_value}%.2f",
|
862
|
+
formatted = sprintf("#{symbol_value}%.2f", self.to_f)
|
575
863
|
end
|
576
864
|
|
577
865
|
delimiter_value = delimiter
|
@@ -585,7 +873,7 @@ class Money
|
|
585
873
|
end
|
586
874
|
|
587
875
|
# Apply delimiter
|
588
|
-
formatted.gsub!(/(\d)(
|
876
|
+
formatted.gsub!(/(\d)(?=(?:\d{3})+(?:\.|$))(\d{3}\..*)?/, "\\1#{delimiter_value}\\2")
|
589
877
|
|
590
878
|
separator_value = separator
|
591
879
|
# Determine separator
|
@@ -613,7 +901,7 @@ class Money
|
|
613
901
|
# Money.ca_dollar(100).to_s #=> "1.00"
|
614
902
|
def to_s
|
615
903
|
return sprintf("%d", cents) if currency.subunit_to_unit == 1
|
616
|
-
sprintf("%.2f",
|
904
|
+
sprintf("%.2f", self.to_f)
|
617
905
|
end
|
618
906
|
|
619
907
|
# Return the amount of money as a float. Floating points cannot guarantee
|
@@ -626,7 +914,7 @@ class Money
|
|
626
914
|
# @example
|
627
915
|
# Money.us_dollar(100).to_f => 1.0
|
628
916
|
def to_f
|
629
|
-
cents.
|
917
|
+
(BigDecimal.new(cents.to_s) / currency.subunit_to_unit).to_f
|
630
918
|
end
|
631
919
|
|
632
920
|
# Receive the amount of this money object in another Currency.
|
@@ -694,10 +982,14 @@ class Money
|
|
694
982
|
#
|
695
983
|
# @return [Hash]
|
696
984
|
def normalize_formatting_rules(rules)
|
697
|
-
if rules.size ==
|
985
|
+
if rules.size == 0
|
986
|
+
rules = {}
|
987
|
+
elsif rules.size == 1
|
698
988
|
rules = rules.pop
|
699
989
|
rules = { rules => true } if rules.is_a?(Symbol)
|
700
990
|
else
|
991
|
+
Money.deprecate "Passing options as parameters is deprecated and will be removed in v3.5.0. " +
|
992
|
+
"Please use `#format(#{rules.map { |r| ":#{r} => true" }.join(", ") })'"
|
701
993
|
rules = rules.inject({}) do |h,s|
|
702
994
|
h[s] = true
|
703
995
|
h
|
@@ -705,4 +997,118 @@ class Money
|
|
705
997
|
end
|
706
998
|
rules
|
707
999
|
end
|
1000
|
+
|
1001
|
+
# Takes a number string and attempts to massage out the number.
|
1002
|
+
#
|
1003
|
+
# @param [String] input The string containing a potential number.
|
1004
|
+
#
|
1005
|
+
# @return [Integer]
|
1006
|
+
#
|
1007
|
+
def self.extract_cents(input, currency = Money.default_currency)
|
1008
|
+
# remove anything that's not a number, potential delimiter, or minus sign
|
1009
|
+
num = input.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
|
1010
|
+
|
1011
|
+
# set a boolean flag for if the number is negative or not
|
1012
|
+
negative = num.split(//).first == "-"
|
1013
|
+
|
1014
|
+
# if negative, remove the minus sign from the number
|
1015
|
+
num = num.gsub(/^-/, '') if negative
|
1016
|
+
|
1017
|
+
# gather all separators within the result number
|
1018
|
+
used_separators = num.scan /[^\d]/
|
1019
|
+
|
1020
|
+
# determine the number of unique separators within the number
|
1021
|
+
#
|
1022
|
+
# e.g.
|
1023
|
+
# $1,234,567.89 would return 2 (, and .)
|
1024
|
+
# $125,00 would return 1
|
1025
|
+
# $199 would return 0
|
1026
|
+
# $1 234,567.89 would raise an error (separators are space, comma, and period)
|
1027
|
+
case used_separators.uniq.length
|
1028
|
+
# no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
|
1029
|
+
when 0 then major, minor = num, 0
|
1030
|
+
|
1031
|
+
# two separators, so we know the last item in this array is the
|
1032
|
+
# major/minor delimiter and the rest are separators
|
1033
|
+
when 2
|
1034
|
+
separator, delimiter = used_separators.uniq
|
1035
|
+
# remove all separators, split on the delimiter
|
1036
|
+
major, minor = num.gsub(separator, '').split(delimiter)
|
1037
|
+
min = 0 unless min
|
1038
|
+
when 1
|
1039
|
+
# we can't determine if the comma or period is supposed to be a separator or a delimiter
|
1040
|
+
# e.g.
|
1041
|
+
# 1,00 - comma is a delimiter
|
1042
|
+
# 1.000 - period is a delimiter
|
1043
|
+
# 1,000 - comma is a separator
|
1044
|
+
# 1,000,000 - comma is a separator
|
1045
|
+
# 10000,00 - comma is a delimiter
|
1046
|
+
# 1000,000 - comma is a delimiter
|
1047
|
+
|
1048
|
+
# assign first separator for reusability
|
1049
|
+
separator = used_separators.first
|
1050
|
+
|
1051
|
+
# separator is used as a separator when there are multiple instances, always
|
1052
|
+
if num.scan(separator).length > 1 # multiple matches; treat as separator
|
1053
|
+
major, minor = num.gsub(separator, ''), 0
|
1054
|
+
else
|
1055
|
+
# ex: 1,000 - 1.0000 - 10001.000
|
1056
|
+
# split number into possible major (dollars) and minor (cents) values
|
1057
|
+
possible_major, possible_minor = num.split(separator)
|
1058
|
+
possible_major ||= "0"
|
1059
|
+
possible_minor ||= "00"
|
1060
|
+
|
1061
|
+
# if the minor (cents) length isn't 3, assign major/minor from the possibles
|
1062
|
+
# e.g.
|
1063
|
+
# 1,00 => 1.00
|
1064
|
+
# 1.0000 => 1.00
|
1065
|
+
# 1.2 => 1.20
|
1066
|
+
if possible_minor.length != 3 # delimiter
|
1067
|
+
major, minor = possible_major, possible_minor
|
1068
|
+
else
|
1069
|
+
# minor length is three
|
1070
|
+
# let's try to figure out intent of the delimiter
|
1071
|
+
|
1072
|
+
# the major length is greater than three, which means
|
1073
|
+
# the comma or period is used as a delimiter
|
1074
|
+
# e.g.
|
1075
|
+
# 1000,000
|
1076
|
+
# 100000,000
|
1077
|
+
if possible_major.length > 3
|
1078
|
+
major, minor = possible_major, possible_minor
|
1079
|
+
else
|
1080
|
+
# number is in format ###{sep}### or ##{sep}### or #{sep}###
|
1081
|
+
# handle as , is sep, . is delimiter
|
1082
|
+
if separator == '.'
|
1083
|
+
major, minor = possible_major, possible_minor
|
1084
|
+
else
|
1085
|
+
major, minor = "#{possible_major}#{possible_minor}", 0
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
end
|
1090
|
+
else
|
1091
|
+
# TODO: ParseError
|
1092
|
+
raise ArgumentError, "Invalid currency amount"
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
# build the string based on major/minor since separator/delimiters have been removed
|
1096
|
+
# avoiding floating point arithmetic here to ensure accuracy
|
1097
|
+
cents = (major.to_i * currency.subunit_to_unit)
|
1098
|
+
# add the minor number as well. this may have any number of digits,
|
1099
|
+
# so we treat minor as a string and truncate or right-fill it with zeroes
|
1100
|
+
# until it becomes a two-digit number string, which we add to cents.
|
1101
|
+
minor = minor.to_s
|
1102
|
+
truncated_minor = minor[0..1]
|
1103
|
+
truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
|
1104
|
+
cents += truncated_minor.to_i
|
1105
|
+
# respect rounding rules
|
1106
|
+
if minor.size >= 3 && minor[2..2].to_i >= 5
|
1107
|
+
cents += 1
|
1108
|
+
end
|
1109
|
+
|
1110
|
+
# if negative, multiply by -1; otherwise, return positive cents
|
1111
|
+
negative ? cents * -1 : cents
|
1112
|
+
end
|
1113
|
+
|
708
1114
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: money
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 9
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 3
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 3.1.
|
9
|
+
- 5
|
10
|
+
version: 3.1.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Tobias Luetke
|
@@ -19,7 +19,7 @@ autorequire:
|
|
19
19
|
bindir: bin
|
20
20
|
cert_chain: []
|
21
21
|
|
22
|
-
date: 2010-
|
22
|
+
date: 2010-10-08 00:00:00 -04:00
|
23
23
|
default_executable:
|
24
24
|
dependencies:
|
25
25
|
- !ruby/object:Gem::Dependency
|
@@ -109,12 +109,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
109
|
requirements:
|
110
110
|
- - ">="
|
111
111
|
- !ruby/object:Gem::Version
|
112
|
-
hash:
|
112
|
+
hash: 23
|
113
113
|
segments:
|
114
114
|
- 1
|
115
115
|
- 3
|
116
|
-
-
|
117
|
-
version: 1.3.
|
116
|
+
- 6
|
117
|
+
version: 1.3.6
|
118
118
|
requirements:
|
119
119
|
- json if you plan to import/export rates formatted as json
|
120
120
|
rubyforge_project: money
|