money 3.6.1 → 3.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,246 @@
1
+ class Money
2
+ module Arithmetic
3
+
4
+ # Checks whether two money objects have the same currency and the same
5
+ # amount. Checks against money objects with a different currency and checks
6
+ # against objects that do not respond to #to_money will always return false.
7
+ #
8
+ # @param [Money] other_money Value to compare with.
9
+ #
10
+ # @return [Boolean]
11
+ #
12
+ # @example
13
+ # Money.new(100) == Money.new(101) #=> false
14
+ # Money.new(100) == Money.new(100) #=> true
15
+ def ==(other_money)
16
+ if other_money.respond_to?(:to_money)
17
+ other_money = other_money.to_money
18
+ cents == other_money.cents && self.currency == other_money.currency
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ # Synonymous with +#==+.
25
+ #
26
+ # @param [Money] other_money Value to compare with.
27
+ #
28
+ # @return [Money]
29
+ #
30
+ # @see #==
31
+ def eql?(other_money)
32
+ self == other_money
33
+ end
34
+
35
+ def <=>(other_money)
36
+ if other_money.respond_to?(:to_money)
37
+ other_money = other_money.to_money
38
+ if self.currency == other_money.currency
39
+ cents <=> other_money.cents
40
+ else
41
+ cents <=> other_money.exchange_to(currency).cents
42
+ end
43
+ else
44
+ raise ArgumentError, "Comparison of #{self.class} with #{other_money.inspect} failed"
45
+ end
46
+ end
47
+
48
+ # Returns a new Money object containing the sum of the two operands' monetary
49
+ # values. If +other_money+ has a different currency then its monetary value
50
+ # is automatically exchanged to this object's currency using +exchange_to+.
51
+ #
52
+ # @param [Money] other_money Other +Money+ object to add.
53
+ #
54
+ # @return [Money]
55
+ #
56
+ # @example
57
+ # Money.new(100) + Money.new(100) #=> #<Money @cents=200>
58
+ def +(other_money)
59
+ if currency == other_money.currency
60
+ Money.new(cents + other_money.cents, other_money.currency)
61
+ else
62
+ Money.new(cents + other_money.exchange_to(currency).cents, currency)
63
+ end
64
+ end
65
+
66
+ # Returns a new Money object containing the difference between the two
67
+ # operands' monetary values. If +other_money+ has a different currency then
68
+ # its monetary value is automatically exchanged to this object's currency
69
+ # using +exchange_to+.
70
+ #
71
+ # @param [Money] other_money Other +Money+ object to subtract.
72
+ #
73
+ # @return [Money]
74
+ #
75
+ # @example
76
+ # Money.new(100) - Money.new(99) #=> #<Money @cents=1>
77
+ def -(other_money)
78
+ if currency == other_money.currency
79
+ Money.new(cents - other_money.cents, other_money.currency)
80
+ else
81
+ Money.new(cents - other_money.exchange_to(currency).cents, currency)
82
+ end
83
+ end
84
+
85
+ # Multiplies the monetary value with the given number and returns a new
86
+ # +Money+ object with this monetary value and the same currency.
87
+ #
88
+ # Note that you can't multiply a Money object by an other +Money+ object.
89
+ #
90
+ # @param [Numeric] value Number to multiply by.
91
+ #
92
+ # @return [Money] The resulting money.
93
+ #
94
+ # @raise [ArgumentError] If +value+ is a Money instance.
95
+ #
96
+ # @example
97
+ # Money.new(100) * 2 #=> #<Money @cents=200>
98
+ #
99
+ def *(value)
100
+ if value.is_a?(Money)
101
+ raise ArgumentError, "Can't multiply a Money by a Money"
102
+ else
103
+ Money.new(cents * value, currency)
104
+ end
105
+ end
106
+
107
+ # Divides the monetary value with the given number and returns a new +Money+
108
+ # object with this monetary value and the same currency.
109
+ # Can also divide by another +Money+ object to get a ratio.
110
+ #
111
+ # +Money/Numeric+ returns +Money+. +Money/Money+ returns +Float+.
112
+ #
113
+ # @param [Money, Numeric] value Number to divide by.
114
+ #
115
+ # @return [Money] The resulting money if you divide Money by a number.
116
+ # @return [Float] The resulting number if you divide Money by a Money.
117
+ #
118
+ # @example
119
+ # Money.new(100) / 10 #=> #<Money @cents=10>
120
+ # Money.new(100) / Money.new(10) #=> 10.0
121
+ #
122
+ def /(value)
123
+ if value.is_a?(Money)
124
+ if currency == value.currency
125
+ (cents / BigDecimal.new(value.cents.to_s)).to_f
126
+ else
127
+ (cents / BigDecimal(value.exchange_to(currency).cents.to_s)).to_f
128
+ end
129
+ else
130
+ Money.new(cents / value, currency)
131
+ end
132
+ end
133
+
134
+ # Synonym for +#/+.
135
+ #
136
+ # @param [Money, Numeric] value Number to divide by.
137
+ #
138
+ # @return [Money] The resulting money if you divide Money by a number.
139
+ # @return [Float] The resulting number if you divide Money by a Money.
140
+ #
141
+ # @see #/
142
+ #
143
+ def div(value)
144
+ self / value
145
+ end
146
+
147
+ # Divide money by money or fixnum and return array containing quotient and
148
+ # modulus.
149
+ #
150
+ # @param [Money, Fixnum] val Number to divmod by.
151
+ #
152
+ # @return [Array<Money,Money>,Array<Fixnum,Money>]
153
+ #
154
+ # @example
155
+ # Money.new(100).divmod(9) #=> [#<Money @cents=11>, #<Money @cents=1>]
156
+ # Money.new(100).divmod(Money.new(9)) #=> [11, #<Money @cents=1>]
157
+ def divmod(val)
158
+ if val.is_a?(Money)
159
+ a = self.cents
160
+ b = self.currency == val.currency ? val.cents : val.exchange_to(self.currency).cents
161
+ q, m = a.divmod(b)
162
+ return [q, Money.new(m, self.currency)]
163
+ else
164
+ return [self.div(val), Money.new(self.cents.modulo(val), self.currency)]
165
+ end
166
+ end
167
+
168
+ # Equivalent to +self.divmod(val)[1]+
169
+ #
170
+ # @param [Money, Fixnum] val Number take modulo with.
171
+ #
172
+ # @return [Money]
173
+ #
174
+ # @example
175
+ # Money.new(100).modulo(9) #=> #<Money @cents=1>
176
+ # Money.new(100).modulo(Money.new(9)) #=> #<Money @cents=1>
177
+ def modulo(val)
178
+ self.divmod(val)[1]
179
+ end
180
+
181
+ # Synonym for +#modulo+.
182
+ #
183
+ # @param [Money, Fixnum] val Number take modulo with.
184
+ #
185
+ # @return [Money]
186
+ #
187
+ # @see #modulo
188
+ def %(val)
189
+ self.modulo(val)
190
+ end
191
+
192
+ # If different signs +self.modulo(val) - val+ otherwise +self.modulo(val)+
193
+ #
194
+ # @param [Money, Fixnum] val Number to rake remainder with.
195
+ #
196
+ # @return [Money]
197
+ #
198
+ # @example
199
+ # Money.new(100).remainder(9) #=> #<Money @cents=1>
200
+ def remainder(val)
201
+ a, b = self, val
202
+ b = b.exchange_to(a.currency) if b.is_a?(Money) and a.currency != b.currency
203
+
204
+ a_sign, b_sign = :pos, :pos
205
+ a_sign = :neg if a.cents < 0
206
+ b_sign = :neg if (b.is_a?(Money) and b.cents < 0) or (b < 0)
207
+
208
+ return a.modulo(b) if a_sign == b_sign
209
+ a.modulo(b) - (b.is_a?(Money) ? b : Money.new(b, a.currency))
210
+ end
211
+
212
+ # Return absolute value of self as a new Money object.
213
+ #
214
+ # @return [Money]
215
+ #
216
+ # @example
217
+ # Money.new(-100).abs #=> #<Money @cents=100>
218
+ def abs
219
+ Money.new(self.cents.abs, self.currency)
220
+ end
221
+
222
+ # Test if the money amount is zero.
223
+ #
224
+ # @return [Boolean]
225
+ #
226
+ # @example
227
+ # Money.new(100).zero? #=> false
228
+ # Money.new(0).zero? #=> true
229
+ def zero?
230
+ cents == 0
231
+ end
232
+
233
+ # Test if the money amount is non-zero. Returns this money object if it is
234
+ # non-zero, or nil otherwise, like +Numeric#nonzero?+.
235
+ #
236
+ # @return [Money, nil]
237
+ #
238
+ # @example
239
+ # Money.new(100).nonzero? #=> #<Money @cents=100>
240
+ # Money.new(0).nonzero? #=> nil
241
+ def nonzero?
242
+ cents != 0 ? self : nil
243
+ end
244
+
245
+ end
246
+ end
@@ -0,0 +1,234 @@
1
+ class Money
2
+ module Formatting
3
+ if Object.const_defined?("I18n")
4
+ def thousands_separator
5
+ I18n.t(
6
+ :"number.currency.format.delimiter",
7
+ :default => I18n.t(
8
+ :"number.format.delimiter",
9
+ :default => (currency.thousands_separator || ",")
10
+ )
11
+ )
12
+ end
13
+ else
14
+ def thousands_separator
15
+ currency.thousands_separator || ","
16
+ end
17
+ end
18
+ alias :delimiter :thousands_separator
19
+
20
+ if Object.const_defined?("I18n")
21
+ def decimal_mark
22
+ I18n.t(
23
+ :"number.currency.format.separator",
24
+ :default => I18n.t(
25
+ :"number.format.separator",
26
+ :default => (currency.decimal_mark || ".")
27
+ )
28
+ )
29
+ end
30
+ else
31
+ def decimal_mark
32
+ currency.decimal_mark || "."
33
+ end
34
+ end
35
+ alias :separator :decimal_mark
36
+
37
+ # Creates a formatted price string according to several rules.
38
+ #
39
+ # @param [Hash] *rules The options used to format the string.
40
+ #
41
+ # @return [String]
42
+ #
43
+ # @option *rules [Boolean, String] :display_free (false) Whether a zero
44
+ # amount of money should be formatted of "free" or as the supplied string.
45
+ #
46
+ # @example
47
+ # Money.us_dollar(0).format(:display_free => true) #=> "free"
48
+ # Money.us_dollar(0).format(:display_free => "gratis") #=> "gratis"
49
+ # Money.us_dollar(0).format #=> "$0.00"
50
+ #
51
+ # @option *rules [Boolean] :with_currency (false) Whether the currency name
52
+ # should be appended to the result string.
53
+ #
54
+ # @example
55
+ # Money.ca_dollar(100).format => "$1.00"
56
+ # Money.ca_dollar(100).format(:with_currency => true) #=> "$1.00 CAD"
57
+ # Money.us_dollar(85).format(:with_currency => true) #=> "$0.85 USD"
58
+ #
59
+ # @option *rules [Boolean] :no_cents (false) Whether cents should be omitted.
60
+ #
61
+ # @example
62
+ # Money.ca_dollar(100).format(:no_cents => true) #=> "$1"
63
+ # Money.ca_dollar(599).format(:no_cents => true) #=> "$5"
64
+ #
65
+ # @option *rules [Boolean] :no_cents_if_whole (false) Whether cents should be
66
+ # omitted if the cent value is zero
67
+ #
68
+ # @example
69
+ # Money.ca_dollar(10000).format(:no_cents_if_whole => true) #=> "$100"
70
+ # Money.ca_dollar(10034).format(:no_cents_if_whole => true) #=> "$100.34"
71
+ #
72
+ # @option *rules [Boolean, String, nil] :symbol (true) Whether a money symbol
73
+ # should be prepended to the result string. The default is true. This method
74
+ # attempts to pick a symbol that's suitable for the given currency.
75
+ #
76
+ # @example
77
+ # Money.new(100, "USD") #=> "$1.00"
78
+ # Money.new(100, "GBP") #=> "£1.00"
79
+ # Money.new(100, "EUR") #=> "€1.00"
80
+ #
81
+ # # Same thing.
82
+ # Money.new(100, "USD").format(:symbol => true) #=> "$1.00"
83
+ # Money.new(100, "GBP").format(:symbol => true) #=> "£1.00"
84
+ # Money.new(100, "EUR").format(:symbol => true) #=> "€1.00"
85
+ #
86
+ # # You can specify a false expression or an empty string to disable
87
+ # # prepending a money symbol.§
88
+ # Money.new(100, "USD").format(:symbol => false) #=> "1.00"
89
+ # Money.new(100, "GBP").format(:symbol => nil) #=> "1.00"
90
+ # Money.new(100, "EUR").format(:symbol => "") #=> "1.00"
91
+ #
92
+ # # If the symbol for the given currency isn't known, then it will default
93
+ # # to "¤" as symbol.
94
+ # Money.new(100, "AWG").format(:symbol => true) #=> "¤1.00"
95
+ #
96
+ # # You can specify a string as value to enforce using a particular symbol.
97
+ # Money.new(100, "AWG").format(:symbol => "ƒ") #=> "ƒ1.00"
98
+ #
99
+ # @option *rules [Boolean, String, nil] :decimal_mark (true) Whether the
100
+ # currency should be separated by the specified character or '.'
101
+ #
102
+ # @example
103
+ # # If a string is specified, it's value is used.
104
+ # Money.new(100, "USD").format(:decimal_mark => ",") #=> "$1,00"
105
+ #
106
+ # # If the decimal_mark for a given currency isn't known, then it will default
107
+ # # to "." as decimal_mark.
108
+ # Money.new(100, "FOO").format #=> "$1.00"
109
+ #
110
+ # @option *rules [Boolean, String, nil] :thousands_separator (true) Whether
111
+ # the currency should be delimited by the specified character or ','
112
+ #
113
+ # @example
114
+ # # If false is specified, no thousands_separator is used.
115
+ # Money.new(100000, "USD").format(:thousands_separator => false) #=> "1000.00"
116
+ # Money.new(100000, "USD").format(:thousands_separator => nil) #=> "1000.00"
117
+ # Money.new(100000, "USD").format(:thousands_separator => "") #=> "1000.00"
118
+ #
119
+ # # If a string is specified, it's value is used.
120
+ # Money.new(100000, "USD").format(:thousands_separator => ".") #=> "$1.000.00"
121
+ #
122
+ # # If the thousands_separator for a given currency isn't known, then it will
123
+ # # default to "," as thousands_separator.
124
+ # Money.new(100000, "FOO").format #=> "$1,000.00"
125
+ #
126
+ # @option *rules [Boolean] :html (false) Whether the currency should be
127
+ # HTML-formatted. Only useful in combination with +:with_currency+.
128
+ #
129
+ # @example
130
+ # s = Money.ca_dollar(570).format(:html => true, :with_currency => true)
131
+ # s #=> "$5.70 <span class=\"currency\">CAD</span>"
132
+ def format(*rules)
133
+ # support for old format parameters
134
+ rules = normalize_formatting_rules(rules)
135
+
136
+ if cents == 0
137
+ if rules[:display_free].respond_to?(:to_str)
138
+ return rules[:display_free]
139
+ elsif rules[:display_free]
140
+ return "free"
141
+ end
142
+ end
143
+
144
+ symbol_value =
145
+ if rules.has_key?(:symbol)
146
+ if rules[:symbol] === true
147
+ symbol
148
+ elsif rules[:symbol]
149
+ rules[:symbol]
150
+ else
151
+ ""
152
+ end
153
+ elsif rules[:html]
154
+ currency.html_entity
155
+ else
156
+ symbol
157
+ end
158
+
159
+ formatted = case rules[:no_cents]
160
+ when true
161
+ "#{self.to_s.to_i}"
162
+ else
163
+ "#{self.to_s}"
164
+ end
165
+
166
+ if rules[:no_cents_if_whole] && cents % currency.subunit_to_unit == 0
167
+ formatted = "#{self.to_s.to_i}"
168
+ end
169
+
170
+ symbol_position =
171
+ if rules.has_key?(:symbol_position)
172
+ rules[:symbol_position]
173
+ elsif currency.symbol_first?
174
+ :before
175
+ else
176
+ :after
177
+ end
178
+
179
+ if symbol_value && !symbol_value.empty?
180
+ formatted = (symbol_position == :before ? "#{symbol_value}#{formatted}" : "#{formatted} #{symbol_value}")
181
+ end
182
+
183
+ if rules.has_key?(:decimal_mark) and rules[:decimal_mark] and
184
+ rules[:decimal_mark] != decimal_mark
185
+ formatted.sub!(decimal_mark, rules[:decimal_mark])
186
+ end
187
+
188
+ thousands_separator_value = thousands_separator
189
+ # Determine thousands_separator
190
+ if rules.has_key?(:thousands_separator)
191
+ if rules[:thousands_separator] === false or rules[:thousands_separator].nil?
192
+ thousands_separator_value = ""
193
+ elsif rules[:thousands_separator]
194
+ thousands_separator_value = rules[:thousands_separator]
195
+ end
196
+ end
197
+
198
+ # Apply thousands_separator
199
+ formatted.gsub!(/(\d)(?=(?:\d{3})+(?:[^\d]|$))/, "\\1#{thousands_separator_value}")
200
+
201
+ if rules[:with_currency]
202
+ formatted << " "
203
+ formatted << '<span class="currency">' if rules[:html]
204
+ formatted << currency.to_s
205
+ formatted << '</span>' if rules[:html]
206
+ end
207
+ formatted
208
+ end
209
+
210
+
211
+ private
212
+
213
+ # Cleans up formatting rules.
214
+ #
215
+ # @param [Hash]
216
+ #
217
+ # @return [Hash]
218
+ def normalize_formatting_rules(rules)
219
+ if rules.size == 0
220
+ rules = {}
221
+ elsif rules.size == 1
222
+ rules = rules.pop
223
+ rules = { rules => true } if rules.is_a?(Symbol)
224
+ end
225
+ if not rules.include?(:decimal_mark) and rules.include?(:separator)
226
+ rules[:decimal_mark] = rules[:separator]
227
+ end
228
+ if not rules.include?(:thousands_separator) and rules.include?(:delimiter)
229
+ rules[:thousands_separator] = rules[:delimiter]
230
+ end
231
+ rules
232
+ end
233
+ end
234
+ end