This repository has been archived by the owner on Aug 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcurrency.go
146 lines (124 loc) · 3.7 KB
/
currency.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package money
import (
"errors"
"fmt"
"strings"
"sync"
"golang.org/x/text/currency"
)
// TODO: Refactor to use directly golang.org/x/text/currency
var (
// ErrInvalidCurrency indicates that the string is not a valid currency as defined by ISO 4217
ErrInvalidCurrency = errors.New("invalid currency")
// ErrUnsupportedCurrency indicates that the currency is not supported
ErrUnsupportedCurrency = errors.New("unsupported currency")
)
// Currency is represented in code as defined by the ISO 4217 format.
//
// Examples:
// * Swiss franc - CHF
// * United States dollar - USD
// * Euro - EUR
// * Polish złoty - PLN
// * Bitcoin - XBT
type Currency string
// MustParseCurrency is like ParseCurency, but panics if the given currency
// unit cannot be parsed. It simplifies safe initialisation of Currency values.
func MustParseCurrency(s string) Currency {
c, err := ParseCurrency(s)
if err != nil {
panic(err)
}
return c
}
// ParseCurrency parses a 3-letter ISO 4217 currency code. It returns an error
// if it is not well-formed or not a recognised currency code.
//
// Examples:
// * CHF
// * XBT
func ParseCurrency(s string) (Currency, error) {
// Sanitise
s = strings.TrimSpace(strings.ToUpper(s))
if _, ok := unoficialCurrencies.Load(s); ok {
return Currency(s), nil
}
u, err := currency.ParseISO(s)
if err != nil {
return nullCurrency, ErrInvalidCurrency
}
return Currency(u.String()), nil
}
// Scale returns the standard currency scale
func (c Currency) Scale() int {
scale, _ := currency.Kind(RoundingStandard.kind()).Rounding(*c.currency())
return scale
}
// RoundUnit returns a rounding unit for the given kind
func (c Currency) RoundUnit(kind RoundingKind) Decimal {
// Get rounding for the currency
scale, inc := currency.Kind(kind.kind()).Rounding(*c.currency())
return buildDecimal(int64(inc), int32(scale*-1))
}
// String returns the ISO 4217 representation of a currency (e.g. CHF)
func (c Currency) String() string {
return string(c)
}
// Validate returns whether the currency is valid
func (c Currency) Validate() error {
_, err := ParseCurrency(string(c))
return err
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (c *Currency) UnmarshalJSON(data []byte) error {
if len(data) > 2 && data[0] == '"' && data[len(data)-1] == '"' {
s := string(data[1 : len(data)-1])
currency, err := ParseCurrency(s)
if err != nil {
return fmt.Errorf("Error parsing money/currency '%s': %s", s, err)
}
*c = currency
return nil
}
// Accept empty data. The Validate function should be used to make sure it
// is valid
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (c Currency) MarshalJSON() ([]byte, error) {
return []byte("\"" + c + "\""), nil
}
// GobEncode implements the gob.GobEncoder interface for gob serialization.
func (c Currency) GobEncode() ([]byte, error) {
return []byte(c), nil
}
// GobDecode implements the gob.GobDecoder interface for gob serialization.
func (c *Currency) GobDecode(data []byte) error {
cur, err := ParseCurrency(string(data))
if err != nil {
return err
}
*c = cur
return nil
}
func (c *Currency) currency() *currency.Unit {
cur, err := currency.ParseISO(c.String())
if err != nil {
panic("invalid currency unit: " + err.Error())
}
return &cur
}
const (
nullCurrency Currency = ""
)
var unoficialCurrencies = sync.Map{}
// RegisterUnoficialCurrency registers a currency code that is not a valid
// ISO 4217 currency code.
//
// This can be used for crypto currency codes, such as ETH, DAI, USDC, ...
func RegisterUnoficialCurrency(code string) {
code = strings.TrimSpace(strings.ToUpper(code))
if _, err := ParseCurrency(code); err == ErrInvalidCurrency {
unoficialCurrencies.Store(code, true)
}
}