monetary_value 1.0.0

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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in monetary_value.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,6 @@
1
+ = Monetary Value -- Library for dealing with monetary values
2
+
3
+ This library provides the classes MonetaryValue and Currency.
4
+
5
+
6
+ Copyright (c) 2009 OpenCuro, Inc., all rights reserved
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/**/*_test.rb'
8
+ end
9
+
10
+ desc "Run tests"
11
+ task :default => :test
data/lib/currency.rb ADDED
@@ -0,0 +1,82 @@
1
+ require 'active_support/core_ext/enumerable' # Provides Enumerable#index_by
2
+
3
+ class Currency
4
+ attr_reader :id, :name, :code, :minor_divisor
5
+
6
+ def initialize(id, name, code, minor_divisor)
7
+ @id = id
8
+ @name = name
9
+ @code = code
10
+ @minor_divisor = minor_divisor
11
+ end
12
+
13
+ unless const_defined?(:CURRENCIES)
14
+ CURRENCIES = [
15
+ new(840, "United States Dollar", "USD", 100),
16
+ ]
17
+ CURRENCIES_BY_ID = CURRENCIES.index_by(&:id)
18
+ CURRENCIES_BY_NAME = CURRENCIES.index_by(&:name)
19
+ CURRENCIES_BY_CODE = CURRENCIES.index_by(&:code)
20
+
21
+ CURRENCIES.freeze
22
+ CURRENCIES_BY_ID.freeze
23
+ CURRENCIES_BY_NAME.freeze
24
+ CURRENCIES_BY_CODE.freeze
25
+ end
26
+
27
+ def convert_to_minor_denomination(major_denomination)
28
+ return nil if major_denomination.nil?
29
+ (major_denomination.to_f * self.minor_divisor.to_f).round
30
+ end
31
+
32
+ def convert_to_major_denomination(minor_denomination)
33
+ return nil if minor_denomination.nil?
34
+ minor_denomination.to_f / self.minor_divisor.to_f
35
+ end
36
+
37
+ class << self
38
+ def all
39
+ CURRENCIES
40
+ end
41
+
42
+ def new(id = 840)
43
+ self[id]
44
+ end
45
+
46
+ def [](arg)
47
+ case arg
48
+ when Integer
49
+ CURRENCIES_BY_ID[arg]
50
+ else
51
+ raise ArgumentError, "invalid argument to Currency[]: #{arg.inspect}"
52
+ end
53
+ end
54
+
55
+ def typecast(object)
56
+ return nil if object.nil?
57
+ return object if object.is_a? self
58
+
59
+ case object
60
+ when Integer
61
+ currency = CURRENCIES_BY_ID[object]
62
+ currency.nil? ? Currency.new : currency
63
+ when String
64
+ currency = CURRENCIES_BY_CODE[object]
65
+ currency.nil? ? Currency.new : currency
66
+ else
67
+ raise UntypecastableArgumentError, "#{object.inspect}:#{object.class} cannot be cast as #{self}"
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ def new_record?
74
+ false
75
+ end
76
+
77
+ def destroyed?
78
+ false
79
+ end
80
+
81
+ end
82
+
@@ -0,0 +1,200 @@
1
+ require 'monetary_value/version'
2
+
3
+ require 'currency'
4
+
5
+ class MonetaryValue
6
+ attr_reader :currency, :denomination
7
+
8
+ def initialize(denomination = 0, currency = Currency.new)
9
+ @denomination = denomination
10
+ @currency = currency
11
+ end
12
+
13
+ # Backwards compatibility
14
+ # TODO: Remove minor_denomination from MonetaryValue
15
+ def minor_denomination
16
+ denomination
17
+ end
18
+
19
+ def major_denomination
20
+ "%01.2f" % (currency.convert_to_major_denomination(denomination))
21
+ end
22
+
23
+ # Display
24
+ def to_s(format = :default)
25
+ case format
26
+ when :default, :short
27
+ "$#{major_denomination}"
28
+ when :long
29
+ "#{major_denomination} #{currency.code}"
30
+ else
31
+ "$#{major_denomination}"
32
+ end
33
+ end
34
+
35
+ def inspect
36
+ to_s(:long)
37
+ end
38
+
39
+ def self.parse(string)
40
+ currency = Currency.new
41
+ string.strip!
42
+ denomination = nil
43
+ nonnegative_string_formats = [
44
+ /^(\d+)$/, # "123"
45
+ /^\$(\d+)$/, # "$123"
46
+ /^(\d*)\.(\d{1,2})$/, # "123.4", ".4", "123.45", ".45"
47
+ /^\$(\d*)\.(\d{1,2})$/, # "$123.4", "$.4", "$123.45", "$.45"
48
+ ]
49
+
50
+ nonnegative_string_formats.each do |format|
51
+ result = string.scan(format)
52
+ if result.size == 1
53
+ denominations = result.first
54
+ major_denomination = denominations[0].to_i
55
+ minor_denomination = denominations[1].nil? ? 0 : denominations[1].ljust(2, "0").to_i
56
+ denomination = major_denomination * 100 + minor_denomination
57
+ return MonetaryValue.new(denomination, currency)
58
+ end
59
+ end
60
+
61
+ negative_string_formats = [
62
+ /^-(\d+)$/, # "-123"
63
+ /^-\$(\d+)$/, # "-$123"
64
+ /^\$-(\d+)$/, # "$-123"
65
+ /^-(\d*)\.(\d{1,2})$/, # "-123.4", "-.4", "-123.45", "-.45"
66
+ /^-\$(\d*)\.(\d{1,2})$/, # "-$123.4", "-$.4", "-$123.45", "-$.45"
67
+ /^\$-(\d*)\.(\d{1,2})$/, # "$-123.4", "$-.4", "$-123.45", "$-.45"
68
+ ]
69
+
70
+ negative_string_formats.each do |format|
71
+ result = string.scan(format)
72
+ if result.size == 1
73
+ denominations = result.first
74
+ major_denomination = denominations[0].to_i
75
+ minor_denomination = denominations[1].nil? ? 0 : denominations[1].ljust(2, "0").to_i
76
+ denomination = major_denomination * 100 + minor_denomination
77
+ return MonetaryValue.new(-denomination, currency)
78
+ end
79
+ end
80
+
81
+ raise ArgumentError, "invalid money_value"
82
+ end
83
+
84
+ class << self
85
+ def typecast(object)
86
+ return nil if object.nil?
87
+ return object if object.is_a? self
88
+
89
+ case object
90
+ when String
91
+ begin
92
+ parse(object)
93
+ rescue ArgumentError => e
94
+ raise UntypecastableArgumentError, "#{object.inspect}:#{object.class} cannot be cast as #{self}"
95
+ end
96
+ else
97
+ raise UntypecastableArgumentError, "#{object.inspect}:#{object.class} cannot be cast as #{self}"
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ ##############
104
+ # Arithmetic #
105
+ ##############
106
+
107
+ # Comparisons
108
+ include Comparable
109
+ def <=>(other)
110
+ raise ArgumentError, "can't compare values in difference currencies" unless self =~ other
111
+ denomination <=> other.denomination
112
+ end
113
+
114
+ # Addition
115
+ def +(other)
116
+ raise ArgumentError, "can't add values in difference currencies" unless self =~ other
117
+ MonetaryValue.new(denomination + other.denomination, currency)
118
+ end
119
+
120
+ # Subtraction
121
+ def -(other)
122
+ raise ArgumentError, "can't subtract values in difference currencies" unless self =~ other
123
+ MonetaryValue.new(denomination - other.denomination, currency)
124
+ end
125
+
126
+ # Multiplication
127
+ def *(num)
128
+ MonetaryValue.new(denomination * num, currency)
129
+ end
130
+
131
+ # Division
132
+ def /(num)
133
+ MonetaryValue.new(denomination / num, currency)
134
+ end
135
+
136
+ # Absolute value
137
+ def abs
138
+ MonetaryValue.new(denomination.abs, currency)
139
+ end
140
+
141
+ # Unary plus
142
+ def +@
143
+ MonetaryValue.new(denomination, currency)
144
+ end
145
+
146
+ # Unary minus
147
+ def -@
148
+ MonetaryValue.new(-denomination, currency)
149
+ end
150
+
151
+ # Currency matching
152
+ def =~(other)
153
+ currency == other.currency
154
+ end
155
+
156
+ # Next
157
+ def next
158
+ MonetaryValue.new(denomination.next, currency)
159
+ end
160
+ alias :succ :next
161
+
162
+ ##############
163
+ # Sign tests #
164
+ ##############
165
+
166
+ def zero?
167
+ denomination.zero?
168
+ end
169
+
170
+ def sign
171
+ denomination.sign
172
+ end
173
+
174
+ def positive?
175
+ denomination.positive?
176
+ end
177
+
178
+ def negative?
179
+ denomination.negative?
180
+ end
181
+
182
+ #################
183
+ # Serialization #
184
+ #################
185
+
186
+ def self.xml_root
187
+ "money"
188
+ end
189
+
190
+ def to_xml(options = {})
191
+ options[:root] ||= self.class.xml_root
192
+ { :currency_id => currency.id, :denomination => denomination }.to_xml(options)
193
+ end
194
+
195
+ def self.from_xml(xml)
196
+ attributes = Hash.from_xml(xml)[xml_root].symbolize_keys
197
+ new( attributes[:denomination] || attributes[:minor_value], Currency.new(attributes[:currency_id]) )
198
+ end
199
+
200
+ end
@@ -0,0 +1,3 @@
1
+ class MonetaryValue
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "monetary_value/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "monetary_value"
7
+ s.version = MonetaryValue::VERSION
8
+ s.authors = ["Ian James"]
9
+ s.email = ["ian@opencuro.com"]
10
+ s.homepage = "https://opencuro.com"
11
+ s.summary = %q{Library for working with monetary values}
12
+ s.description = %q{Provides the MonetaryValue and Currency classes.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ # specify any dependencies here; for example:
20
+ # s.add_development_dependency "rspec"
21
+ # s.add_runtime_dependency "rest-client"
22
+ s.add_dependency "activesupport"
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'currency'
3
+
4
+ class CurrencyTest < ActiveSupport::TestCase
5
+ # Replace this with your real tests.
6
+ test "the truth" do
7
+ assert true
8
+ end
9
+ end
@@ -0,0 +1,149 @@
1
+ require 'test_helper'
2
+ require 'monetary_value'
3
+
4
+ class MonetaryValueTest < ActiveSupport::TestCase
5
+ def test_parse
6
+ valid_strings = {
7
+ "0" => MonetaryValue.new(0),
8
+ "00" => MonetaryValue.new(0),
9
+ "1" => MonetaryValue.new(100),
10
+ "123" => MonetaryValue.new(12300),
11
+
12
+ "$0" => MonetaryValue.new(0),
13
+ "$00" => MonetaryValue.new(0),
14
+ "$1" => MonetaryValue.new(100),
15
+ "$123" => MonetaryValue.new(12300),
16
+
17
+ "0.00" => MonetaryValue.new(0),
18
+ "0.0" => MonetaryValue.new(0),
19
+ ".00" => MonetaryValue.new(0),
20
+ ".0" => MonetaryValue.new(0),
21
+ "0.12" => MonetaryValue.new(12),
22
+ "0.10" => MonetaryValue.new(10),
23
+ "0.1" => MonetaryValue.new(10),
24
+ ".12" => MonetaryValue.new(12),
25
+ ".10" => MonetaryValue.new(10),
26
+ ".1" => MonetaryValue.new(10),
27
+ "1.23" => MonetaryValue.new(123),
28
+ "1.20" => MonetaryValue.new(120),
29
+ "1.2" => MonetaryValue.new(120),
30
+
31
+ "$0.00" => MonetaryValue.new(0),
32
+ "$0.0" => MonetaryValue.new(0),
33
+ "$.00" => MonetaryValue.new(0),
34
+ "$.0" => MonetaryValue.new(0),
35
+ "$0.12" => MonetaryValue.new(12),
36
+ "$0.10" => MonetaryValue.new(10),
37
+ "$0.1" => MonetaryValue.new(10),
38
+ "$.12" => MonetaryValue.new(12),
39
+ "$.10" => MonetaryValue.new(10),
40
+ "$.1" => MonetaryValue.new(10),
41
+ "$1.23" => MonetaryValue.new(123),
42
+ "$1.20" => MonetaryValue.new(120),
43
+ "$1.2" => MonetaryValue.new(120),
44
+
45
+ "-0" => MonetaryValue.new(0),
46
+ "-00" => MonetaryValue.new(0),
47
+ "-1" => MonetaryValue.new(-100),
48
+ "-123" => MonetaryValue.new(-12300),
49
+
50
+ "-$0" => MonetaryValue.new(0),
51
+ "-$00" => MonetaryValue.new(0),
52
+ "-$1" => MonetaryValue.new(-100),
53
+ "-$123" => MonetaryValue.new(-12300),
54
+
55
+ "$-0" => MonetaryValue.new(0),
56
+ "$-00" => MonetaryValue.new(0),
57
+ "$-1" => MonetaryValue.new(-100),
58
+ "$-123" => MonetaryValue.new(-12300),
59
+
60
+ "-0.00" => MonetaryValue.new(0),
61
+ "-0.0" => MonetaryValue.new(0),
62
+ "-.00" => MonetaryValue.new(0),
63
+ "-.0" => MonetaryValue.new(0),
64
+ "-0.12" => MonetaryValue.new(-12),
65
+ "-0.10" => MonetaryValue.new(-10),
66
+ "-0.1" => MonetaryValue.new(-10),
67
+ "-.12" => MonetaryValue.new(-12),
68
+ "-.10" => MonetaryValue.new(-10),
69
+ "-.1" => MonetaryValue.new(-10),
70
+ "-1.23" => MonetaryValue.new(-123),
71
+ "-1.20" => MonetaryValue.new(-120),
72
+ "-1.2" => MonetaryValue.new(-120),
73
+
74
+ "-$0.00" => MonetaryValue.new(0),
75
+ "-$0.0" => MonetaryValue.new(0),
76
+ "-$.00" => MonetaryValue.new(0),
77
+ "-$.0" => MonetaryValue.new(0),
78
+ "-$0.12" => MonetaryValue.new(-12),
79
+ "-$0.10" => MonetaryValue.new(-10),
80
+ "-$0.1" => MonetaryValue.new(-10),
81
+ "-$.12" => MonetaryValue.new(-12),
82
+ "-$.10" => MonetaryValue.new(-10),
83
+ "-$.1" => MonetaryValue.new(-10),
84
+ "-$1.23" => MonetaryValue.new(-123),
85
+ "-$1.20" => MonetaryValue.new(-120),
86
+ "-$1.2" => MonetaryValue.new(-120),
87
+
88
+ "$-0.00" => MonetaryValue.new(0),
89
+ "$-0.0" => MonetaryValue.new(0),
90
+ "$-.00" => MonetaryValue.new(0),
91
+ "$-.0" => MonetaryValue.new(0),
92
+ "$-0.12" => MonetaryValue.new(-12),
93
+ "$-0.10" => MonetaryValue.new(-10),
94
+ "$-0.1" => MonetaryValue.new(-10),
95
+ "$-.12" => MonetaryValue.new(-12),
96
+ "$-.10" => MonetaryValue.new(-10),
97
+ "$-.1" => MonetaryValue.new(-10),
98
+ "$-1.23" => MonetaryValue.new(-123),
99
+ "$-1.20" => MonetaryValue.new(-120),
100
+ "$-1.2" => MonetaryValue.new(-120),
101
+
102
+ }
103
+
104
+ valid_strings.each do |valid_string, result|
105
+ begin
106
+ assert_equal result, MonetaryValue.parse(valid_string), valid_string
107
+ rescue ArgumentError
108
+ flunk "#{valid_string} should be able to be parsed"
109
+ end
110
+ end
111
+
112
+ invalid_strings = [
113
+ "",
114
+ "$",
115
+ ".",
116
+ "$.",
117
+
118
+ "-",
119
+ "-$",
120
+ "$-",
121
+ "-.",
122
+ "-$.",
123
+ "$-.",
124
+
125
+ "$1.234",
126
+ "a1.23",
127
+ "1a.23",
128
+ "1. 23",
129
+ "1 .23",
130
+ "1,23",
131
+ "1.023",
132
+ "0x1",
133
+
134
+ "0.",
135
+ "1.",
136
+ "$0.",
137
+ "$-1.",
138
+ "-$1.",
139
+ ]
140
+
141
+ invalid_strings.each do |invalid_string|
142
+ assert_raise ArgumentError do
143
+ MonetaryValue.parse(invalid_string)
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support'
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monetary_value
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Ian James
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-06-06 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: Provides the MonetaryValue and Currency classes.
35
+ email:
36
+ - ian@opencuro.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - README.rdoc
47
+ - Rakefile
48
+ - lib/currency.rb
49
+ - lib/monetary_value.rb
50
+ - lib/monetary_value/version.rb
51
+ - monetary_value.gemspec
52
+ - test/currency_test.rb
53
+ - test/monetary_value_test.rb
54
+ - test/test_helper.rb
55
+ homepage: https://opencuro.com
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options: []
60
+
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ requirements: []
82
+
83
+ rubyforge_project:
84
+ rubygems_version: 1.8.10
85
+ signing_key:
86
+ specification_version: 3
87
+ summary: Library for working with monetary values
88
+ test_files: []
89
+