Files

128 lines
2.5 KiB
Go
Raw Permalink Normal View History

2026-08-25 06:37:14 +01:00
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)
}
}