package lib import ( "strconv" "strings" ) // ParseNumeric parses a numeric string that may use comma or period as // decimal or thousands separator. Returns the float value and true on success. // // Rules: // 1. Both comma and period → last one is decimal separator // 2. Only one separator type → count digits after last occurrence: // - 3 digits → ambiguous → hint decides (default: thousands) // - other → decimal separator // 3. No separators → plain integer // // Hint: locale like "en", "de", "nl". Empty = default (comma=thousands). func ParseNumeric(s string, hint string) (float64, bool) { s = strings.TrimSpace(s) if s == "" { return 0, false } s = strings.TrimPrefix(s, "+") hasComma := strings.Contains(s, ",") hasPeriod := strings.Contains(s, ".") switch { case hasComma && hasPeriod: // Last separator is the decimal one if strings.LastIndex(s, ",") > strings.LastIndex(s, ".") { // "1.234,56" → comma is decimal s = strings.ReplaceAll(s, ".", "") s = strings.Replace(s, ",", ".", 1) } else { // "1,234.56" → period is decimal s = strings.ReplaceAll(s, ",", "") } case hasComma: last := strings.LastIndex(s, ",") after := s[last+1:] if len(after) == 3 && commaIsThousands(hint) { // "1,234" in en context → thousands s = strings.ReplaceAll(s, ",", "") } else { // "12,5" or "1,23" → decimal s = strings.ReplaceAll(s, ",", ".") } case hasPeriod: last := strings.LastIndex(s, ".") after := s[last+1:] if len(after) == 3 && !commaIsThousands(hint) { // "1.234" in de context → thousands s = strings.ReplaceAll(s, ".", "") } // else: period is decimal (default), leave as-is } v, err := strconv.ParseFloat(s, 64) return v, err == nil } // commaIsThousands returns true if comma is used as thousands separator // in the given locale (English convention). False for European convention. func commaIsThousands(hint string) bool { switch strings.ToLower(hint) { case "de", "nl", "fr", "es", "it", "pt", "ru", "tr", "pl", "cs", "da", "sv", "nb", "fi": return false default: return true // en, en-us, en-gb, ja, zh, ko, "" (default) } }