mirror of
https://github.com/miguelmota/cointop
synced 2024-11-10 13:10:26 +00:00
b5b68833f5
* Add support for declaring a BuyPrice and BuyCurrency in portfolio. eg: ["Algorand", "125.4", "0.8", "USD"] Add optional (default off) columns to portfolio: "buy_price", "buy_currency", "profit", "profit_percent" Note: there is no UI for entering this yet.
164 lines
4.8 KiB
Go
164 lines
4.8 KiB
Go
package humanize
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/goodsign/monday"
|
|
"github.com/jeandeaual/go-locale"
|
|
"golang.org/x/text/language"
|
|
"golang.org/x/text/message"
|
|
)
|
|
|
|
var cachedSystemLocale = ""
|
|
|
|
// Numericf produces a string from of the given number with give fixed precision
|
|
// in base 10 with thousands separators after every three orders of magnitude
|
|
// using thousands and decimal separator according to LC_NUMERIC; defaulting "en".
|
|
//
|
|
// e.g. Numericf(834142.32, 2) -> "834,142.32"
|
|
func Numericf(value float64, precision int) string {
|
|
return f(value, precision, "LC_NUMERIC", true)
|
|
}
|
|
|
|
// Monetaryf produces a string from of the given number give minimum precision
|
|
// in base 10 with thousands separators after every three orders of magnitude
|
|
// using thousands and decimal separator according to LC_MONETARY; defaulting "en".
|
|
//
|
|
// e.g. Monetaryf(834142.3256, 2) -> "834,142.3256"
|
|
func Monetaryf(value float64, precision int) string {
|
|
return f(value, precision, "LC_MONETARY", false)
|
|
}
|
|
|
|
// FixedMonetaryf produces a fixed-precision monetary-value string. See Monetaryf.
|
|
func FixedMonetaryf(value float64, precision int) string {
|
|
return f(value, precision, "LC_MONETARY", true)
|
|
}
|
|
|
|
// borrowed from go-locale/util.go
|
|
func splitLocale(locale string) (string, string) {
|
|
// Remove the encoding, if present
|
|
formattedLocale := strings.Split(locale, ".")[0]
|
|
// Normalize by replacing the hyphens with underscores
|
|
formattedLocale = strings.Replace(formattedLocale, "-", "_", -1)
|
|
|
|
// Split at the underscore
|
|
split := strings.Split(formattedLocale, "_")
|
|
language := split[0]
|
|
territory := ""
|
|
if len(split) > 1 {
|
|
territory = split[1]
|
|
}
|
|
|
|
return language, territory
|
|
}
|
|
|
|
// GetLocale returns the current locale as defined in IETF BCP 47 (e.g. "en-US").
|
|
// The envvar provided is checked first (eg LC_TIME), before the platform-specific defaults.
|
|
func getLocale(envvar string) string {
|
|
userLocale := "en-US" // default language-REGION
|
|
// First try looking up envar directly
|
|
envlang, ok := os.LookupEnv(envvar)
|
|
if ok {
|
|
language, region := splitLocale(envlang)
|
|
userLocale = language
|
|
if len(region) > 0 {
|
|
userLocale = strings.Join([]string{language, region}, "-")
|
|
}
|
|
} else {
|
|
// Then use (cached) system-specific locale
|
|
if cachedSystemLocale == "" {
|
|
if loc, err := locale.GetLocale(); err == nil {
|
|
userLocale = loc
|
|
cachedSystemLocale = loc
|
|
}
|
|
} else {
|
|
userLocale = cachedSystemLocale
|
|
}
|
|
}
|
|
return userLocale
|
|
}
|
|
|
|
// formatTimeExplicit formats the given time using the prescribed layout with the provided userLocale
|
|
func formatTimeExplicit(time time.Time, layout string, userLocale string) string {
|
|
mondayLocale := monday.Locale(strings.Replace(userLocale, "-", "_", 1))
|
|
return monday.Format(time, layout, mondayLocale)
|
|
}
|
|
|
|
// FormatTime is a dropin replacement time.Format(layout) that uses system locale + LC_TIME
|
|
func FormatTime(time time.Time, layout string) string {
|
|
return formatTimeExplicit(time, layout, getLocale("LC_TIME"))
|
|
}
|
|
|
|
// f formats given value, with precision decimal places using thousands and decimal
|
|
// separator according to language found in given locale environment variable e.
|
|
// If fixed is true the decimal places are fixed to the given precision otherwise d is the
|
|
// minimum of decimal places until the first 0.
|
|
func f(value float64, precision int, envvar string, fixed bool) string {
|
|
parts := strings.Split(strconv.FormatFloat(value, 'f', -1, 64), ".")
|
|
if !fixed && len(parts) > 1 {
|
|
for ; precision < len(parts[1]); precision += 1 {
|
|
if parts[1][precision] == '0' {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
envlang, ok := os.LookupEnv(envvar)
|
|
if !ok {
|
|
envlang = "en"
|
|
}
|
|
lang := language.Make(envlang)
|
|
|
|
format := fmt.Sprintf("%%.%df", precision)
|
|
return message.NewPrinter(lang).Sprintf(format, value)
|
|
}
|
|
|
|
// Scale returns a scaled-down version of value and a suffix to add (M,B,etc.)
|
|
func Scale(value float64) (float64, string) {
|
|
type scalingUnit struct {
|
|
value float64
|
|
suffix string
|
|
}
|
|
|
|
// quadrillion, quintrillion, sextillion, septillion, octillion, nonillion, and decillion
|
|
var scales = [...]scalingUnit{
|
|
{value: 1e12, suffix: "T"},
|
|
{value: 1e9, suffix: "B"},
|
|
{value: 1e6, suffix: "M"},
|
|
{value: 1e3, suffix: "K"},
|
|
}
|
|
|
|
for _, scale := range scales {
|
|
if math.Abs(value) > scale.value {
|
|
return value / scale.value, scale.suffix
|
|
}
|
|
}
|
|
return value, ""
|
|
}
|
|
|
|
// ScaleNumericf scales a large number down using a suffix, then formats it with the
|
|
// prescribed number of significant digits.
|
|
func ScaleNumericf(value float64, digits int) string {
|
|
value, suffix := Scale(value)
|
|
|
|
// Round the scaled value to a certain number of significant figures
|
|
var s string
|
|
if math.Abs(value) < 1 {
|
|
s = Numericf(value, digits)
|
|
} else {
|
|
numDigits := len(fmt.Sprintf("%.0f", math.Abs(value)))
|
|
if numDigits >= digits {
|
|
s = Numericf(value, 0)
|
|
} else {
|
|
s = Numericf(value, digits-numDigits)
|
|
}
|
|
}
|
|
|
|
return s + suffix
|
|
}
|