package web import ( "fmt" "strconv" "strings" "time" ) var currencySymbols = map[string]string{ "USD": "$", "GBP": "£", "EUR": "€", "JPY": "¥", } func currencySymbol(currency string) string { if sym, ok := currencySymbols[currency]; ok { return sym } return currency + " " } // money formats an amount in the given currency with thousands separators, // e.g. money(-1234.5, "GBP") -> "-£1,234.50". func money(v float64, currency string) string { neg := v < 0 if neg { v = -v } s := strconv.FormatFloat(v, 'f', 2, 64) intPart, decPart, _ := strings.Cut(s, ".") var out []byte n := len(intPart) for i := 0; i < n; i++ { if i > 0 && (n-i)%3 == 0 { out = append(out, ',') } out = append(out, intPart[i]) } res := currencySymbol(currency) + string(out) + "." + decPart if neg { res = "-" + res } return res } // amt formats a crypto quantity, trimming trailing zeros. func amt(v float64) string { s := strconv.FormatFloat(v, 'f', 8, 64) s = strings.TrimRight(s, "0") s = strings.TrimRight(s, ".") if s == "" || s == "-" { s = "0" } return s } func pctStr(v *float64) string { if v == nil { return "—" } sign := "" if *v > 0 { sign = "+" } return fmt.Sprintf("%s%.2f%%", sign, *v) } func pctClass(v *float64) string { if v == nil { return "flat" } return signClass(*v) } func signClass(v float64) string { if v > 0 { return "gain" } if v < 0 { return "loss" } return "flat" } func dateStr(t time.Time) string { return t.Format("Jan 2, 2006") } // capitalize upper-cases the first letter of an entry type ("withdrawal" -> "Withdrawal"). func capitalize(s string) string { if s == "" { return s } return strings.ToUpper(s[:1]) + s[1:] } // sortVal renders a float64 or *float64 as a plain number string for a // data-sort-value attribute, so the client-side table sort compares numbers // rather than formatted display text ("£1,234.56", "+3.21%", "—"). func sortVal(v interface{}) string { switch x := v.(type) { case float64: return strconv.FormatFloat(x, 'f', -1, 64) case *float64: if x == nil { return "" } return strconv.FormatFloat(*x, 'f', -1, 64) default: return fmt.Sprint(v) } } func holdDuration(t time.Time) string { days := int(time.Since(t).Hours() / 24) switch { case days < 1: return "<1 day" case days == 1: return "1 day" case days < 30: return fmt.Sprintf("%d days", days) case days < 365: return fmt.Sprintf("%d mo", days/30) default: return fmt.Sprintf("%.1f yr", float64(days)/365) } }