You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

215 lines
5.5 KiB

  1. // Copyright 2015 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package currency
  5. import (
  6. "fmt"
  7. "io"
  8. "sort"
  9. "golang.org/x/text/internal/format"
  10. "golang.org/x/text/internal/language/compact"
  11. )
  12. // Amount is an amount-currency unit pair.
  13. type Amount struct {
  14. amount interface{} // Change to decimal(64|128).
  15. currency Unit
  16. }
  17. // Currency reports the currency unit of this amount.
  18. func (a Amount) Currency() Unit { return a.currency }
  19. // TODO: based on decimal type, but may make sense to customize a bit.
  20. // func (a Amount) Decimal()
  21. // func (a Amount) Int() (int64, error)
  22. // func (a Amount) Fraction() (int64, error)
  23. // func (a Amount) Rat() *big.Rat
  24. // func (a Amount) Float() (float64, error)
  25. // func (a Amount) Scale() uint
  26. // func (a Amount) Precision() uint
  27. // func (a Amount) Sign() int
  28. //
  29. // Add/Sub/Div/Mul/Round.
  30. var space = []byte(" ")
  31. // Format implements fmt.Formatter. It accepts format.State for
  32. // language-specific rendering.
  33. func (a Amount) Format(s fmt.State, verb rune) {
  34. v := formattedValue{
  35. currency: a.currency,
  36. amount: a.amount,
  37. format: defaultFormat,
  38. }
  39. v.Format(s, verb)
  40. }
  41. // formattedValue is currency amount or unit that implements language-sensitive
  42. // formatting.
  43. type formattedValue struct {
  44. currency Unit
  45. amount interface{} // Amount, Unit, or number.
  46. format *options
  47. }
  48. // Format implements fmt.Formatter. It accepts format.State for
  49. // language-specific rendering.
  50. func (v formattedValue) Format(s fmt.State, verb rune) {
  51. var lang compact.ID
  52. if state, ok := s.(format.State); ok {
  53. lang, _ = compact.RegionalID(compact.Tag(state.Language()))
  54. }
  55. // Get the options. Use DefaultFormat if not present.
  56. opt := v.format
  57. if opt == nil {
  58. opt = defaultFormat
  59. }
  60. cur := v.currency
  61. if cur.index == 0 {
  62. cur = opt.currency
  63. }
  64. // TODO: use pattern.
  65. io.WriteString(s, opt.symbol(lang, cur))
  66. if v.amount != nil {
  67. s.Write(space)
  68. // TODO: apply currency-specific rounding
  69. scale, _ := opt.kind.Rounding(cur)
  70. if _, ok := s.Precision(); !ok {
  71. fmt.Fprintf(s, "%.*f", scale, v.amount)
  72. } else {
  73. fmt.Fprint(s, v.amount)
  74. }
  75. }
  76. }
  77. // Formatter decorates a given number, Unit or Amount with formatting options.
  78. type Formatter func(amount interface{}) formattedValue
  79. // func (f Formatter) Options(opts ...Option) Formatter
  80. // TODO: call this a Formatter or FormatFunc?
  81. var dummy = USD.Amount(0)
  82. // adjust creates a new Formatter based on the adjustments of fn on f.
  83. func (f Formatter) adjust(fn func(*options)) Formatter {
  84. var o options = *(f(dummy).format)
  85. fn(&o)
  86. return o.format
  87. }
  88. // Default creates a new Formatter that defaults to currency unit c if a numeric
  89. // value is passed that is not associated with a currency.
  90. func (f Formatter) Default(currency Unit) Formatter {
  91. return f.adjust(func(o *options) { o.currency = currency })
  92. }
  93. // Kind sets the kind of the underlying currency unit.
  94. func (f Formatter) Kind(k Kind) Formatter {
  95. return f.adjust(func(o *options) { o.kind = k })
  96. }
  97. var defaultFormat *options = ISO(dummy).format
  98. var (
  99. // Uses Narrow symbols. Overrides Symbol, if present.
  100. NarrowSymbol Formatter = Formatter(formNarrow)
  101. // Use Symbols instead of ISO codes, when available.
  102. Symbol Formatter = Formatter(formSymbol)
  103. // Use ISO code as symbol.
  104. ISO Formatter = Formatter(formISO)
  105. // TODO:
  106. // // Use full name as symbol.
  107. // Name Formatter
  108. )
  109. // options configures rendering and rounding options for an Amount.
  110. type options struct {
  111. currency Unit
  112. kind Kind
  113. symbol func(compactIndex compact.ID, c Unit) string
  114. }
  115. func (o *options) format(amount interface{}) formattedValue {
  116. v := formattedValue{format: o}
  117. switch x := amount.(type) {
  118. case Amount:
  119. v.amount = x.amount
  120. v.currency = x.currency
  121. case *Amount:
  122. v.amount = x.amount
  123. v.currency = x.currency
  124. case Unit:
  125. v.currency = x
  126. case *Unit:
  127. v.currency = *x
  128. default:
  129. if o.currency.index == 0 {
  130. panic("cannot format number without a currency being set")
  131. }
  132. // TODO: Must be a number.
  133. v.amount = x
  134. v.currency = o.currency
  135. }
  136. return v
  137. }
  138. var (
  139. optISO = options{symbol: lookupISO}
  140. optSymbol = options{symbol: lookupSymbol}
  141. optNarrow = options{symbol: lookupNarrow}
  142. )
  143. // These need to be functions, rather than curried methods, as curried methods
  144. // are evaluated at init time, causing tables to be included unconditionally.
  145. func formISO(x interface{}) formattedValue { return optISO.format(x) }
  146. func formSymbol(x interface{}) formattedValue { return optSymbol.format(x) }
  147. func formNarrow(x interface{}) formattedValue { return optNarrow.format(x) }
  148. func lookupISO(x compact.ID, c Unit) string { return c.String() }
  149. func lookupSymbol(x compact.ID, c Unit) string { return normalSymbol.lookup(x, c) }
  150. func lookupNarrow(x compact.ID, c Unit) string { return narrowSymbol.lookup(x, c) }
  151. type symbolIndex struct {
  152. index []uint16 // position corresponds with compact index of language.
  153. data []curToIndex
  154. }
  155. var (
  156. normalSymbol = symbolIndex{normalLangIndex, normalSymIndex}
  157. narrowSymbol = symbolIndex{narrowLangIndex, narrowSymIndex}
  158. )
  159. func (x *symbolIndex) lookup(lang compact.ID, c Unit) string {
  160. for {
  161. index := x.data[x.index[lang]:x.index[lang+1]]
  162. i := sort.Search(len(index), func(i int) bool {
  163. return index[i].cur >= c.index
  164. })
  165. if i < len(index) && index[i].cur == c.index {
  166. x := index[i].idx
  167. start := x + 1
  168. end := start + uint16(symbols[x])
  169. if start == end {
  170. return c.String()
  171. }
  172. return symbols[start:end]
  173. }
  174. if lang == 0 {
  175. break
  176. }
  177. lang = lang.Parent()
  178. }
  179. return c.String()
  180. }