money 3.6.1 → 3.6.2

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,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