Search code examples
gocurrency-formatting

Currency Format in Golang using currency.Symbol from golang.org/x/text/currency


Hi I'm working on a function to format values in currency. I'm using golang.org/x/text/currency for the job, but I'm getting the output with points at the place of commas and no thousands separators.

func (produto *Produto) FormataPreco(valor int64) string {
    unit, _ := currency.ParseISO("BRL")
    p := message.NewPrinter(language.BrazilianPortuguese)
    return p.Sprint(currency.Symbol(unit.Amount(float64(valor) / 100)))
}

The expected result should be R$ 123.456,78 but I'm getting R$ 123456.78

--- Edit ---

I did a version using hardcoded values, but I would like a solution that uses system locale resources.

func (produto *Produto) FormataPreco(valor int64) string {
    p := message.NewPrinter(language.BrazilianPortuguese)
    return p.Sprintf("R$ %.2f", float64(valor/100))
}

Solution

  • In this example, I inferred the currency format from the language code.

    https://goplay.space/#fqs9t8MG062

    n := display.Tags(language.English)
    for _, lcode := range []string{"en_US", "pt_BR", "de", "ja", "hi"} {
        lang := language.MustParse(lcode)
        cur, _ := currency.FromTag(lang)
        scale, _ := currency.Cash.Rounding(cur) // fractional digits
        dec := number.Decimal(100000.00, number.Scale(scale))
        p := message.NewPrinter(lang)
        p.Printf("%24v (%v): %v%v\n", n.Name(lang), cur, currency.Symbol(cur), dec)
    }
    
    //         American English (USD): $100,000.00
    //     Brazilian Portuguese (BRL): R$100.000,00
    //                   German (EUR): €100.000,00
    //                 Japanese (JPY): ¥100,000
    //                    Hindi (INR): ₹1,00,000.00
    

    You could also parse ISO currency codes, but then you must also specify the language in which to format the number. The output language will not affect the number of fractional digits, but it will affect where commas and periods are used:

    https://goplay.space/#DlxSmjZbHH6

    for _, iso := range []string{"USD", "BRL", "EUR", "JPY", "INR"} {
        cur := currency.MustParseISO(iso)
        scale, _ := currency.Cash.Rounding(cur) // fractional digits
        dec := number.Decimal(100000.00, number.Scale(scale))
        p := message.NewPrinter(language.English)
        p.Printf("%v: %v%v\n", cur, currency.Symbol(cur), dec)
    }
    
    // USD: $100,000.00
    // BRL: R$100,000.00
    // EUR: €100,000.00
    // JPY: ¥100,000
    // INR: ₹100,000.00
    

    Certain currencies are rounded in increments, like 0.05 or 0.50. For those cases, the second return value of currency.Cash.Rounding(cur) will return 5 or 50 instead of 1. To give the Decimal formatter the IncrementString it expects, we have to do a little more processing:

    package main
    
    import (
        "math"
        "strconv"
    
        "golang.org/x/text/currency"
        "golang.org/x/text/language"
        "golang.org/x/text/language/display"
        "golang.org/x/text/message"
        "golang.org/x/text/number"
    )
    
    func main() {
        n := display.Tags(language.English)
        for _, lcode := range []string{"en_US", "en_CA", "da", "ja"} {
            lang := language.MustParse(lcode)
            cur, _ := currency.FromTag(lang)
            scale, incCents := currency.Cash.Rounding(cur) // fractional digits
            incFloat := math.Pow10(-scale) * float64(incCents)
            incFmt := strconv.FormatFloat(incFloat, 'f', scale, 64)
            dec := number.Decimal(100000.26,
                number.Scale(scale), number.IncrementString(incFmt))
            p := message.NewPrinter(lang)
            p.Printf("%24v %v, %4s-rounding: %3v%v\n",
                n.Name(lang), cur, incFmt, currency.Symbol(cur), dec)
        }
    }
    
    //    American English USD, 0.01-rounding: $100,000.26
    //    Canadian English CAD, 0.05-rounding: CA$100,000.25
    //              Danish DKK, 0.50-rounding: DKK100.000,50
    //            Japanese JPY,    1-rounding: ¥100,000