Proposal: New API to format/parse currency amounts (JB:2825)

Alan S Liu <[email protected]> Thu, 15 Apr 2004 11:22:54 -0700
Newsgroups gmane.comp.lib.icu.general
Message-ID <OF78033C1D.F7FE6222-ON88256E77.0064A735-88256E77.0064FA23@us.ibm.com>
NOTE: This is a proposal to add new API to format and parse currencies. 
This is distinct from existing currency format capabilities because it 
allows any currency to be parsed, not just the currency that the formatter 
is set to. It also allows any currency to be formatted, although this is 
already possible using the existing setCurrency() API.

===

Currency Parsing (and Formatting) in ICU 3.0

Jitterbug: 2825 (http://www.jtcsv.com/cgibin/icu-bugs?findid=2825)

Java: NumberFormat
------------------

  public final String format(CurrencyAmount currAmt) {...}
 
  public StringBuffer format(CurrencyAmount currAmt,
                             StringBuffer toAppendTo,
                             FieldPosition pos) {...}

  public CurrencyAmount parseCurrency(String text, ParsePosition pos) 
{...}
 
  public CurrencyAmount parseCurrency(String text) throws ParseException 
{...}

Note that parsing for currency requires a separate method, not an
overload of parse.  Overloading doesn't work because it will break
existing code.

These methods will be overridden as appropriate in DecimalFormat, but
probably *not* in RuleBasedNumberFormat.  The overrides will do what you
expect.  The default implementations in NumberFormat will be "dumb"--that
is, they will call through to the usual format and parse methods, and
handle the currency using existing API.

Java: CurrencyAmount
--------------------

Here's the version I have coded up now.  You need getAmount so you can
do things like if (currencyAmount.getAmount() instanceof BigDecimal) {
... }.

public class CurrencyAmount extends Number {
 
  public CurrencyAmount(Number amount, Currency currency) {...}

  public Number getNumber() {...}

  public Currency getCurrency() {...}
 
  public int intValue() { return amount.intValue(); }
 
  public long longValue() { return amount.longValue(); }
 
  public float floatValue() { return amount.floatValue(); }
 
  public double doubleValue() { return amount.doubleValue(); }
}

This is an immutable object.  See Mark's papers and other Java docs for
this design pattern.

C++: Formattable
----------------

  /**
   * Creates a Formattable for a currency amount.
   * @param n the numeric value
   * @param currency the currency code
   * @draft ICU 3.0
   */
  Formattable(double n, const UChar* currency);

  /**
   * Creates a Formattable for a currency amount.
   * @param n the numeric value
   * @param currency the currency code
   * @draft ICU 3.0
   */
  Formattable(int32_t n, const UChar* currency);

  /**
   * Creates a Formattable for a currency amount.
   * @param n the numeric value
   * @param currency the currency code
   * @draft ICU 3.0
   */
  Formattable(int64_t n, const UChar* currency);
 
  /**
   * Returns the currency of this object, or NULL if this object has
   * no associated currency.  Any type may have a currency, although
   * this is intended for use with numeric types.
   * @return a null-terminated 3-letter ISO 4217 code, or NULL
   * @draft ICU 3.0
   */
  const UChar*    getCurrency() const;

  /**
   * Sets the currency of this object.  Only numeric types may have
   * a currency.  If isoCode is NULL then the currency is removed
   * from this object.  Any type may have a currency, although
   * this is intended for use with numeric types.
   * @param currency a null-terminated 3-letter ISO 4217 code, or NULL
   * @draft ICU 3.0
   */
  void            setCurrency(const UChar* currency);

The Formattable API gets two new methods and three new constructors. 
The currency is stored separately, alongside whatever other value the
Formattable contains.

Instead of adding 3 new constructors, we could add a second parameter to
the existing parameters, and default it to NULL:

  /**
   * Creates a Formattable object with a double number.
   * @param d the double number.
   * @stable ICU 2.0
   */
  Formattable(double d, const UChar* currency = NULL);

This should be a compatible change across a recompile.  However, I'm
going to use the separate constructors, since it's a small amount of
code, and it is more flexible in the future.

C++: NumberFormat
-----------------

  virtual Formattable& parseCurrency(const UnicodeString& text,
                                     Formattable& result,
                                     ParsePosition& pos) const;

  Formattable& parseCurrency(const UnicodeString& text,
                             Formattable& result,
                             UErrorCode& status) const;

Because there is no new type in C++ (the currency amount is stored in
Formattable) the API changes for C++ NumberFormat are fewer than for
Java NumberFormat.

C: unum
-------

The unum header defines separate calls to parse and format 32-bit
integers, 64-bit integers, and doubles.  For the 3.0 release I will add
API to parse and format a double together with a currency.  If we decide
to add API for integers with currencies, we can easily do so later, but
a double is most useful for currency.

/**
 * Format a double currency amount using a UNumberFormat.
 * The double will be formatted according to the UNumberFormat's locale.
 * @param fmt the formatter to use
 * @param number the number to format
 * @param currency the 3-letter null-terminated ISO 4217 currency code
 * @param result a pointer to the buffer to receive the formatted number
 * @param resultLength the maximum number of UChars to write to result
 * @param pos a pointer to a UFieldPosition.  On input,
 * position->field is read.  On output, position->beginIndex and
 * position->endIndex indicate the beginning and ending indices of
 * field number position->field, if such a field exists.  This
 * parameter may be NULL, in which case it is ignored.
 * @param status a pointer to an input-output UErrorCode
 * @return the total buffer size needed; if greater than resultLength,
 * the output was truncated.
 * @see unum_formatDouble
 * @see unum_parseDoubleCurrency
 * @see UFieldPosition
 * @draft ICU 3.0
 */
U_DRAFT int32_t U_EXPORT2 
unum_formatDoubleCurrency(const UNumberFormat* fmt,
                          double number,
                          UChar* currency,
                          UChar* result,
                          int32_t resultLength,
                          UFieldPosition* pos, /* ignored if 0 */
                          UErrorCode* status);

/**
 * Parse a string into a double and a currency using a UNumberFormat.
 * The string will be parsed according to the UNumberFormat's locale.
 * @param fmt the formatter to use
 * @param text the text to parse
 * @param textLength the length of text, or -1 if null-terminated
 * @param parsePos a pointer to an offset index into text at which to
 * begin parsing. On output, *parsePos will point after the last
 * parsed character.  This parameter may be 0, in which case parsing
 * begins at offset 0.
 * @param currency a pointer to the buffer to receive the parsed null-
 * terminated currency. This buffer must have a capacity of at least
 * 4 UChars.
 * @param status a pointer to an input-output UErrorCode
 * @return the parsed double
 * @see unum_parseDouble
 * @see unum_formatDoubleCurrency
 * @draft ICU 3.0
 */
U_DRAFT double U_EXPORT2 
unum_parseDoubleCurrency(const UNumberFormat* fmt,
                         const UChar* text,
                         int32_t textLength,
                         int32_t* parsePos, /* 0 = start */
                         UChar* currency,
                         UErrorCode *status);

[Alan S Liu/San Jose/IBM@IBMUS; [email protected];; IBM Globalization; 
5600 Cottle Road; San Jose, CA 95193;; (408) 256-3155]