money-joshm1 5.1.2

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