4

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))
}
André G. Andrade
  • 501
  • 11
  • 21
  • 1
    I don't see any indication in the documentation of `x/text/currency` implying that it formats digit groupings in any way at all. It renders the currency symbol as expected, which is what it claims it does. – Adrian Dec 13 '17 at 19:57
  • 2
    For grouping: [How to fmt.Printf an integer with thousands comma](https://stackoverflow.com/questions/13020308/how-to-fmt-printf-an-integer-with-thousands-comma/31046325#31046325) – icza Dec 13 '17 at 20:09
  • Hi @icza I did a version using this solution. But I would like one that uses system locales. I did using the following code where the currency is hardcoded p := message.NewPrinter(language.BrazilianPortuguese) return p.Sprintf("R$ %.2f", float64(produto.Preco/100)) – André G. Andrade Dec 13 '17 at 20:12

2 Answers2

6

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
Chaim Leib Halbert
  • 2,194
  • 20
  • 23
0

As you may notice, the golang.org/x/text/currency is still a work in progress and hasn't been updated in a while.

NOTE: the formatting functionality is currently under development and may change without notice.

If you prefer a simple helper function to get the job done, you can use something like this:

func formatMoney(value int32, thousand, decimal string) string {
    var result string
    var isNegative bool

    if value < 0 {
        value = value * -1
        isNegative = true
    }

    // apply the decimal separator
    result = fmt.Sprintf("%s%02d%s", decimal, value%100, result)
    value /= 100

    // for each 3 dígits put a dot "."
    for value >= 1000 {
        result = fmt.Sprintf("%s%03d%s", thousand, value%1000, result)
        value /= 1000
    }

    if isNegative {
        return fmt.Sprintf("$ -%d%s", value, result)
    }

    return fmt.Sprintf("$ %d%s", value, result)
}

usage:

formatMoney(10, ",", ".") // $ 0.10
formatMoney(12345, ",", ".") // $ 123.45
formatMoney(1234567, ",", ".") // $ 12,345.67
formatMoney(-1234567, ",", ".") // $ -12,345.67
Rafael Affonso
  • 181
  • 3
  • 6