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
|
package utils
import (
"fmt"
"strconv"
"strings"
"github.com/Rhymond/go-money"
)
// supported currencies
var currencies = money.Currencies{
"USD": money.GetCurrency(money.USD),
"EUR": money.GetCurrency(money.EUR),
"GBP": money.GetCurrency(money.GBP),
"JPY": money.GetCurrency(money.JPY),
"CNY": money.GetCurrency(money.CNY),
}
func ParseMoney(s string) (*money.Money, error) {
for _, c := range currencies {
numPart, ok := isCurrency(s, c)
if !ok {
continue
}
// Parse the number part
num, err := strconv.ParseUint(numPart, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse number: %w", err)
}
return money.New(int64(num), c.Code), nil
}
return nil, fmt.Errorf("matching currency not found")
}
func isCurrency(s string, c *money.Currency) (string, bool) {
var numPart string
for _, tp := range c.Template {
switch tp {
case '$':
// There should be a matching grapheme in the s at this position
remaining, ok := strings.CutPrefix(s, c.Grapheme)
if !ok {
return "", false
}
s = remaining
case '1':
// There should be a number, thousands, or decimal separator in the s at this position
// Number of expected decimal places
decimalFound := -1
// Read from string until a non-number, non-thousands, non-decimal, or EOF is found
for len(s) > 0 && (string(s[0]) == c.Thousand || string(s[0]) == c.Decimal || '0' <= s[0] && s[0] <= '9') {
// If the character is a number
if '0' <= s[0] && s[0] <= '9' {
// If we've hit decimal limit, break
if decimalFound == 0 {
break
}
// add the number to the numPart
numPart += string(s[0])
// Decrement the decimal count
// If the decimal has been found, `decimalFound` is positive
// If the decimal hasn't been found, `decimalFound` is negative, and decrementing it does nothing
decimalFound--
}
// If decimal has been found (>= 0) and the character is a thousand separator or decimal separator,
// then the number is invalid
if decimalFound >= 0 && (string(s[0]) == c.Thousand || string(s[0]) == c.Decimal) {
return "", false
}
// If the character is a decimal separator, set `decimalFound` to the number of
// expected decimal places for the currency
if string(s[0]) == c.Decimal {
decimalFound = c.Fraction
}
// Move to the next character
s = s[1:]
}
if decimalFound > 0 {
// If there should be more decimal places, add them
numPart += strings.Repeat("0", decimalFound)
} else if decimalFound < 0 {
// If no decimal was found, add the expected number of decimal places
numPart += strings.Repeat("0", c.Fraction)
}
case ' ':
// There should be a space in the s at this position
if len(s) == 0 || s[0] != ' ' {
return "", false
}
s = s[1:]
default:
panic(fmt.Sprintf("unsupported template character: %c", tp))
}
}
return numPart, true
}
|