money 2.1.5 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
@@ -1,21 +1,21 @@
1
- Copyright (c) 2005 Tobias Lutke
2
- Copyright (c) 2008 Phusion
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining
5
- a copy of this software and associated documentation files (the
6
- "Software"), to deal in the Software without restriction, including
7
- without limitation the rights to use, copy, modify, merge, publish,
8
- distribute, sublicense, and/or sell copies of the Software, and to
9
- permit persons to whom the Software is furnished to do so, subject to
10
- the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be
13
- included in all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2005 Tobias Lutke
2
+ Copyright (c) 2008 Phusion
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,97 +1,97 @@
1
- = Introduction
2
-
3
- This library aids one in handling money and different currencies. Features:
4
-
5
- - Provides a Money class which encapsulates all information about an certain
6
- amount of money, such as its value and its currency.
7
- - Represents monetary values as integers, in cents. This avoids floating point
8
- rounding errors.
9
- - Provides APIs for exchanging money from one currency to another.
10
- - Has the ability to parse a money string into a Money object.
11
-
12
- Resources:
13
-
14
- - Website: http://money.rubyforge.org
15
- - RDoc API: http://money.rubyforge.org
16
- - Git repository: http://github.com/FooBarWidget/money/tree/master
17
-
18
- == Download
19
-
20
- Install stable releases with the following command:
21
-
22
- gem install money
23
-
24
- The development version (hosted on Github) can be installed with:
25
-
26
- gem sources -a http://gems.github.com
27
- gem install FooBarWidget-money
28
-
29
- == Usage
30
-
31
- === Synopsis
32
-
33
- require 'money'
34
-
35
- # 10.00 USD
36
- money = Money.new(1000, "USD")
37
- money.cents # => 1000
38
- money.currency # => "USD"
39
-
40
- Money.new(1000, "USD") == Money.new(1000, "USD") # => true
41
- Money.new(1000, "USD") == Money.new(100, "USD") # => false
42
- Money.new(1000, "USD") == Money.new(1000, "EUR") # => false
43
-
44
- === Currency Exchange
45
-
46
- Exchanging money is performed through an exchange bank object. The default
47
- exchange bank object requires one to manually specify the exchange rate. Here's
48
- an example of how it works:
49
-
50
- Money.add_rate("USD", "CAD", 1.24515)
51
- Money.add_rate("CAD", "USD", 0.803115)
52
-
53
- Money.us_dollar(100).exchange_to("CAD") # => Money.new(124, "CAD")
54
- Money.ca_dollar(100).exchange_to("USD") # => Money.new(80, "USD")
55
-
56
- Comparison and arithmetic operations work as expected:
57
-
58
- Money.new(1000, "USD") <=> Money.new(900, "USD") # => 1; 9.00 USD is smaller
59
- Money.new(1000, "EUR") + Money.new(10, "EUR") == Money.new(1010, "EUR")
60
-
61
- Money.add_rate("USD", "EUR", 0.5)
62
- Money.new(1000, "EUR") + Money.new(1000, "USD") == Money.new(1500, "EUR")
63
-
64
- There is nothing stopping you from creating bank objects which scrapes
65
- www.xe.com for the current rates or just returns <tt>rand(2)</tt>:
66
-
67
- Money.default_bank = ExchangeBankWhichScrapesXeDotCom.new
68
-
69
- === Ruby on Rails
70
-
71
- Use the +compose_of+ helper to let Active Record deal with embedding the money
72
- object in your models. The following example requires a +cents+ and a +currency+
73
- field.
74
-
75
- class ProductUnit < ActiveRecord::Base
76
- belongs_to :product
77
- composed_of :price, :class_name => "Money", :mapping => [%w(cents cents), %w(currency currency)]
78
-
79
- private
80
- validate :cents_not_zero
81
-
82
- def cents_not_zero
83
- errors.add("cents", "cannot be zero or less") unless cents > 0
84
- end
85
-
86
- validates_presence_of :sku, :currency
87
- validates_uniqueness_of :sku
88
- end
89
-
90
- === Default Currency
91
-
92
- By default Money defaults to USD as its currency. This can be overwritten using
93
-
94
- Money.default_currency = "CAD"
95
-
96
- If you use Rails, then environment.rb is a very good place to put this.
97
-
1
+ = Introduction
2
+
3
+ This library aids one in handling money and different currencies. Features:
4
+
5
+ - Provides a Money class which encapsulates all information about an certain
6
+ amount of money, such as its value and its currency.
7
+ - Represents monetary values as integers, in cents. This avoids floating point
8
+ rounding errors.
9
+ - Provides APIs for exchanging money from one currency to another.
10
+ - Has the ability to parse a money string into a Money object.
11
+
12
+ Resources:
13
+
14
+ - Website: http://money.rubyforge.org
15
+ - RDoc API: http://money.rubyforge.org
16
+ - Git repository: http://github.com/FooBarWidget/money
17
+
18
+ == Download
19
+
20
+ Install stable releases with the following command:
21
+
22
+ gem install money
23
+
24
+ The development version (hosted on Github) can be installed with:
25
+
26
+ git clone git://github.com/FooBarWidget/money.git
27
+ cd money
28
+ rake install
29
+
30
+ == Usage
31
+
32
+ === Synopsis
33
+
34
+ require 'money'
35
+
36
+ # 10.00 USD
37
+ money = Money.new(1000, "USD")
38
+ money.cents # => 1000
39
+ money.currency # => "USD"
40
+
41
+ Money.new(1000, "USD") == Money.new(1000, "USD") # => true
42
+ Money.new(1000, "USD") == Money.new(100, "USD") # => false
43
+ Money.new(1000, "USD") == Money.new(1000, "EUR") # => false
44
+
45
+ === Currency Exchange
46
+
47
+ Exchanging money is performed through an exchange bank object. The default
48
+ exchange bank object requires one to manually specify the exchange rate. Here's
49
+ an example of how it works:
50
+
51
+ Money.add_rate("USD", "CAD", 1.24515)
52
+ Money.add_rate("CAD", "USD", 0.803115)
53
+
54
+ Money.us_dollar(100).exchange_to("CAD") # => Money.new(124, "CAD")
55
+ Money.ca_dollar(100).exchange_to("USD") # => Money.new(80, "USD")
56
+
57
+ Comparison and arithmetic operations work as expected:
58
+
59
+ Money.new(1000, "USD") <=> Money.new(900, "USD") # => 1; 9.00 USD is smaller
60
+ Money.new(1000, "EUR") + Money.new(10, "EUR") == Money.new(1010, "EUR")
61
+
62
+ Money.add_rate("USD", "EUR", 0.5)
63
+ Money.new(1000, "EUR") + Money.new(1000, "USD") == Money.new(1500, "EUR")
64
+
65
+ There is nothing stopping you from creating bank objects which scrapes
66
+ www.xe.com for the current rates or just returns <tt>rand(2)</tt>:
67
+
68
+ Money.default_bank = ExchangeBankWhichScrapesXeDotCom.new
69
+
70
+ === Ruby on Rails
71
+
72
+ Use the +compose_of+ helper to let Active Record deal with embedding the money
73
+ object in your models. The following example requires a +cents+ and a +currency+
74
+ field.
75
+
76
+ class ProductUnit < ActiveRecord::Base
77
+ belongs_to :product
78
+ composed_of :price, :class_name => "Money", :mapping => [%w(cents cents), %w(currency currency)]
79
+
80
+ private
81
+ validate :cents_not_zero
82
+
83
+ def cents_not_zero
84
+ errors.add("cents", "cannot be zero or less") unless cents > 0
85
+ end
86
+
87
+ validates_presence_of :sku, :currency
88
+ validates_uniqueness_of :sku
89
+ end
90
+
91
+ === Default Currency
92
+
93
+ By default Money defaults to USD as its currency. This can be overwritten using
94
+
95
+ Money.default_currency = "CAD"
96
+
97
+ If you use Rails, then environment.rb is a very good place to put this.
data/Rakefile CHANGED
@@ -1,18 +1,44 @@
1
- desc "Build a gem"
2
- task :gem do
3
- sh "gem build money.gemspec"
4
- end
5
-
6
- task "Generate RDoc documentation"
7
- task :rdoc do
8
- sh "hanna README.rdoc lib -U"
9
- end
10
-
11
- task :upload => :rdoc do
12
- sh "scp -r doc/* rubyforge.org:/var/www/gforge-projects/money/"
13
- end
14
-
15
- desc "Run unit tests"
16
- task :test do
17
- ruby "-S spec -f s -c test/*_spec.rb"
18
- end
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "money"
8
+ gem.summary = "Money and currency exchange support library"
9
+ gem.description = "Money and currency exchange support library."
10
+ gem.email = "hongli@phusion.nl"
11
+ gem.homepage = "http://money.rubyforge.org/"
12
+ gem.authors = ["Tobias Luetke", "Hongli Lai", "Jeremy McNevin", "Shane Emmons"]
13
+ gem.rubyforge_project = "money"
14
+ gem.add_development_dependency "rspec", ">= 1.2.9"
15
+ gem.add_development_dependency "hanna", ">= 0.1.12"
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ Jeweler::RubyforgeTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'test'
26
+ spec.spec_files = FileList['test/**/*_spec.rb']
27
+ spec.spec_opts << '--format specdoc'
28
+ end
29
+
30
+ task :spec => :check_dependencies
31
+
32
+ task :default => :spec
33
+
34
+ require 'hanna/rdoctask'
35
+ Rake::RDocTask.new do |rdoc|
36
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
37
+
38
+ rdoc.rdoc_dir = 'rdoc'
39
+ rdoc.main = 'README.rdoc'
40
+ rdoc.title = "money #{version}"
41
+ rdoc.rdoc_files.include('README.rdoc', 'LICENSE')
42
+ rdoc.rdoc_files.include('lib/**/*.rb')
43
+ rdoc.options << '-U'
44
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.2.0
@@ -1,26 +1,26 @@
1
- # Copyright (c) 2005 Tobias Luetke
2
- # Copyright (c) 2008 Phusion
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining
5
- # a copy of this software and associated documentation files (the
6
- # "Software"), to deal in the Software without restriction, including
7
- # without limitation the rights to use, copy, modify, merge, publish,
8
- # distribute, sublicense, and/or sell copies of the Software, and to
9
- # permit persons to whom the Software is furnished to do so, subject to
10
- # the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be
13
- # included in all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
-
23
- $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
24
- require 'money/money'
25
- require 'money/symbols'
26
- require 'money/core_extensions'
1
+ # Copyright (c) 2005 Tobias Luetke
2
+ # Copyright (c) 2008 Phusion
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
24
+ require 'money/money'
25
+ require 'money/defaults'
26
+ require 'money/core_extensions'
@@ -1,138 +1,138 @@
1
- class Numeric
2
- # Converts this numeric to a Money object in the default currency. It
3
- # multiplies the numeric value by 100 and treats that as cents.
4
- #
5
- # 100.to_money => #<Money @cents=10000>
6
- # 100.37.to_money => #<Money @cents=10037>
7
- def to_money
8
- Money.new(self * 100)
9
- end
10
- end
11
-
12
- class String
13
- # Parses the current string and converts it to a Money object.
14
- # Excess characters will be discarded.
15
- #
16
- # '100'.to_money # => #<Money @cents=10000>
17
- # '100.37'.to_money # => #<Money @cents=10037>
18
- # '100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
19
- # 'USD 100'.to_money # => #<Money @cents=10000, @currency="USD">
20
- # '$100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
21
- # 'hello 2000 world'.to_money # => #<Money @cents=200000 @currency="USD")>
22
- def to_money
23
- # Get the currency.
24
- matches = scan /([A-Z]{2,3})/
25
- currency = matches[0] ? matches[0][0] : Money.default_currency
26
- cents = calculate_cents(self)
27
- Money.new(cents, currency)
28
- end
29
-
30
- private
31
-
32
- def calculate_cents(number)
33
- # remove anything that's not a number, potential delimiter, or minus sign
34
- num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
35
-
36
- # set a boolean flag for if the number is negative or not
37
- negative = num.split(//).first == "-"
38
-
39
- # if negative, remove the minus sign from the number
40
- num = num.gsub(/^-/, '') if negative
41
-
42
- # gather all separators within the result number
43
- used_separators = num.scan /[^\d]/
44
-
45
- # determine the number of unique separators within the number
46
- #
47
- # e.g.
48
- # $1,234,567.89 would return 2 (, and .)
49
- # $125,00 would return 1
50
- # $199 would return 0
51
- # $1 234,567.89 would raise an error (separators are space, comma, and period)
52
- case used_separators.uniq.length
53
- # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
54
- when 0 then major, minor = num, 0
55
-
56
- # two separators, so we know the last item in this array is the
57
- # major/minor delimiter and the rest are separators
58
- when 2
59
- separator, delimiter = used_separators.uniq
60
- # remove all separators, split on the delimiter
61
- major, minor = num.gsub(separator, '').split(delimiter)
62
- min = 0 unless min
63
- when 1
64
- # we can't determine if the comma or period is supposed to be a separator or a delimiter
65
- # e.g.
66
- # 1,00 - comma is a delimiter
67
- # 1.000 - period is a delimiter
68
- # 1,000 - comma is a separator
69
- # 1,000,000 - comma is a separator
70
- # 10000,00 - comma is a delimiter
71
- # 1000,000 - comma is a delimiter
72
-
73
- # assign first separator for reusability
74
- separator = used_separators.first
75
-
76
- # separator is used as a separator when there are multiple instances, always
77
- if num.scan(separator).length > 1 # multiple matches; treat as separator
78
- major, minor = num.gsub(separator, ''), 0
79
- else
80
- # ex: 1,000 - 1.0000 - 10001.000
81
- # split number into possible major (dollars) and minor (cents) values
82
- possible_major, possible_minor = num.split(separator)
83
- possible_major ||= "0"
84
- possible_minor ||= "00"
85
-
86
- # if the minor (cents) length isn't 3, assign major/minor from the possibles
87
- # e.g.
88
- # 1,00 => 1.00
89
- # 1.0000 => 1.00
90
- # 1.2 => 1.20
91
- if possible_minor.length != 3 # delimiter
92
- major, minor = possible_major, possible_minor
93
- else
94
- # minor length is three
95
- # let's try to figure out intent of the delimiter
96
-
97
- # the major length is greater than three, which means
98
- # the comma or period is used as a delimiter
99
- # e.g.
100
- # 1000,000
101
- # 100000,000
102
- if possible_major.length > 3
103
- major, minor = possible_major, possible_minor
104
- else
105
- # number is in format ###{sep}### or ##{sep}### or #{sep}###
106
- # handle as , is sep, . is delimiter
107
- if separator == '.'
108
- major, minor = possible_major, possible_minor
109
- else
110
- major, minor = "#{possible_major}#{possible_minor}", 0
111
- end
112
- end
113
- end
114
- end
115
- else
116
- raise ArgumentError, "Invalid currency amount"
117
- end
118
-
119
- # build the string based on major/minor since separator/delimiters have been removed
120
- # avoiding floating point arithmetic here to ensure accuracy
121
- cents = (major.to_i * 100)
122
- # add the minor number as well. this may have any number of digits,
123
- # so we treat minor as a string and truncate or right-fill it with zeroes
124
- # until it becomes a two-digit number string, which we add to cents.
125
- minor = minor.to_s
126
- truncated_minor = minor[0..1]
127
- truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
128
- cents += truncated_minor.to_i
129
- # respect rounding rules
130
- if minor.size >= 3 && minor[2..2].to_i >= 5
131
- cents += 1
132
- end
133
-
134
- # if negative, multiply by -1; otherwise, return positive cents
135
- negative ? cents * -1 : cents
136
- end
137
-
138
- end
1
+ class Numeric
2
+ # Converts this numeric to a Money object in the default currency. It
3
+ # multiplies the numeric value by 100 and treats that as cents.
4
+ #
5
+ # 100.to_money => #<Money @cents=10000>
6
+ # 100.37.to_money => #<Money @cents=10037>
7
+ def to_money
8
+ Money.new(self * 100)
9
+ end
10
+ end
11
+
12
+ class String
13
+ # Parses the current string and converts it to a Money object.
14
+ # Excess characters will be discarded.
15
+ #
16
+ # '100'.to_money # => #<Money @cents=10000>
17
+ # '100.37'.to_money # => #<Money @cents=10037>
18
+ # '100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
19
+ # 'USD 100'.to_money # => #<Money @cents=10000, @currency="USD">
20
+ # '$100 USD'.to_money # => #<Money @cents=10000, @currency="USD">
21
+ # 'hello 2000 world'.to_money # => #<Money @cents=200000 @currency="USD")>
22
+ def to_money
23
+ # Get the currency.
24
+ matches = scan /([A-Z]{2,3})/
25
+ currency = matches[0] ? matches[0][0] : Money.default_currency
26
+ cents = calculate_cents(self)
27
+ Money.new(cents, currency)
28
+ end
29
+
30
+ private
31
+
32
+ def calculate_cents(number)
33
+ # remove anything that's not a number, potential delimiter, or minus sign
34
+ num = number.gsub(/[^\d|\.|,|\'|\s|\-]/, '').strip
35
+
36
+ # set a boolean flag for if the number is negative or not
37
+ negative = num.split(//).first == "-"
38
+
39
+ # if negative, remove the minus sign from the number
40
+ num = num.gsub(/^-/, '') if negative
41
+
42
+ # gather all separators within the result number
43
+ used_separators = num.scan /[^\d]/
44
+
45
+ # determine the number of unique separators within the number
46
+ #
47
+ # e.g.
48
+ # $1,234,567.89 would return 2 (, and .)
49
+ # $125,00 would return 1
50
+ # $199 would return 0
51
+ # $1 234,567.89 would raise an error (separators are space, comma, and period)
52
+ case used_separators.uniq.length
53
+ # no separator or delimiter; major (dollars) is the number, and minor (cents) is 0
54
+ when 0 then major, minor = num, 0
55
+
56
+ # two separators, so we know the last item in this array is the
57
+ # major/minor delimiter and the rest are separators
58
+ when 2
59
+ separator, delimiter = used_separators.uniq
60
+ # remove all separators, split on the delimiter
61
+ major, minor = num.gsub(separator, '').split(delimiter)
62
+ min = 0 unless min
63
+ when 1
64
+ # we can't determine if the comma or period is supposed to be a separator or a delimiter
65
+ # e.g.
66
+ # 1,00 - comma is a delimiter
67
+ # 1.000 - period is a delimiter
68
+ # 1,000 - comma is a separator
69
+ # 1,000,000 - comma is a separator
70
+ # 10000,00 - comma is a delimiter
71
+ # 1000,000 - comma is a delimiter
72
+
73
+ # assign first separator for reusability
74
+ separator = used_separators.first
75
+
76
+ # separator is used as a separator when there are multiple instances, always
77
+ if num.scan(separator).length > 1 # multiple matches; treat as separator
78
+ major, minor = num.gsub(separator, ''), 0
79
+ else
80
+ # ex: 1,000 - 1.0000 - 10001.000
81
+ # split number into possible major (dollars) and minor (cents) values
82
+ possible_major, possible_minor = num.split(separator)
83
+ possible_major ||= "0"
84
+ possible_minor ||= "00"
85
+
86
+ # if the minor (cents) length isn't 3, assign major/minor from the possibles
87
+ # e.g.
88
+ # 1,00 => 1.00
89
+ # 1.0000 => 1.00
90
+ # 1.2 => 1.20
91
+ if possible_minor.length != 3 # delimiter
92
+ major, minor = possible_major, possible_minor
93
+ else
94
+ # minor length is three
95
+ # let's try to figure out intent of the delimiter
96
+
97
+ # the major length is greater than three, which means
98
+ # the comma or period is used as a delimiter
99
+ # e.g.
100
+ # 1000,000
101
+ # 100000,000
102
+ if possible_major.length > 3
103
+ major, minor = possible_major, possible_minor
104
+ else
105
+ # number is in format ###{sep}### or ##{sep}### or #{sep}###
106
+ # handle as , is sep, . is delimiter
107
+ if separator == '.'
108
+ major, minor = possible_major, possible_minor
109
+ else
110
+ major, minor = "#{possible_major}#{possible_minor}", 0
111
+ end
112
+ end
113
+ end
114
+ end
115
+ else
116
+ raise ArgumentError, "Invalid currency amount"
117
+ end
118
+
119
+ # build the string based on major/minor since separator/delimiters have been removed
120
+ # avoiding floating point arithmetic here to ensure accuracy
121
+ cents = (major.to_i * 100)
122
+ # add the minor number as well. this may have any number of digits,
123
+ # so we treat minor as a string and truncate or right-fill it with zeroes
124
+ # until it becomes a two-digit number string, which we add to cents.
125
+ minor = minor.to_s
126
+ truncated_minor = minor[0..1]
127
+ truncated_minor << "0" * (2 - truncated_minor.size) if truncated_minor.size < 2
128
+ cents += truncated_minor.to_i
129
+ # respect rounding rules
130
+ if minor.size >= 3 && minor[2..2].to_i >= 5
131
+ cents += 1
132
+ end
133
+
134
+ # if negative, multiply by -1; otherwise, return positive cents
135
+ negative ? cents * -1 : cents
136
+ end
137
+
138
+ end