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 }