Add simple currency symbol support for Dart Intl
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120456447
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 959149f..0d6af5d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
 ## 0.13.0
  * Add support for compact number formats ("1.2K") and for significant digits in
    number formats.
+ * Add a NumberFormat.simpleCurrency constructor which will attempt to
+   automatically determine the currency symbol. Very simple implementation but
+   can be expanded to be per-locale.
 
 ## 0.12.7+1
  * Change the signature for args and examples in Intl.plural/gender/select to
diff --git a/lib/src/intl/number_format.dart b/lib/src/intl/number_format.dart
index 62a37ea..fb57102 100644
--- a/lib/src/intl/number_format.dart
+++ b/lib/src/intl/number_format.dart
@@ -239,6 +239,228 @@
       : this._forPattern(locale, (x) => x.CURRENCY_PATTERN,
             name: name, currencySymbol: symbol, decimalDigits: decimalDigits);
 
+  /// Creates a [NumberFormat] for currencies, using the simple symbol for the
+  /// currency if one is available (e.g. $, €), so it should only be used if the
+  /// short currency symbol will be unambiguous.
+  ///
+  /// If [locale] is not specified, it will use the current default locale.
+  ///
+  /// If [name] is specified, the currency with that ISO 4217 name will be used.
+  /// Otherwise we will use the default currency name for the current locale. We
+  /// will assume that the symbol for this is well known in the locale and
+  /// unambiguous. If you format CAD in an en_US locale using this format it
+  /// will display as "$", which may be confusing to the user.
+  ///
+  /// If [decimalDigits] is specified, numbers will format with that many digits
+  /// after the decimal place. If it's not, they will use the default for the
+  /// currency in [name], and the default currency for [locale] if the currency
+  /// name is not specified. e.g.
+  ///       new NumberFormat.simpleCurrency(name: 'USD', decimalDigits: 7)
+  /// will format with 7 decimal digits, because that's what we asked for. But
+  ///       new NumberFormat.simpleCurrency(locale: 'en_US', name: 'JPY')
+  /// will format with zero, because that's the default for JPY, and the
+  /// currency's default takes priority over the locale's default.
+  ///       new NumberFormat.simpleCurrency(locale: 'en_US')
+  /// will format with two, which is the default for that locale.
+  factory NumberFormat.simpleCurrency(
+      {String locale, String name, int decimalDigits}) {
+    return new NumberFormat._forPattern(locale, (x) => x.CURRENCY_PATTERN,
+        name: name,
+        currencySymbol: _simpleCurrencySymbols[name] ?? name,
+        decimalDigits: decimalDigits);
+  }
+
+  /// Returns the simple currency symbol for given currency code, or
+  /// [currencyCode] if no simple symbol is listed.
+  ///
+  /// The simple currency symbol is generally short, and the same or related to
+  /// what is used in countries having the currency as an official symbol. It
+  /// may be a symbol character, or may have letters, or both. It may be
+  /// different according to the locale: for example, for an Arabic locale it
+  /// may consist of Arabic letters, but for a French locale consist of Latin
+  /// letters. It will not be unique: for example, "$" can appear for both USD
+  /// and CAD.
+  ///
+  /// (The current implementation is the same for all locales, but this is
+  /// temporary and callers shouldn't rely on it.)
+  String simpleCurrencySymbol(String currencyCode) =>
+      _simpleCurrencySymbols[currencyCode] ?? currencyCode;
+
+  /// A map from currency names to the simple name/symbol.
+  ///
+  /// The simple currency symbol is generally short, and the same or related to
+  /// what is used in countries having the currency as an official symbol. It
+  /// may be a symbol character, or may have letters, or both. It may be
+  /// different according to the locale: for example, for an Arabic locale it
+  /// may consist of Arabic letters, but for a French locale consist of Latin
+  /// letters. It will not be unique: for example, "$" can appear for both USD
+  /// and CAD.
+  ///
+  /// (The current implementation is the same for all locales, but this is
+  /// temporary and callers shouldn't rely on it.)
+  static Map<String, String> _simpleCurrencySymbols = {
+    "AFN": "Af.",
+    "TOP": r"T$",
+    "MGA": "Ar",
+    "THB": "\u0e3f",
+    "PAB": "B/.",
+    "ETB": "Birr",
+    "VEF": "Bs",
+    "BOB": "Bs",
+    "GHS": "GHS",
+    "CRC": "\u20a1",
+    "NIO": r"C$",
+    "GMD": "GMD",
+    "MKD": "din",
+    "BHD": "din",
+    "DZD": "din",
+    "IQD": "din",
+    "JOD": "din",
+    "KWD": "din",
+    "LYD": "din",
+    "RSD": "din",
+    "TND": "din",
+    "AED": "dh",
+    "MAD": "dh",
+    "STD": "Db",
+    "BSD": r"$",
+    "FJD": r"$",
+    "GYD": r"$",
+    "KYD": r"$",
+    "LRD": r"$",
+    "SBD": r"$",
+    "SRD": r"$",
+    "AUD": r"$",
+    "BBD": r"$",
+    "BMD": r"$",
+    "BND": r"$",
+    "BZD": r"$",
+    "CAD": r"$",
+    "HKD": r"$",
+    "JMD": r"$",
+    "NAD": r"$",
+    "NZD": r"$",
+    "SGD": r"$",
+    "TTD": r"$",
+    "TWD": r"NT$",
+    "USD": r"$",
+    "XCD": r"$",
+    "VND": "\u20ab",
+    "AMD": "Dram",
+    "CVE": "CVE",
+    "EUR": "\u20ac",
+    "AWG": "Afl.",
+    "HUF": "Ft",
+    "BIF": "FBu",
+    "CDF": "FrCD",
+    "CHF": "CHF",
+    "DJF": "Fdj",
+    "GNF": "FG",
+    "RWF": "RF",
+    "XOF": "CFA",
+    "XPF": "FCFP",
+    "KMF": "CF",
+    "XAF": "FCFA",
+    "HTG": "HTG",
+    "PYG": "Gs",
+    "UAH": "\u20b4",
+    "PGK": "PGK",
+    "LAK": "\u20ad",
+    "CZK": "K\u010d",
+    "SEK": "kr",
+    "ISK": "kr",
+    "DKK": "kr",
+    "NOK": "kr",
+    "HRK": "kn",
+    "MWK": "MWK",
+    "ZMK": "ZWK",
+    "AOA": "Kz",
+    "MMK": "K",
+    "GEL": "GEL",
+    "LVL": "Ls",
+    "ALL": "Lek",
+    "HNL": "L",
+    "SLL": "SLL",
+    "MDL": "MDL",
+    "RON": "RON",
+    "BGN": "lev",
+    "SZL": "SZL",
+    "TRY": "TL",
+    "LTL": "Lt",
+    "LSL": "LSL",
+    "AZN": "man.",
+    "BAM": "KM",
+    "MZN": "MTn",
+    "NGN": "\u20a6",
+    "ERN": "Nfk",
+    "BTN": "Nu.",
+    "MRO": "MRO",
+    "MOP": "MOP",
+    "CUP": r"$",
+    "CUC": r"$",
+    "ARS": r"$",
+    "CLF": "UF",
+    "CLP": r"$",
+    "COP": r"$",
+    "DOP": r"$",
+    "MXN": r"$",
+    "PHP": "\u20b1",
+    "UYU": r"$",
+    "FKP": "£",
+    "GIP": "£",
+    "SHP": "£",
+    "EGP": "E£",
+    "LBP": "L£",
+    "SDG": "SDG",
+    "SSP": "SSP",
+    "GBP": "£",
+    "SYP": "£",
+    "BWP": "P",
+    "GTQ": "Q",
+    "ZAR": "R",
+    "BRL": r"R$",
+    "OMR": "Rial",
+    "QAR": "Rial",
+    "YER": "Rial",
+    "IRR": "Rial",
+    "KHR": "Riel",
+    "MYR": "RM",
+    "SAR": "Rial",
+    "BYR": "BYR",
+    "RUB": "руб.",
+    "MUR": "Rs",
+    "SCR": "SCR",
+    "LKR": "Rs",
+    "NPR": "Rs",
+    "INR": "\u20b9",
+    "PKR": "Rs",
+    "IDR": "Rp",
+    "ILS": "\u20aa",
+    "KES": "Ksh",
+    "SOS": "SOS",
+    "TZS": "TSh",
+    "UGX": "UGX",
+    "PEN": "S/.",
+    "KGS": "KGS",
+    "UZS": "so\u02bcm",
+    "TJS": "Som",
+    "BDT": "\u09f3",
+    "WST": "WST",
+    "KZT": "\u20b8",
+    "MNT": "\u20ae",
+    "VUV": "VUV",
+    "KPW": "\u20a9",
+    "KRW": "\u20a9",
+    "JPY": "¥",
+    "CNY": "¥",
+    "PLN": "z\u0142",
+    "MVR": "Rf",
+    "NLG": "NAf",
+    "ZMW": "ZK",
+    "ANG": "ƒ",
+    "TMT": "TMT",
+  };
+
   /// Create a number format that prints in a pattern we get from
   /// the [getPattern] function using the locale [locale].
   NumberFormat._forPattern(String locale, _PatternGetter getPattern,
@@ -377,7 +599,11 @@
   /// Helper to round a number which might not be num.
   _round(number) {
     if (number is num) {
-      return number.round();
+      if (number.isInfinite) {
+        return _maxInt;
+      } else {
+        return number.round();
+      }
     } else if (number.remainder(1) == 0) {
       // Not a normal number, but int-like, e.g. Int64
       return number;
@@ -697,6 +923,7 @@
   _NumberParser(this.format, text)
       : this.text = text,
         this.input = new _Stream(text) {
+    scale = format._internalMultiplier;
     value = parse();
   }
 
@@ -797,6 +1024,16 @@
   /// that's not a digit and doesn't have a replacement, then we're done
   /// and the number is probably invalid.
   void processNonDigit() {
+    // It might just be a prefix that we haven't skipped. We don't want to
+    // skip them initially because they might also be semantically meaningful,
+    // e.g. leading %. So we allow them through the loop, but only once.
+    var foundAnInterpretation = false;
+    if (input.index == 0 && !prefixesSkipped) {
+      prefixesSkipped = true;
+      checkPrefixes(skip: true);
+      foundAnInterpretation = true;
+    }
+
     for (var key in replacements.keys) {
       if (input.startsWith(key)) {
         _normalized.write(replacements[key]());
@@ -804,13 +1041,8 @@
         return;
       }
     }
-    // It might just be a prefix that we haven't skipped. We don't want to
-    // skip them initially because they might also be semantically meaningful,
-    // e.g. leading %. So we allow them through the loop, but only once.
-    if (input.index == 0 && !prefixesSkipped) {
-      prefixesSkipped = true;
-      checkPrefixes(skip: true);
-    } else {
+    // We haven't found either of these things, this seems invalid.
+    if (!foundAnInterpretation) {
       done = true;
     }
   }
@@ -843,6 +1075,9 @@
   /// Parse the number portion of the input, i.e. not any prefixes or suffixes,
   /// and assuming NaN and Infinity are already handled.
   num parseNumber(_Stream input) {
+    if (gotNegative) {
+      _normalized.write('-');
+    }
     while (!done && !input.atEnd()) {
       int digit = asDigit(input.peek());
       if (digit != null) {
diff --git a/pubspec.yaml b/pubspec.yaml
index d0faf60..e774afe 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: intl
-version: 0.13.0
+version: 0.13.0-dev
 author: Dart Team <misc@dartlang.org>
 description: Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
 homepage: https://github.com/dart-lang/intl
diff --git a/test/number_format_test.dart b/test/number_format_test.dart
index 652cc14..d7141e5 100644
--- a/test/number_format_test.dart
+++ b/test/number_format_test.dart
@@ -215,6 +215,8 @@
     formatted = tnd.format(amount);
     expect(formatted, digitsCheck[3]);
   });
+
+  testSimpleCurrencySymbols();
 }
 
 void testAgainstIcu(locale, List<NumberFormat> testFormats, list) {
@@ -245,3 +247,38 @@
     }
   });
 }
+
+testSimpleCurrencySymbols() {
+  var currencies = ['USD', 'CAD', 'EUR', 'CRC'];
+  //  Note that these print using the simple symbol as if we were in a
+  // a locale where that currency symbol is well understood. So we
+  // expect Canadian dollars printed as $, even though our locale is
+  // en_US, and this would confuse users.
+  var simple = currencies.map((currency) =>
+      new NumberFormat.simpleCurrency(locale: 'en_US', name: currency));
+  var expectedSimple = [r'$', r'$', '\u20ac', '\u20a1'];
+  // These will always print as the global name, regardless of locale
+  var global = currencies.map(
+      (currency) => new NumberFormat.currency(locale: 'en_US', name: currency));
+  var expectedGlobal = currencies;
+
+  testCurrencySymbolsFor(expectedGlobal, global, "global");
+  testCurrencySymbolsFor(expectedSimple, simple, "simple");
+}
+
+testCurrencySymbolsFor(expected, formats, name) {
+  var amount = 1000000.32;
+  new Map.fromIterables(expected, formats)
+      .forEach((expected, NumberFormat format) {
+    test("Test $name ${format.currencyName}", () {
+      // We have to allow for currencies with different fraction digits, e.g. CRC.
+      var maxDigits = format.maximumFractionDigits;
+      var rounded = maxDigits == 0 ? amount.round() : amount;
+      var fractionDigits = (amount - rounded) < 0.00001 ? '.32' : '';
+      var formatted = format.format(rounded);
+      expect(formatted, "${expected}1,000,000$fractionDigits");
+      var parsed = format.parse(formatted);
+      expect(parsed, rounded);
+    });
+  });
+}