first commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user