Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(365)

Side by Side Diff: packages/intl/lib/src/intl/number_format.dart

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of intl; 5 part of intl;
6 6
7 /** 7 /// The function that we pass internally to NumberFormat to get
8 * Provides the ability to format a number in a locale-specific way. The 8 /// the appropriate pattern (e.g. currency)
9 * format is specified as a pattern using a subset of the ICU formatting 9 typedef String _PatternGetter(NumberSymbols symbols);
10 * patterns. 10
11 * 11 /// Provides the ability to format a number in a locale-specific way. The
12 * - `0` A single digit 12 /// format is specified as a pattern using a subset of the ICU formatting
13 * - `#` A single digit, omitted if the value is zero 13 /// patterns.
14 * - `.` Decimal separator 14 ///
15 * - `-` Minus sign 15 /// - `0` A single digit
16 * - `,` Grouping separator 16 /// - `#` A single digit, omitted if the value is zero
17 * - `E` Separates mantissa and expontent 17 /// - `.` Decimal separator
18 * - `+` - Before an exponent, indicates it should be prefixed with a plus sign. 18 /// - `-` Minus sign
19 * - `%` - In prefix or suffix, multiply by 100 and show as percentage 19 /// - `,` Grouping separator
20 * - `‰ (\u2030)` In prefix or suffix, multiply by 1000 and show as per mille 20 /// - `E` Separates mantissa and expontent
21 * - `¤ (\u00A4)` Currency sign, replaced by currency name 21 /// - `+` - Before an exponent, to say it should be prefixed with a plus sign.
22 * - `'` Used to quote special characters 22 /// - `%` - In prefix or suffix, multiply by 100 and show as percentage
23 * - `;` Used to separate the positive and negative patterns if both are present 23 /// - `‰ (\u2030)` In prefix or suffix, multiply by 1000 and show as per mille
24 * 24 /// - `¤ (\u00A4)` Currency sign, replaced by currency name
25 * For example, 25 /// - `'` Used to quote special characters
26 * var f = new NumberFormat("###.0#", "en_US"); 26 /// - `;` Used to separate the positive and negative patterns (if both present)
27 * print(f.format(12.345)); 27 ///
28 * ==> 12.34 28 /// For example,
29 * If the locale is not specified, it will default to the current locale. If 29 /// var f = new NumberFormat("###.0#", "en_US");
30 * the format is not specified it will print in a basic format with at least 30 /// print(f.format(12.345));
31 * one integer digit and three fraction digits. 31 /// ==> 12.34
32 * 32 /// If the locale is not specified, it will default to the current locale. If
33 * There are also standard patterns available via the special constructors. e.g. 33 /// the format is not specified it will print in a basic format with at least
34 * var percent = new NumberFormat.percentFormat("ar"); 34 /// one integer digit and three fraction digits.
35 * var eurosInUSFormat = new NumberFormat.currencyPattern("en_US", "€"); 35 ///
36 * There are four such constructors: decimalFormat, percentFormat, 36 /// There are also standard patterns available via the special constructors.
37 * scientificFormat and currencyFormat. However, at the moment, 37 /// e.g.
38 * scientificFormat prints only as equivalent to "#E0" and does not take 38 /// var percent = new NumberFormat.percentFormat("ar");
39 * into account significant digits. The currencyFormat will default to the 39 /// var eurosInUSFormat = new NumberFormat.currency(locale: "en_US",
40 * three-letter name of the currency if no explicit name/symbol is provided. 40 /// symbol: "€");
41 */ 41 /// There are four such constructors: decimalFormat, percentFormat,
42 /// scientificFormat and currencyFormat. However, at the moment,
43 /// scientificFormat prints only as equivalent to "#E0" and does not take
44 /// into account significant digits. The currencyFormat will default to the
45 /// three-letter name of the currency if no explicit name/symbol is provided.
42 class NumberFormat { 46 class NumberFormat {
43 /** Variables to determine how number printing behaves. */ 47 /// Variables to determine how number printing behaves.
44 // TODO(alanknight): If these remain as variables and are set based on the 48 // TODO(alanknight): If these remain as variables and are set based on the
45 // pattern, can we make them final? 49 // pattern, can we make them final?
46 String _negativePrefix = '-'; 50 String _negativePrefix = '-';
47 String _positivePrefix = ''; 51 String _positivePrefix = '';
48 String _negativeSuffix = ''; 52 String _negativeSuffix = '';
49 String _positiveSuffix = ''; 53 String _positiveSuffix = '';
50 /** 54
51 * How many numbers in a group when using punctuation to group digits in 55 /// How many numbers in a group when using punctuation to group digits in
52 * large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits 56 /// large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits
53 * between commas. 57 /// between commas.
54 */
55 int _groupingSize = 3; 58 int _groupingSize = 3;
56 /** 59
57 * In some formats the last grouping size may be different than previous 60 /// In some formats the last grouping size may be different than previous
58 * ones, e.g. Hindi. 61 /// ones, e.g. Hindi.
59 */
60 int _finalGroupingSize = 3; 62 int _finalGroupingSize = 3;
61 /** 63
62 * Set to true if the format has explicitly set the grouping size. 64 /// Set to true if the format has explicitly set the grouping size.
63 */
64 bool _groupingSizeSetExplicitly = false; 65 bool _groupingSizeSetExplicitly = false;
65 bool _decimalSeparatorAlwaysShown = false; 66 bool _decimalSeparatorAlwaysShown = false;
66 bool _useSignForPositiveExponent = false; 67 bool _useSignForPositiveExponent = false;
67 bool _useExponentialNotation = false; 68 bool _useExponentialNotation = false;
68 69
70 /// Explicitly store if we are a currency format, and so should use the
71 /// appropriate number of decimal digits for a currency.
72 // TODO(alanknight): Handle currency formats which are specified in a raw
73 /// pattern, not using one of the currency constructors.
74 bool _isForCurrency = false;
75
69 int maximumIntegerDigits = 40; 76 int maximumIntegerDigits = 40;
70 int minimumIntegerDigits = 1; 77 int minimumIntegerDigits = 1;
71 int maximumFractionDigits = 3; 78 int maximumFractionDigits = 3;
72 int minimumFractionDigits = 0; 79 int minimumFractionDigits = 0;
73 int minimumExponentDigits = 0; 80 int minimumExponentDigits = 0;
74 81 int _significantDigits = 0;
75 /** 82
76 * For percent and permille, what are we multiplying by in order to 83 /// How many significant digits should we print.
77 * get the printed value, e.g. 100 for percent. 84 ///
78 */ 85 /// Note that if significantDigitsInUse is the default false, this
86 /// will be ignored.
87 int get significantDigits => _significantDigits;
88 set significantDigits(int x) {
89 _significantDigits = x;
90 significantDigitsInUse = true;
91 }
92
93 bool significantDigitsInUse = false;
94
95 /// For percent and permille, what are we multiplying by in order to
96 /// get the printed value, e.g. 100 for percent.
79 int get _multiplier => _internalMultiplier; 97 int get _multiplier => _internalMultiplier;
80 set _multiplier(int x) { 98 set _multiplier(int x) {
81 _internalMultiplier = x; 99 _internalMultiplier = x;
82 _multiplierDigits = (log(_multiplier) / LN10).round(); 100 _multiplierDigits = (log(_multiplier) / LN10).round();
83 } 101 }
102
84 int _internalMultiplier = 1; 103 int _internalMultiplier = 1;
85 104
86 /** How many digits are there in the [_multiplier]. */ 105 /// How many digits are there in the [_multiplier].
87 int _multiplierDigits = 0; 106 int _multiplierDigits = 0;
88 107
89 /** 108 /// Stores the pattern used to create this format. This isn't used, but
90 * Stores the pattern used to create this format. This isn't used, but 109 /// is helpful in debugging.
91 * is helpful in debugging.
92 */
93 String _pattern; 110 String _pattern;
94 111
95 /** The locale in which we print numbers. */ 112 /// The locale in which we print numbers.
96 final String _locale; 113 final String _locale;
97 114
98 /** Caches the symbols used for our locale. */ 115 /// Caches the symbols used for our locale.
99 NumberSymbols _symbols; 116 NumberSymbols _symbols;
100 117
101 /** The name (or symbol) of the currency to print. */ 118 /// The name of the currency to print, in ISO 4217 form.
102 String currencyName; 119 String currencyName;
103 120
104 /** 121 /// The symbol to be used when formatting this as currency.
105 * Transient internal state in which to build up the result of the format 122 ///
106 * operation. We can have this be just an instance variable because Dart is 123 /// For example, "$", "US$", or "€".
107 * single-threaded and unless we do an asynchronous operation in the process 124 String _currencySymbol;
108 * of formatting then there will only ever be one number being formatted 125
109 * at a time. In languages with threads we'd need to pass this on the stack. 126 /// The symbol to be used when formatting this as currency.
110 */ 127 ///
128 /// For example, "$", "US$", or "€".
129 String get currencySymbol => _currencySymbol ?? currencyName;
130
131 /// The number of decimal places to use when formatting.
132 ///
133 /// If this is not explicitly specified in the constructor, then for
134 /// currencies we use the default value for the currency if the name is given,
135 /// otherwise we use the value from the pattern for the locale.
136 ///
137 /// So, for example,
138 /// new NumberFormat.currency(name: 'USD', decimalDigits: 7)
139 /// will format with 7 decimal digits, because that's what we asked for. But
140 /// new NumberFormat.currency(locale: 'en_US', name: 'JPY')
141 /// will format with zero, because that's the default for JPY, and the
142 /// currency's default takes priority over the locale's default.
143 /// new NumberFormat.currency(locale: 'en_US')
144 /// will format with two, which is the default for that locale.
145 ///
146 int get decimalDigits => _decimalDigits;
147
148 int _decimalDigits;
149
150 /// For currencies, the default number of decimal places to use in
151 /// formatting. Defaults to two for non-currencies or currencies where it's
152 /// not specified.
153 int get _defaultDecimalDigits =>
154 currencyFractionDigits[currencyName.toUpperCase()] ??
155 currencyFractionDigits['DEFAULT'];
156
157 /// If we have a currencyName, use the decimal digits for that currency,
158 /// unless we've explicitly specified some other number.
159 bool get _overridesDecimalDigits => decimalDigits != null || _isForCurrency;
160
161 /// Transient internal state in which to build up the result of the format
162 /// operation. We can have this be just an instance variable because Dart is
163 /// single-threaded and unless we do an asynchronous operation in the process
164 /// of formatting then there will only ever be one number being formatted
165 /// at a time. In languages with threads we'd need to pass this on the stack.
111 final StringBuffer _buffer = new StringBuffer(); 166 final StringBuffer _buffer = new StringBuffer();
112 167
113 /** 168 /// Create a number format that prints using [newPattern] as it applies in
114 * Create a number format that prints using [newPattern] as it applies in 169 /// [locale].
115 * [locale].
116 */
117 factory NumberFormat([String newPattern, String locale]) => 170 factory NumberFormat([String newPattern, String locale]) =>
118 new NumberFormat._forPattern(locale, (x) => newPattern); 171 new NumberFormat._forPattern(locale, (x) => newPattern);
119 172
120 /** Create a number format that prints as DECIMAL_PATTERN. */ 173 /// Create a number format that prints as DECIMAL_PATTERN.
121 NumberFormat.decimalPattern([String locale]) 174 NumberFormat.decimalPattern([String locale])
122 : this._forPattern(locale, (x) => x.DECIMAL_PATTERN); 175 : this._forPattern(locale, (x) => x.DECIMAL_PATTERN);
123 176
124 /** Create a number format that prints as PERCENT_PATTERN. */ 177 /// Create a number format that prints as PERCENT_PATTERN.
125 NumberFormat.percentPattern([String locale]) 178 NumberFormat.percentPattern([String locale])
126 : this._forPattern(locale, (x) => x.PERCENT_PATTERN); 179 : this._forPattern(locale, (x) => x.PERCENT_PATTERN);
127 180
128 /** Create a number format that prints as SCIENTIFIC_PATTERN. */ 181 /// Create a number format that prints as SCIENTIFIC_PATTERN.
129 NumberFormat.scientificPattern([String locale]) 182 NumberFormat.scientificPattern([String locale])
130 : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN); 183 : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN);
131 184
132 /** 185 /// A regular expression to validate currency names are exactly three
133 * Create a number format that prints as CURRENCY_PATTERN. If provided, 186 /// alphabetic characters.
134 * use [nameOrSymbol] in place of the default currency name. e.g. 187 static final _checkCurrencyName = new RegExp(r'^[a-zA-Z]{3}$');
135 * var eurosInCurrentLocale = new NumberFormat 188
136 * .currencyPattern(Intl.defaultLocale, "€"); 189 /// Create a number format that prints as CURRENCY_PATTERN. (Deprecated:
137 */ 190 /// prefer NumberFormat.currency)
138 NumberFormat.currencyPattern([String locale, String nameOrSymbol]) 191 ///
139 : this._forPattern(locale, (x) => x.CURRENCY_PATTERN, nameOrSymbol); 192 /// If provided,
140 193 /// use [nameOrSymbol] in place of the default currency name. e.g.
141 /** 194 /// var eurosInCurrentLocale = new NumberFormat
142 * Create a number format that prints in a pattern we get from 195 /// .currencyPattern(Intl.defaultLocale, "€");
143 * the [getPattern] function using the locale [locale]. 196 @Deprecated("Use NumberFormat.currency")
144 */ 197 factory NumberFormat.currencyPattern(
145 NumberFormat._forPattern(String locale, Function getPattern, 198 [String locale, String currencyNameOrSymbol]) {
146 [this.currencyName]) 199 // If it looks like an iso4217 name, pass as name, otherwise as symbol.
147 : _locale = Intl.verifiedLocale(locale, localeExists) { 200 if (currencyNameOrSymbol != null &&
201 _checkCurrencyName.hasMatch(currencyNameOrSymbol)) {
202 return new NumberFormat.currency(
203 locale: locale, name: currencyNameOrSymbol);
204 } else {
205 return new NumberFormat.currency(
206 locale: locale, symbol: currencyNameOrSymbol);
207 }
208 }
209
210 /// Create a [NumberFormat] that formats using the locale's CURRENCY_PATTERN.
211 ///
212 /// If [locale] is not specified, it will use the current default locale.
213 ///
214 /// If [name] is specified, the currency with that ISO 4217 name will be used.
215 /// Otherwise we will use the default currency name for the current locale. If
216 /// no [symbol] is specified, we will use the currency name in the formatted
217 /// result. e.g.
218 /// var f = new NumberFormat.currency(locale: 'en_US', name: 'EUR')
219 /// will format currency like "EUR1.23". If we did not specify the name, it
220 /// would format like "USD1.23".
221 ///
222 /// If [symbol] is used, then that symbol will be used in formatting instead
223 /// of the name. e.g.
224 /// var eurosInCurrentLocale = new NumberFormat.currency(symbol: "€");
225 /// will format like "€1.23". Otherwise it will use the currency name.
226 /// If this is not explicitly specified in the constructor, then for
227 /// currencies we use the default value for the currency if the name is given,
228 /// otherwise we use the value from the pattern for the locale.
229 ///
230 /// If [decimalDigits] is specified, numbers will format with that many digits
231 /// after the decimal place. If it's not, they will use the default for the
232 /// currency in [name], and the default currency for [locale] if the currency
233 /// name is not specified. e.g.
234 /// new NumberFormat.currency(name: 'USD', decimalDigits: 7)
235 /// will format with 7 decimal digits, because that's what we asked for. But
236 /// new NumberFormat.currency(locale: 'en_US', name: 'JPY')
237 /// will format with zero, because that's the default for JPY, and the
238 /// currency's default takes priority over the locale's default.
239 /// new NumberFormat.currency(locale: 'en_US')
240 /// will format with two, which is the default for that locale.
241 // TODO(alanknight): Should we allow decimalDigits on other numbers.
242 NumberFormat.currency(
243 {String locale, String name, String symbol, int decimalDigits})
244 : this._forPattern(locale, (x) => x.CURRENCY_PATTERN,
245 name: name,
246 currencySymbol: symbol,
247 decimalDigits: decimalDigits,
248 isForCurrency: true);
249
250 /// Creates a [NumberFormat] for currencies, using the simple symbol for the
251 /// currency if one is available (e.g. $, €), so it should only be used if the
252 /// short currency symbol will be unambiguous.
253 ///
254 /// If [locale] is not specified, it will use the current default locale.
255 ///
256 /// If [name] is specified, the currency with that ISO 4217 name will be used.
257 /// Otherwise we will use the default currency name for the current locale. We
258 /// will assume that the symbol for this is well known in the locale and
259 /// unambiguous. If you format CAD in an en_US locale using this format it
260 /// will display as "$", which may be confusing to the user.
261 ///
262 /// If [decimalDigits] is specified, numbers will format with that many digits
263 /// after the decimal place. If it's not, they will use the default for the
264 /// currency in [name], and the default currency for [locale] if the currency
265 /// name is not specified. e.g.
266 /// new NumberFormat.simpleCurrency(name: 'USD', decimalDigits: 7)
267 /// will format with 7 decimal digits, because that's what we asked for. But
268 /// new NumberFormat.simpleCurrency(locale: 'en_US', name: 'JPY')
269 /// will format with zero, because that's the default for JPY, and the
270 /// currency's default takes priority over the locale's default.
271 /// new NumberFormat.simpleCurrency(locale: 'en_US')
272 /// will format with two, which is the default for that locale.
273 factory NumberFormat.simpleCurrency(
274 {String locale, String name, int decimalDigits}) {
275 return new NumberFormat._forPattern(locale, (x) => x.CURRENCY_PATTERN,
276 name: name,
277 computeCurrencySymbol: (format) =>
278 _simpleCurrencySymbols[format.currencyName] ?? format.currencyName,
279 decimalDigits: decimalDigits,
280 isForCurrency: true);
281 }
282
283 /// Returns the simple currency symbol for given currency code, or
284 /// [currencyCode] if no simple symbol is listed.
285 ///
286 /// The simple currency symbol is generally short, and the same or related to
287 /// what is used in countries having the currency as an official symbol. It
288 /// may be a symbol character, or may have letters, or both. It may be
289 /// different according to the locale: for example, for an Arabic locale it
290 /// may consist of Arabic letters, but for a French locale consist of Latin
291 /// letters. It will not be unique: for example, "$" can appear for both USD
292 /// and CAD.
293 ///
294 /// (The current implementation is the same for all locales, but this is
295 /// temporary and callers shouldn't rely on it.)
296 String simpleCurrencySymbol(String currencyCode) =>
297 _simpleCurrencySymbols[currencyCode] ?? currencyCode;
298
299 /// A map from currency names to the simple name/symbol.
300 ///
301 /// The simple currency symbol is generally short, and the same or related to
302 /// what is used in countries having the currency as an official symbol. It
303 /// may be a symbol character, or may have letters, or both. It may be
304 /// different according to the locale: for example, for an Arabic locale it
305 /// may consist of Arabic letters, but for a French locale consist of Latin
306 /// letters. It will not be unique: for example, "$" can appear for both USD
307 /// and CAD.
308 ///
309 /// (The current implementation is the same for all locales, but this is
310 /// temporary and callers shouldn't rely on it.)
311 static Map<String, String> _simpleCurrencySymbols = {
312 "AFN": "Af.",
313 "TOP": r"T$",
314 "MGA": "Ar",
315 "THB": "\u0e3f",
316 "PAB": "B/.",
317 "ETB": "Birr",
318 "VEF": "Bs",
319 "BOB": "Bs",
320 "GHS": "GHS",
321 "CRC": "\u20a1",
322 "NIO": r"C$",
323 "GMD": "GMD",
324 "MKD": "din",
325 "BHD": "din",
326 "DZD": "din",
327 "IQD": "din",
328 "JOD": "din",
329 "KWD": "din",
330 "LYD": "din",
331 "RSD": "din",
332 "TND": "din",
333 "AED": "dh",
334 "MAD": "dh",
335 "STD": "Db",
336 "BSD": r"$",
337 "FJD": r"$",
338 "GYD": r"$",
339 "KYD": r"$",
340 "LRD": r"$",
341 "SBD": r"$",
342 "SRD": r"$",
343 "AUD": r"$",
344 "BBD": r"$",
345 "BMD": r"$",
346 "BND": r"$",
347 "BZD": r"$",
348 "CAD": r"$",
349 "HKD": r"$",
350 "JMD": r"$",
351 "NAD": r"$",
352 "NZD": r"$",
353 "SGD": r"$",
354 "TTD": r"$",
355 "TWD": r"NT$",
356 "USD": r"$",
357 "XCD": r"$",
358 "VND": "\u20ab",
359 "AMD": "Dram",
360 "CVE": "CVE",
361 "EUR": "\u20ac",
362 "AWG": "Afl.",
363 "HUF": "Ft",
364 "BIF": "FBu",
365 "CDF": "FrCD",
366 "CHF": "CHF",
367 "DJF": "Fdj",
368 "GNF": "FG",
369 "RWF": "RF",
370 "XOF": "CFA",
371 "XPF": "FCFP",
372 "KMF": "CF",
373 "XAF": "FCFA",
374 "HTG": "HTG",
375 "PYG": "Gs",
376 "UAH": "\u20b4",
377 "PGK": "PGK",
378 "LAK": "\u20ad",
379 "CZK": "K\u010d",
380 "SEK": "kr",
381 "ISK": "kr",
382 "DKK": "kr",
383 "NOK": "kr",
384 "HRK": "kn",
385 "MWK": "MWK",
386 "ZMK": "ZWK",
387 "AOA": "Kz",
388 "MMK": "K",
389 "GEL": "GEL",
390 "LVL": "Ls",
391 "ALL": "Lek",
392 "HNL": "L",
393 "SLL": "SLL",
394 "MDL": "MDL",
395 "RON": "RON",
396 "BGN": "lev",
397 "SZL": "SZL",
398 "TRY": "TL",
399 "LTL": "Lt",
400 "LSL": "LSL",
401 "AZN": "man.",
402 "BAM": "KM",
403 "MZN": "MTn",
404 "NGN": "\u20a6",
405 "ERN": "Nfk",
406 "BTN": "Nu.",
407 "MRO": "MRO",
408 "MOP": "MOP",
409 "CUP": r"$",
410 "CUC": r"$",
411 "ARS": r"$",
412 "CLF": "UF",
413 "CLP": r"$",
414 "COP": r"$",
415 "DOP": r"$",
416 "MXN": r"$",
417 "PHP": "\u20b1",
418 "UYU": r"$",
419 "FKP": "£",
420 "GIP": "£",
421 "SHP": "£",
422 "EGP": "E£",
423 "LBP": "L£",
424 "SDG": "SDG",
425 "SSP": "SSP",
426 "GBP": "£",
427 "SYP": "£",
428 "BWP": "P",
429 "GTQ": "Q",
430 "ZAR": "R",
431 "BRL": r"R$",
432 "OMR": "Rial",
433 "QAR": "Rial",
434 "YER": "Rial",
435 "IRR": "Rial",
436 "KHR": "Riel",
437 "MYR": "RM",
438 "SAR": "Rial",
439 "BYR": "BYR",
440 "RUB": "руб.",
441 "MUR": "Rs",
442 "SCR": "SCR",
443 "LKR": "Rs",
444 "NPR": "Rs",
445 "INR": "\u20b9",
446 "PKR": "Rs",
447 "IDR": "Rp",
448 "ILS": "\u20aa",
449 "KES": "Ksh",
450 "SOS": "SOS",
451 "TZS": "TSh",
452 "UGX": "UGX",
453 "PEN": "S/.",
454 "KGS": "KGS",
455 "UZS": "so\u02bcm",
456 "TJS": "Som",
457 "BDT": "\u09f3",
458 "WST": "WST",
459 "KZT": "\u20b8",
460 "MNT": "\u20ae",
461 "VUV": "VUV",
462 "KPW": "\u20a9",
463 "KRW": "\u20a9",
464 "JPY": "Â¥",
465 "CNY": "Â¥",
466 "PLN": "z\u0142",
467 "MVR": "Rf",
468 "NLG": "NAf",
469 "ZMW": "ZK",
470 "ANG": "Æ’",
471 "TMT": "TMT",
472 };
473
474 /// Create a number format that prints in a pattern we get from
475 /// the [getPattern] function using the locale [locale].
476 ///
477 /// The [currencySymbol] can either be specified directly, or we can pass a
478 /// function [computeCurrencySymbol] that will compute it later, given other
479 /// information, typically the verified locale.
480 NumberFormat._forPattern(String locale, _PatternGetter getPattern,
481 {String name,
482 String currencySymbol,
483 String computeCurrencySymbol(NumberFormat),
484 int decimalDigits,
485 bool isForCurrency: false})
486 : _locale = Intl.verifiedLocale(locale, localeExists),
487 _isForCurrency = isForCurrency {
488 this._currencySymbol = currencySymbol;
489 this._decimalDigits = decimalDigits;
148 _symbols = numberFormatSymbols[_locale]; 490 _symbols = numberFormatSymbols[_locale];
149 if (currencyName == null) { 491 _localeZero = _symbols.ZERO_DIGIT.codeUnitAt(0);
150 currencyName = _symbols.DEF_CURRENCY_CODE; 492 _zeroOffset = _localeZero - _zero;
493 _negativePrefix = _symbols.MINUS_SIGN;
494 currencyName = name ?? _symbols.DEF_CURRENCY_CODE;
495 if (this._currencySymbol == null && computeCurrencySymbol != null) {
496 this._currencySymbol = computeCurrencySymbol(this);
151 } 497 }
152 _setPattern(getPattern(_symbols)); 498 _setPattern(getPattern(_symbols));
153 } 499 }
154 500
155 /** 501 /// A number format for compact representations, e.g. "1.2M" instead
156 * Return the locale code in which we operate, e.g. 'en_US' or 'pt'. 502 /// of "1,200,000".
157 */ 503 factory NumberFormat.compact({String locale}) {
504 return new _CompactNumberFormat(
505 locale: locale,
506 formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN);
507 }
508
509 /// A number format for "long" compact representations, e.g. "1.2 million"
510 /// instead of of "1,200,000".
511 factory NumberFormat.compactLong({String locale}) {
512 return new _CompactNumberFormat(
513 locale: locale,
514 formatType: _CompactFormatType.COMPACT_DECIMAL_LONG_PATTERN);
515 }
516
517 /// A number format for compact currency representations, e.g. "$1.2M" instead
518 /// of "$1,200,000", and which will automatically determine a currency symbol
519 /// based on the currency name or the locale. See
520 /// [NumberFormat.simpleCurrency].
521 factory NumberFormat.compactSimpleCurrency({String locale, String name}) {
522 return new _CompactNumberFormat(
523 locale: locale,
524 formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN,
525 name: name,
526 getPattern: (symbols) => symbols.CURRENCY_PATTERN,
527 computeCurrencySymbol: (format) =>
528 _simpleCurrencySymbols[format.currencyName] ?? format.currencyName,
529 isForCurrency: true);
530 }
531
532 /// A number format for compact currency representations, e.g. "$1.2M" instead
533 /// of "$1,200,000".
534 factory NumberFormat.compactCurrency(
535 {String locale, String name, String symbol, int decimalDigits}) {
536 return new _CompactNumberFormat(
537 locale: locale,
538 formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN,
539 name: name,
540 getPattern: (symbols) => symbols.CURRENCY_PATTERN,
541 currencySymbol: symbol,
542 decimalDigits: decimalDigits,
543 isForCurrency: true);
544 }
545
546 /// Return the locale code in which we operate, e.g. 'en_US' or 'pt'.
158 String get locale => _locale; 547 String get locale => _locale;
159 548
160 /** 549 /// Return true if the locale exists, or if it is null. The null case
161 * Return true if the locale exists, or if it is null. The null case 550 /// is interpreted to mean that we use the default locale.
162 * is interpreted to mean that we use the default locale.
163 */
164 static bool localeExists(localeName) { 551 static bool localeExists(localeName) {
165 if (localeName == null) return false; 552 if (localeName == null) return false;
166 return numberFormatSymbols.containsKey(localeName); 553 return numberFormatSymbols.containsKey(localeName);
167 } 554 }
168 555
169 /** 556 /// Return the symbols which are used in our locale. Cache them to avoid
170 * Return the symbols which are used in our locale. Cache them to avoid 557 /// repeated lookup.
171 * repeated lookup.
172 */
173 NumberSymbols get symbols => _symbols; 558 NumberSymbols get symbols => _symbols;
174 559
175 /** 560 /// Format [number] according to our pattern and return the formatted string.
176 * Format [number] according to our pattern and return the formatted string.
177 */
178 String format(number) { 561 String format(number) {
179 if (_isNaN(number)) return symbols.NAN; 562 if (_isNaN(number)) return symbols.NAN;
180 if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}"; 563 if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}";
181 564
182 _add(_signPrefix(number)); 565 _add(_signPrefix(number));
183 _formatNumber(number.abs()); 566 _formatNumber(number.abs());
184 _add(_signSuffix(number)); 567 _add(_signSuffix(number));
185 568
186 var result = _buffer.toString(); 569 var result = _buffer.toString();
187 _buffer.clear(); 570 _buffer.clear();
188 return result; 571 return result;
189 } 572 }
190 573
191 /** 574 /// Parse the number represented by the string. If it's not
192 * Parse the number represented by the string. If it's not 575 /// parseable, throws a [FormatException].
193 * parseable, throws a [FormatException].
194 */
195 num parse(String text) => new _NumberParser(this, text).value; 576 num parse(String text) => new _NumberParser(this, text).value;
196 577
197 /** 578 /// Format the main part of the number in the form dictated by the pattern.
198 * Format the main part of the number in the form dictated by the pattern.
199 */
200 void _formatNumber(number) { 579 void _formatNumber(number) {
201 if (_useExponentialNotation) { 580 if (_useExponentialNotation) {
202 _formatExponential(number); 581 _formatExponential(number);
203 } else { 582 } else {
204 _formatFixed(number); 583 _formatFixed(number);
205 } 584 }
206 } 585 }
207 586
208 /** Format the number in exponential notation. */ 587 /// Format the number in exponential notation.
209 void _formatExponential(num number) { 588 void _formatExponential(num number) {
210 if (number == 0.0) { 589 if (number == 0.0) {
211 _formatFixed(number); 590 _formatFixed(number);
212 _formatExponent(0); 591 _formatExponent(0);
213 return; 592 return;
214 } 593 }
215 594
216 var exponent = (log(number) / log(10)).floor(); 595 var exponent = (log(number) / LN10).floor();
217 var mantissa = number / pow(10.0, exponent); 596 var mantissa = number / pow(10.0, exponent);
218 597
219 var minIntDigits = minimumIntegerDigits;
220 if (maximumIntegerDigits > 1 && 598 if (maximumIntegerDigits > 1 &&
221 maximumIntegerDigits > minimumIntegerDigits) { 599 maximumIntegerDigits > minimumIntegerDigits) {
222 // A repeating range is defined; adjust to it as follows. 600 // A repeating range is defined; adjust to it as follows.
223 // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; 601 // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3;
224 // -3,-4,-5=>-6, etc. This takes into account that the 602 // -3,-4,-5=>-6, etc. This takes into account that the
225 // exponent we have here is off by one from what we expect; 603 // exponent we have here is off by one from what we expect;
226 // it is for the format 0.MMMMMx10^n. 604 // it is for the format 0.MMMMMx10^n.
227 while ((exponent % maximumIntegerDigits) != 0) { 605 while ((exponent % maximumIntegerDigits) != 0) {
228 mantissa *= 10; 606 mantissa *= 10;
229 exponent--; 607 exponent--;
230 } 608 }
231 minIntDigits = 1;
232 } else { 609 } else {
233 // No repeating range is defined, use minimum integer digits. 610 // No repeating range is defined, use minimum integer digits.
234 if (minimumIntegerDigits < 1) { 611 if (minimumIntegerDigits < 1) {
235 exponent++; 612 exponent++;
236 mantissa /= 10; 613 mantissa /= 10;
237 } else { 614 } else {
238 exponent -= minimumIntegerDigits - 1; 615 exponent -= minimumIntegerDigits - 1;
239 mantissa *= pow(10, minimumIntegerDigits - 1); 616 mantissa *= pow(10, minimumIntegerDigits - 1);
240 } 617 }
241 } 618 }
242 _formatFixed(mantissa); 619 _formatFixed(mantissa);
243 _formatExponent(exponent); 620 _formatExponent(exponent);
244 } 621 }
245 622
246 /** 623 /// Format the exponent portion, e.g. in "1.3e-5" the "e-5".
247 * Format the exponent portion, e.g. in "1.3e-5" the "e-5".
248 */
249 void _formatExponent(num exponent) { 624 void _formatExponent(num exponent) {
250 _add(symbols.EXP_SYMBOL); 625 _add(symbols.EXP_SYMBOL);
251 if (exponent < 0) { 626 if (exponent < 0) {
252 exponent = -exponent; 627 exponent = -exponent;
253 _add(symbols.MINUS_SIGN); 628 _add(symbols.MINUS_SIGN);
254 } else if (_useSignForPositiveExponent) { 629 } else if (_useSignForPositiveExponent) {
255 _add(symbols.PLUS_SIGN); 630 _add(symbols.PLUS_SIGN);
256 } 631 }
257 _pad(minimumExponentDigits, exponent.toString()); 632 _pad(minimumExponentDigits, exponent.toString());
258 } 633 }
259 634
260 /** Used to test if we have exceeded Javascript integer limits. */ 635 /// Used to test if we have exceeded Javascript integer limits.
261 final _maxInt = pow(2, 52); 636 final _maxInt = pow(2, 52);
262 637
263 /** 638 /// Helpers to check numbers that don't conform to the [num] interface,
264 * Helpers to check numbers that don't conform to the [num] interface, 639 /// e.g. Int64
265 * e.g. Int64
266 */
267 _isInfinite(number) => number is num ? number.isInfinite : false; 640 _isInfinite(number) => number is num ? number.isInfinite : false;
268 _isNaN(number) => number is num ? number.isNaN : false; 641 _isNaN(number) => number is num ? number.isNaN : false;
269 _round(number) => number is num ? number.round() : number;
270 _floor(number) => number is num ? number.floor() : number;
271 642
272 /** 643 /// Helper to get the floor of a number which might not be num. This should
273 * Format the basic number portion, inluding the fractional digits. 644 /// only ever be called with an argument which is positive, or whose abs()
274 */ 645 /// is negative. The second case is the maximum negative value on a
646 /// fixed-length integer. Since they are integers, they are also their own
647 /// floor.
648 _floor(number) {
649 if (number.isNegative && !(number.abs().isNegative)) {
650 throw new ArgumentError(
651 "Internal error: expected positive number, got $number");
652 }
653 return (number is num) ? number.floor() : number ~/ 1;
654 }
655
656 /// Helper to round a number which might not be num.
657 _round(number) {
658 if (number is num) {
659 if (number.isInfinite) {
660 return _maxInt;
661 } else {
662 return number.round();
663 }
664 } else if (number.remainder(1) == 0) {
665 // Not a normal number, but int-like, e.g. Int64
666 return number;
667 } else {
668 // TODO(alanknight): Do this more efficiently. If IntX had floor and roun d we could avoid this.
669 var basic = _floor(number);
670 var fraction = (number - basic).toDouble().round();
671 return fraction == 0 ? number : number + fraction;
672 }
673 }
674
675 // Return the number of digits left of the decimal place in [number].
676 static int numberOfIntegerDigits(number) {
677 var simpleNumber = number.toDouble().abs();
678 // It's unfortunate that we have to do this, but we get precision errors
679 // that affect the result if we use logs, e.g. 1000000
680 if (simpleNumber < 10) return 1;
681 if (simpleNumber < 100) return 2;
682 if (simpleNumber < 1000) return 3;
683 if (simpleNumber < 10000) return 4;
684 if (simpleNumber < 100000) return 5;
685 if (simpleNumber < 1000000) return 6;
686 if (simpleNumber < 10000000) return 7;
687 if (simpleNumber < 100000000) return 8;
688 if (simpleNumber < 1000000000) return 9;
689 if (simpleNumber < 10000000000) return 10;
690 if (simpleNumber < 100000000000) return 11;
691 if (simpleNumber < 1000000000000) return 12;
692 if (simpleNumber < 10000000000000) return 13;
693 if (simpleNumber < 100000000000000) return 14;
694 if (simpleNumber < 1000000000000000) return 15;
695 if (simpleNumber < 10000000000000000) return 16;
696 // We're past the point where being off by one on the number of digits
697 // will affect the pattern, so now we can use logs.
698 return max(1, (log(simpleNumber) / LN10).ceil());
699 }
700
701 int _fractionDigitsAfter(int remainingSignificantDigits) =>
702 max(0, remainingSignificantDigits);
703
704 /// Format the basic number portion, including the fractional digits.
275 void _formatFixed(number) { 705 void _formatFixed(number) {
276 var integerPart; 706 var integerPart;
277 int fractionPart; 707 int fractionPart;
278 int extraIntegerDigits; 708 int extraIntegerDigits;
709 var fractionDigits = maximumFractionDigits;
279 710
280 final power = pow(10, maximumFractionDigits); 711 var power = 0;
281 final digitMultiplier = power * _multiplier; 712 var digitMultiplier;
282 713
283 if (_isInfinite(number)) { 714 if (_isInfinite(number)) {
284 integerPart = number.toInt(); 715 integerPart = number.toInt();
285 extraIntegerDigits = 0; 716 extraIntegerDigits = 0;
286 fractionPart = 0; 717 fractionPart = 0;
287 } else { 718 } else {
288 // We have three possible pieces. First, the basic integer part. If this 719 // We have three possible pieces. First, the basic integer part. If this
289 // is a percent or permille, the additional 2 or 3 digits. Finally the 720 // is a percent or permille, the additional 2 or 3 digits. Finally the
290 // fractional part. 721 // fractional part.
291 // We avoid multiplying the number because it might overflow if we have 722 // We avoid multiplying the number because it might overflow if we have
292 // a fixed-size integer type, so we extract each of the three as an 723 // a fixed-size integer type, so we extract each of the three as an
293 // integer pieces. 724 // integer pieces.
294 integerPart = _floor(number); 725 integerPart = _floor(number);
295 var fraction = number - integerPart; 726 var fraction = number - integerPart;
727
728 /// If we have significant digits, recalculate the number of fraction
729 /// digits based on that.
730 if (significantDigitsInUse) {
731 var integerLength = numberOfIntegerDigits(integerPart);
732 var remainingSignificantDigits =
733 significantDigits - _multiplierDigits - integerLength;
734 fractionDigits = _fractionDigitsAfter(remainingSignificantDigits);
735 if (remainingSignificantDigits < 0) {
736 // We may have to round.
737 var divideBy = pow(10, integerLength - significantDigits);
738 integerPart = (integerPart / divideBy).round() * divideBy;
739 }
740 }
741 power = pow(10, fractionDigits);
742 digitMultiplier = power * _multiplier;
743
296 // Multiply out to the number of decimal places and the percent, then 744 // Multiply out to the number of decimal places and the percent, then
297 // round. For fixed-size integer types this should always be zero, so 745 // round. For fixed-size integer types this should always be zero, so
298 // multiplying is OK. 746 // multiplying is OK.
299 var remainingDigits = _round(fraction * digitMultiplier).toInt(); 747 var remainingDigits = _round(fraction * digitMultiplier).toInt();
300 // However, in rounding we may overflow into the main digits. 748 // However, in rounding we may overflow into the main digits.
301 if (remainingDigits >= digitMultiplier) { 749 if (remainingDigits >= digitMultiplier) {
302 integerPart++; 750 integerPart++;
303 remainingDigits -= digitMultiplier; 751 remainingDigits -= digitMultiplier;
304 } 752 }
305 // Separate out the extra integer parts from the fraction part. 753 // Separate out the extra integer parts from the fraction part.
306 extraIntegerDigits = remainingDigits ~/ power; 754 extraIntegerDigits = remainingDigits ~/ power;
307 fractionPart = remainingDigits % power; 755 fractionPart = remainingDigits % power;
308 } 756 }
309 var fractionPresent = minimumFractionDigits > 0 || fractionPart > 0;
310 757
311 var integerDigits = _integerDigits(integerPart, extraIntegerDigits); 758 var integerDigits = _integerDigits(integerPart, extraIntegerDigits);
312 var digitLength = integerDigits.length; 759 var digitLength = integerDigits.length;
760 var fractionPresent =
761 fractionDigits > 0 && (minimumFractionDigits > 0 || fractionPart > 0);
313 762
314 if (_hasIntegerDigits(integerDigits)) { 763 if (_hasIntegerDigits(integerDigits)) {
315 _pad(minimumIntegerDigits - digitLength); 764 _padEmpty(minimumIntegerDigits - digitLength);
316 for (var i = 0; i < digitLength; i++) { 765 for (var i = 0; i < digitLength; i++) {
317 _addDigit(integerDigits.codeUnitAt(i)); 766 _addDigit(integerDigits.codeUnitAt(i));
318 _group(digitLength, i); 767 _group(digitLength, i);
319 } 768 }
320 } else if (!fractionPresent) { 769 } else if (!fractionPresent) {
321 // If neither fraction nor integer part exists, just print zero. 770 // If neither fraction nor integer part exists, just print zero.
322 _addZero(); 771 _addZero();
323 } 772 }
324 773
325 _decimalSeparator(fractionPresent); 774 _decimalSeparator(fractionPresent);
326 _formatFractionPart((fractionPart + power).toString()); 775 _formatFractionPart((fractionPart + power).toString());
327 } 776 }
328 777
329 /** 778 /// Compute the raw integer digits which will then be printed with
330 * Compute the raw integer digits which will then be printed with 779 /// grouping and translated to localized digits.
331 * grouping and translated to localized digits.
332 */
333 String _integerDigits(integerPart, extraIntegerDigits) { 780 String _integerDigits(integerPart, extraIntegerDigits) {
334 // If the int part is larger than 2^52 and we're on Javascript (so it's 781 // If the int part is larger than 2^52 and we're on Javascript (so it's
335 // really a float) it will lose precision, so pad out the rest of it 782 // really a float) it will lose precision, so pad out the rest of it
336 // with zeros. Check for Javascript by seeing if an integer is double. 783 // with zeros. Check for Javascript by seeing if an integer is double.
337 var paddingDigits = ''; 784 var paddingDigits = '';
338 if (1 is double && integerPart is num && integerPart > _maxInt) { 785 if (1 is double && integerPart is num && integerPart > _maxInt) {
339 var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16; 786 var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16;
340 var divisor = pow(10, howManyDigitsTooBig).round(); 787 var divisor = pow(10, howManyDigitsTooBig).round();
341 paddingDigits = symbols.ZERO_DIGIT * howManyDigitsTooBig.toInt(); 788 paddingDigits = '0' * howManyDigitsTooBig.toInt();
342 integerPart = (integerPart / divisor).truncate(); 789 integerPart = (integerPart / divisor).truncate();
343 } 790 }
344 791
345 var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString(); 792 var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString();
346 var intDigits = _mainIntegerDigits(integerPart); 793 var intDigits = _mainIntegerDigits(integerPart);
347 var paddedExtra = 794 var paddedExtra =
348 intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0'); 795 intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0');
349 return "${intDigits}${paddedExtra}${paddingDigits}"; 796 return "${intDigits}${paddedExtra}${paddingDigits}";
350 } 797 }
351 798
352 /** 799 /// The digit string of the integer part. This is the empty string if the
353 * The digit string of the integer part. This is the empty string if the 800 /// integer part is zero and otherwise is the toString() of the integer
354 * integer part is zero and otherwise is the toString() of the integer 801 /// part, stripping off any minus sign.
355 * part, stripping off any minus sign.
356 */
357 String _mainIntegerDigits(integer) { 802 String _mainIntegerDigits(integer) {
358 if (integer == 0) return ''; 803 if (integer == 0) return '';
359 var digits = integer.toString(); 804 var digits = integer.toString();
805 if (significantDigitsInUse && digits.length > significantDigits) {
806 digits = digits.substring(0, significantDigits) +
807 ''.padLeft(digits.length - significantDigits, '0');
808 }
360 // If we have a fixed-length int representation, it can have a negative 809 // If we have a fixed-length int representation, it can have a negative
361 // number whose negation is also negative, e.g. 2^-63 in 64-bit. 810 // number whose negation is also negative, e.g. 2^-63 in 64-bit.
362 // Remove the minus sign. 811 // Remove the minus sign.
363 return digits.startsWith('-') ? digits.substring(1) : digits; 812 return digits.startsWith('-') ? digits.substring(1) : digits;
364 } 813 }
365 814
366 /** 815 /// Format the part after the decimal place in a fixed point number.
367 * Format the part after the decimal place in a fixed point number.
368 */
369 void _formatFractionPart(String fractionPart) { 816 void _formatFractionPart(String fractionPart) {
370 var fractionCodes = fractionPart.codeUnits;
371 var fractionLength = fractionPart.length; 817 var fractionLength = fractionPart.length;
372 while (fractionCodes[fractionLength - 1] == _zero && 818 while (fractionPart.codeUnitAt(fractionLength - 1) == _zero &&
373 fractionLength > minimumFractionDigits + 1) { 819 fractionLength > minimumFractionDigits + 1) {
374 fractionLength--; 820 fractionLength--;
375 } 821 }
376 for (var i = 1; i < fractionLength; i++) { 822 for (var i = 1; i < fractionLength; i++) {
377 _addDigit(fractionCodes[i]); 823 _addDigit(fractionPart.codeUnitAt(i));
378 } 824 }
379 } 825 }
380 826
381 /** Print the decimal separator if appropriate. */ 827 /// Print the decimal separator if appropriate.
382 void _decimalSeparator(bool fractionPresent) { 828 void _decimalSeparator(bool fractionPresent) {
383 if (_decimalSeparatorAlwaysShown || fractionPresent) { 829 if (_decimalSeparatorAlwaysShown || fractionPresent) {
384 _add(symbols.DECIMAL_SEP); 830 _add(symbols.DECIMAL_SEP);
385 } 831 }
386 } 832 }
387 833
388 /** 834 /// Return true if we have a main integer part which is printable, either
389 * Return true if we have a main integer part which is printable, either 835 /// because we have digits left of the decimal point (this may include digits
390 * because we have digits left of the decimal point (this may include digits 836 /// which have been moved left because of percent or permille formatting),
391 * which have been moved left because of percent or permille formatting), 837 /// or because the minimum number of printable digits is greater than 1.
392 * or because the minimum number of printable digits is greater than 1.
393 */
394 bool _hasIntegerDigits(String digits) => 838 bool _hasIntegerDigits(String digits) =>
395 digits.isNotEmpty || minimumIntegerDigits > 0; 839 digits.isNotEmpty || minimumIntegerDigits > 0;
396 840
397 /** A group of methods that provide support for writing digits and other 841 /// A group of methods that provide support for writing digits and other
398 * required characters into [_buffer] easily. 842 /// required characters into [_buffer] easily.
399 */
400 void _add(String x) { 843 void _add(String x) {
401 _buffer.write(x); 844 _buffer.write(x);
402 } 845 }
403 void _addCharCode(int x) { 846
404 _buffer.writeCharCode(x);
405 }
406 void _addZero() { 847 void _addZero() {
407 _buffer.write(symbols.ZERO_DIGIT); 848 _buffer.write(symbols.ZERO_DIGIT);
408 } 849 }
850
409 void _addDigit(int x) { 851 void _addDigit(int x) {
410 _buffer.writeCharCode(_localeZero + x - _zero); 852 _buffer.writeCharCode(x + _zeroOffset);
411 } 853 }
412 854
413 /** Print padding up to [numberOfDigits] above what's included in [basic]. */ 855 void _padEmpty(int howMany) {
414 void _pad(int numberOfDigits, [String basic = '']) { 856 _buffer.write(symbols.ZERO_DIGIT * howMany);
857 }
858
859 void _pad(int numberOfDigits, String basic) {
860 if (_zeroOffset == 0) {
861 _buffer.write(basic.padLeft(numberOfDigits, '0'));
862 } else {
863 _slowPad(numberOfDigits, basic);
864 }
865 }
866
867 /// Print padding up to [numberOfDigits] above what's included in [basic].
868 void _slowPad(int numberOfDigits, String basic) {
415 for (var i = 0; i < numberOfDigits - basic.length; i++) { 869 for (var i = 0; i < numberOfDigits - basic.length; i++) {
416 _add(symbols.ZERO_DIGIT); 870 _add(symbols.ZERO_DIGIT);
417 } 871 }
418 for (var x in basic.codeUnits) { 872 for (int i = 0; i < basic.length; i++) {
419 _addDigit(x); 873 _addDigit(basic.codeUnitAt(i));
420 } 874 }
421 } 875 }
422 876
423 /** 877 /// We are printing the digits of the number from left to right. We may need
424 * We are printing the digits of the number from left to right. We may need 878 /// to print a thousands separator or other grouping character as appropriate
425 * to print a thousands separator or other grouping character as appropriate 879 /// to the locale. So we find how many places we are from the end of the numbe r
426 * to the locale. So we find how many places we are from the end of the number 880 /// by subtracting our current [position] from the [totalLength] and printing
427 * by subtracting our current [position] from the [totalLength] and printing 881 /// the separator character every [_groupingSize] digits, with the final
428 * the separator character every [_groupingSize] digits, with the final 882 /// grouping possibly being of a different size, [_finalGroupingSize].
429 * grouping possibly being of a different size, [_finalGroupingSize].
430 */
431 void _group(int totalLength, int position) { 883 void _group(int totalLength, int position) {
432 var distanceFromEnd = totalLength - position; 884 var distanceFromEnd = totalLength - position;
433 if (distanceFromEnd <= 1 || _groupingSize <= 0) return; 885 if (distanceFromEnd <= 1 || _groupingSize <= 0) return;
434 if (distanceFromEnd == _finalGroupingSize + 1) { 886 if (distanceFromEnd == _finalGroupingSize + 1) {
435 _add(symbols.GROUP_SEP); 887 _add(symbols.GROUP_SEP);
436 } else if ((distanceFromEnd > _finalGroupingSize) && 888 } else if ((distanceFromEnd > _finalGroupingSize) &&
437 (distanceFromEnd - _finalGroupingSize) % _groupingSize == 1) { 889 (distanceFromEnd - _finalGroupingSize) % _groupingSize == 1) {
438 _add(symbols.GROUP_SEP); 890 _add(symbols.GROUP_SEP);
439 } 891 }
440 } 892 }
441 893
442 /** Returns the code point for the character '0'. */ 894 /// The code point for the character '0'.
443 final _zero = '0'.codeUnits.first; 895 static const _zero = 48;
444 896
445 /** Returns the code point for the locale's zero digit. */ 897 /// The code point for the locale's zero digit.
446 // Note that there is a slight risk of a locale's zero digit not fitting 898 ///
447 // into a single code unit, but it seems very unlikely, and if it did, 899 /// Initialized when the locale is set.
448 // there's a pretty good chance that our assumptions about being able to do 900 int _localeZero = 0;
449 // arithmetic on it would also be invalid.
450 get _localeZero => symbols.ZERO_DIGIT.codeUnits.first;
451 901
452 /** 902 /// The difference between our zero and '0'.
453 * Returns the prefix for [x] based on whether it's positive or negative. 903 ///
454 * In en_US this would be '' and '-' respectively. 904 /// In other words, a constant _localeZero - _zero. Initialized when
455 */ 905 /// the locale is set.
906 int _zeroOffset = 0;
907
908 /// Returns the prefix for [x] based on whether it's positive or negative.
909 /// In en_US this would be '' and '-' respectively.
456 String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix; 910 String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix;
457 911
458 /** 912 /// Returns the suffix for [x] based on wether it's positive or negative.
459 * Returns the suffix for [x] based on wether it's positive or negative. 913 /// In en_US there are no suffixes for positive or negative.
460 * In en_US there are no suffixes for positive or negative.
461 */
462 String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix; 914 String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix;
463 915
464 void _setPattern(String newPattern) { 916 void _setPattern(String newPattern) {
465 if (newPattern == null) return; 917 if (newPattern == null) return;
466 // Make spaces non-breaking 918 // Make spaces non-breaking
467 _pattern = newPattern.replaceAll(' ', '\u00a0'); 919 _pattern = newPattern.replaceAll(' ', '\u00a0');
468 var parser = new _NumberFormatParser(this, newPattern, currencyName); 920 var parser = new _NumberFormatParser(
921 this, newPattern, currencySymbol, decimalDigits);
469 parser.parse(); 922 parser.parse();
923 if (_overridesDecimalDigits) {
924 _decimalDigits ??= _defaultDecimalDigits;
925 minimumFractionDigits = _decimalDigits;
926 maximumFractionDigits = _decimalDigits;
927 }
928 }
929
930 /// Explicitly turn off any grouping (e.g. by thousands) in this format.
931 ///
932 /// This is used in compact number formatting, where we
933 /// omit the normal grouping. Best to know what you're doing if you call it.
934 void turnOffGrouping() {
935 _groupingSize = 0;
936 _finalGroupingSize = 0;
470 } 937 }
471 938
472 String toString() => "NumberFormat($_locale, $_pattern)"; 939 String toString() => "NumberFormat($_locale, $_pattern)";
473 } 940 }
474 941
475 /** 942 /// A one-time object for parsing a particular numeric string. One-time here
476 * A one-time object for parsing a particular numeric string. One-time here 943 /// means an instance can only parse one string. This is implemented by
477 * means an instance can only parse one string. This is implemented by 944 /// transforming from a locale-specific format to one that the system can parse,
478 * transforming from a locale-specific format to one that the system can parse, 945 /// then calls the system parsing methods on it.
479 * then calls the system parsing methods on it.
480 */
481 class _NumberParser { 946 class _NumberParser {
482 947 /// The format for which we are parsing.
483 /** The format for which we are parsing. */
484 final NumberFormat format; 948 final NumberFormat format;
485 949
486 /** The text we are parsing. */ 950 /// The text we are parsing.
487 final String text; 951 final String text;
488 952
489 /** What we use to iterate over the input text. */ 953 /// What we use to iterate over the input text.
490 final _Stream input; 954 final _Stream input;
491 955
492 /** 956 /// The result of parsing [text] according to [format]. Automatically
493 * The result of parsing [text] according to [format]. Automatically 957 /// populated in the constructor.
494 * populated in the constructor.
495 */
496 num value; 958 num value;
497 959
498 /** The symbols used by our format. */ 960 /// The symbols used by our format.
499 NumberSymbols get symbols => format.symbols; 961 NumberSymbols get symbols => format.symbols;
500 962
501 /** Where we accumulate the normalized representation of the number. */ 963 /// Where we accumulate the normalized representation of the number.
502 final StringBuffer _normalized = new StringBuffer(); 964 final StringBuffer _normalized = new StringBuffer();
503 965
504 /** 966 /// Did we see something that indicates this is, or at least might be,
505 * Did we see something that indicates this is, or at least might be, 967 /// a positive number.
506 * a positive number.
507 */
508 bool gotPositive = false; 968 bool gotPositive = false;
509 969
510 /** 970 /// Did we see something that indicates this is, or at least might be,
511 * Did we see something that indicates this is, or at least might be, 971 /// a negative number.
512 * a negative number.
513 */
514 bool gotNegative = false; 972 bool gotNegative = false;
515 /** 973
516 * Did we see the required positive suffix at the end. Should 974 /// Did we see the required positive suffix at the end. Should
517 * match [gotPositive]. 975 /// match [gotPositive].
518 */
519 bool gotPositiveSuffix = false; 976 bool gotPositiveSuffix = false;
520 /** 977
521 * Did we see the required negative suffix at the end. Should 978 /// Did we see the required negative suffix at the end. Should
522 * match [gotNegative]. 979 /// match [gotNegative].
523 */
524 bool gotNegativeSuffix = false; 980 bool gotNegativeSuffix = false;
525 981
526 /** Should we stop parsing before hitting the end of the string. */ 982 /// Should we stop parsing before hitting the end of the string.
527 bool done = false; 983 bool done = false;
528 984
529 /** Have we already skipped over any required prefixes. */ 985 /// Have we already skipped over any required prefixes.
530 bool prefixesSkipped = false; 986 bool prefixesSkipped = false;
531 987
532 /** If the number is percent or permill, what do we divide by at the end. */ 988 /// If the number is percent or permill, what do we divide by at the end.
533 int scale = 1; 989 int scale = 1;
534 990
535 String get _positivePrefix => format._positivePrefix; 991 String get _positivePrefix => format._positivePrefix;
536 String get _negativePrefix => format._negativePrefix; 992 String get _negativePrefix => format._negativePrefix;
537 String get _positiveSuffix => format._positiveSuffix; 993 String get _positiveSuffix => format._positiveSuffix;
538 String get _negativeSuffix => format._negativeSuffix; 994 String get _negativeSuffix => format._negativeSuffix;
539 int get _zero => format._zero; 995 int get _zero => NumberFormat._zero;
540 int get _localeZero => format._localeZero; 996 int get _localeZero => format._localeZero;
541 997
542 /** 998 /// Create a new [_NumberParser] on which we can call parse().
543 * Create a new [_NumberParser] on which we can call parse().
544 */
545 _NumberParser(this.format, text) 999 _NumberParser(this.format, text)
546 : this.text = text, 1000 : this.text = text,
547 this.input = new _Stream(text) { 1001 this.input = new _Stream(text) {
1002 scale = format._internalMultiplier;
548 value = parse(); 1003 value = parse();
549 } 1004 }
550 1005
551 /** 1006 /// The strings we might replace with functions that return the replacement
552 * The strings we might replace with functions that return the replacement 1007 /// values. They are functions because we might need to check something
553 * values. They are functions because we might need to check something 1008 /// in the context. Note that the ordering is important here. For example,
554 * in the context. Note that the ordering is important here. For example, 1009 /// [symbols.PERCENT] might be " %", and we must handle that before we
555 * [symbols.PERCENT] might be " %", and we must handle that before we 1010 /// look at an individual space.
556 * look at an individual space. 1011 Map<String, Function> get replacements =>
557 */ 1012 _replacements ??= _initializeReplacements();
558 Map<String, Function> get replacements => _replacements == null
559 ? _replacements = _initializeReplacements()
560 : _replacements;
561 1013
562 var _replacements; 1014 Map<String, Function> _replacements;
563 1015
564 Map _initializeReplacements() => { 1016 Map<String, Function> _initializeReplacements() => {
565 symbols.DECIMAL_SEP: () => '.', 1017 symbols.DECIMAL_SEP: () => '.',
566 symbols.EXP_SYMBOL: () => 'E', 1018 symbols.EXP_SYMBOL: () => 'E',
567 symbols.GROUP_SEP: handleSpace, 1019 symbols.GROUP_SEP: handleSpace,
568 symbols.PERCENT: () { 1020 symbols.PERCENT: () {
569 scale = _NumberFormatParser._PERCENT_SCALE; 1021 scale = _NumberFormatParser._PERCENT_SCALE;
570 return ''; 1022 return '';
571 }, 1023 },
572 symbols.PERMILL: () { 1024 symbols.PERMILL: () {
573 scale = _NumberFormatParser._PER_MILLE_SCALE; 1025 scale = _NumberFormatParser._PER_MILLE_SCALE;
574 return ''; 1026 return '';
575 }, 1027 },
576 ' ': handleSpace, 1028 ' ': handleSpace,
577 '\u00a0': handleSpace, 1029 '\u00a0': handleSpace,
578 '+': () => '+', 1030 '+': () => '+',
579 '-': () => '-', 1031 '-': () => '-',
580 }; 1032 };
581 1033
582 invalidFormat() => 1034 invalidFormat() =>
583 throw new FormatException("Invalid number: ${input.contents}"); 1035 throw new FormatException("Invalid number: ${input.contents}");
584 1036
585 /** 1037 /// Replace a space in the number with the normalized form. If space is not
586 * Replace a space in the number with the normalized form. If space is not 1038 /// a significant character (normally grouping) then it's just invalid. If it
587 * a significant character (normally grouping) then it's just invalid. If it 1039 /// is the grouping character, then it's only valid if it's followed by a
588 * is the grouping character, then it's only valid if it's followed by a 1040 /// digit. e.g. '$12 345.00'
589 * digit. e.g. '$12 345.00'
590 */
591 handleSpace() => 1041 handleSpace() =>
592 groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat(); 1042 groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat();
593 1043
594 /** 1044 /// Determine if a space is a valid character in the number. See
595 * Determine if a space is a valid character in the number. See [handleSpace]. 1045 /// [handleSpace].
596 */
597 bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit { 1046 bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit {
598 if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true; 1047 if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true;
599 var peeked = input.peek(symbols.GROUP_SEP.length + 1); 1048 var peeked = input.peek(symbols.GROUP_SEP.length + 1);
600 return asDigit(peeked[peeked.length - 1]) != null; 1049 return asDigit(peeked[peeked.length - 1]) != null;
601 } 1050 }
602 1051
603 /** 1052 /// Turn [char] into a number representing a digit, or null if it doesn't
604 * Turn [char] into a number representing a digit, or null if it doesn't 1053 /// represent a digit in this locale.
605 * represent a digit in this locale.
606 */
607 int asDigit(String char) { 1054 int asDigit(String char) {
608 var charCode = char.codeUnitAt(0); 1055 var charCode = char.codeUnitAt(0);
609 var digitValue = charCode - _localeZero; 1056 var digitValue = charCode - _localeZero;
610 if (digitValue >= 0 && digitValue < 10) { 1057 if (digitValue >= 0 && digitValue < 10) {
611 return digitValue; 1058 return digitValue;
612 } else { 1059 } else {
613 return null; 1060 return null;
614 } 1061 }
615 } 1062 }
616 1063
617 /** 1064 /// Check to see if the input begins with either the positive or negative
618 * Check to see if the input begins with either the positive or negative 1065 /// prefixes. Set the [gotPositive] and [gotNegative] variables accordingly.
619 * prefixes. Set the [gotPositive] and [gotNegative] variables accordingly.
620 */
621 void checkPrefixes({bool skip: false}) { 1066 void checkPrefixes({bool skip: false}) {
622 bool checkPrefix(String prefix, skip) { 1067 bool checkPrefix(String prefix) =>
623 var matched = prefix.isNotEmpty && input.startsWith(prefix); 1068 prefix.isNotEmpty && input.startsWith(prefix);
624 if (skip && matched) input.read(prefix.length);
625 return matched;
626 }
627 1069
628 // TODO(alanknight): There's a faint possibility of a bug here where 1070 // TODO(alanknight): There's a faint possibility of a bug here where
629 // a positive prefix is followed by a negative prefix that's also a valid 1071 // a positive prefix is followed by a negative prefix that's also a valid
630 // part of the number, but that seems very unlikely. 1072 // part of the number, but that seems very unlikely.
631 if (checkPrefix(_positivePrefix, skip)) gotPositive = true; 1073 if (checkPrefix(_positivePrefix)) gotPositive = true;
632 if (checkPrefix(_negativePrefix, skip)) gotNegative = true; 1074 if (checkPrefix(_negativePrefix)) gotNegative = true;
633 1075
634 // Copied from Closure. It doesn't seem to be necessary to pass the test 1076 // The positive prefix might be a substring of the negative, in
635 // suite, so I'm not sure it's really needed. 1077 // which case both would match.
636 if (gotPositive && gotNegative) { 1078 if (gotPositive && gotNegative) {
637 if (_positivePrefix.length > _negativePrefix.length) { 1079 if (_positivePrefix.length > _negativePrefix.length) {
638 gotNegative = false; 1080 gotNegative = false;
639 } else if (_negativePrefix.length > _positivePrefix.length) { 1081 } else if (_negativePrefix.length > _positivePrefix.length) {
640 gotPositive = false; 1082 gotPositive = false;
641 } 1083 }
642 } 1084 }
1085 if (skip) {
1086 if (gotPositive) input.read(_positivePrefix.length);
1087 if (gotNegative) input.read(_negativePrefix.length);
1088 }
643 } 1089 }
644 1090
645 /** 1091 /// If the rest of our input is either the positive or negative suffix,
646 * If the rest of our input is either the positive or negative suffix, 1092 /// set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly.
647 * set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly.
648 */
649 void checkSuffixes() { 1093 void checkSuffixes() {
650 var remainder = input.rest(); 1094 var remainder = input.rest();
651 if (remainder == _positiveSuffix) gotPositiveSuffix = true; 1095 if (remainder == _positiveSuffix) gotPositiveSuffix = true;
652 if (remainder == _negativeSuffix) gotNegativeSuffix = true; 1096 if (remainder == _negativeSuffix) gotNegativeSuffix = true;
653 } 1097 }
654 1098
655 /** 1099 /// We've encountered a character that's not a digit. Go through our
656 * We've encountered a character that's not a digit. Go through our 1100 /// replacement rules looking for how to handle it. If we see something
657 * replacement rules looking for how to handle it. If we see something 1101 /// that's not a digit and doesn't have a replacement, then we're done
658 * that's not a digit and doesn't have a replacement, then we're done 1102 /// and the number is probably invalid.
659 * and the number is probably invalid.
660 */
661 void processNonDigit() { 1103 void processNonDigit() {
1104 // It might just be a prefix that we haven't skipped. We don't want to
1105 // skip them initially because they might also be semantically meaningful,
1106 // e.g. leading %. So we allow them through the loop, but only once.
1107 var foundAnInterpretation = false;
1108 if (input.index == 0 && !prefixesSkipped) {
1109 prefixesSkipped = true;
1110 checkPrefixes(skip: true);
1111 foundAnInterpretation = true;
1112 }
1113
662 for (var key in replacements.keys) { 1114 for (var key in replacements.keys) {
663 if (input.startsWith(key)) { 1115 if (input.startsWith(key)) {
664 _normalized.write(replacements[key]()); 1116 _normalized.write(replacements[key]());
665 input.read(key.length); 1117 input.read(key.length);
666 return; 1118 return;
667 } 1119 }
668 } 1120 }
669 // It might just be a prefix that we haven't skipped. We don't want to 1121 // We haven't found either of these things, this seems invalid.
670 // skip them initially because they might also be semantically meaningful, 1122 if (!foundAnInterpretation) {
671 // e.g. leading %. So we allow them through the loop, but only once.
672 if (input.index == 0 && !prefixesSkipped) {
673 prefixesSkipped = true;
674 checkPrefixes(skip: true);
675 } else {
676 done = true; 1123 done = true;
677 } 1124 }
678 } 1125 }
679 1126
680 /** 1127 /// Parse [text] and return the resulting number. Throws [FormatException]
681 * Parse [text] and return the resulting number. Throws [FormatException] 1128 /// if we can't parse it.
682 * if we can't parse it.
683 */
684 num parse() { 1129 num parse() {
685 if (text == symbols.NAN) return double.NAN; 1130 if (text == symbols.NAN) return double.NAN;
686 if (text == "$_positivePrefix${symbols.INFINITY}$_positiveSuffix") { 1131 if (text == "$_positivePrefix${symbols.INFINITY}$_positiveSuffix") {
687 return double.INFINITY; 1132 return double.INFINITY;
688 } 1133 }
689 if (text == "$_negativePrefix${symbols.INFINITY}$_negativeSuffix") { 1134 if (text == "$_negativePrefix${symbols.INFINITY}$_negativeSuffix") {
690 return double.NEGATIVE_INFINITY; 1135 return double.NEGATIVE_INFINITY;
691 } 1136 }
692 1137
693 checkPrefixes(); 1138 checkPrefixes();
694 var parsed = parseNumber(input); 1139 var parsed = parseNumber(input);
695 1140
696 if (gotPositive && !gotPositiveSuffix) invalidNumber(); 1141 if (gotPositive && !gotPositiveSuffix) invalidNumber();
697 if (gotNegative && !gotNegativeSuffix) invalidNumber(); 1142 if (gotNegative && !gotNegativeSuffix) invalidNumber();
698 if (!input.atEnd()) invalidNumber(); 1143 if (!input.atEnd()) invalidNumber();
699 1144
700 return parsed; 1145 return parsed;
701 } 1146 }
702 1147
703 /** The number is invalid, throw a [FormatException]. */ 1148 /// The number is invalid, throw a [FormatException].
704 void invalidNumber() => 1149 void invalidNumber() =>
705 throw new FormatException("Invalid Number: ${input.contents}"); 1150 throw new FormatException("Invalid Number: ${input.contents}");
706 1151
707 /** 1152 /// Parse the number portion of the input, i.e. not any prefixes or suffixes,
708 * Parse the number portion of the input, i.e. not any prefixes or suffixes, 1153 /// and assuming NaN and Infinity are already handled.
709 * and assuming NaN and Infinity are already handled.
710 */
711 num parseNumber(_Stream input) { 1154 num parseNumber(_Stream input) {
1155 if (gotNegative) {
1156 _normalized.write('-');
1157 }
712 while (!done && !input.atEnd()) { 1158 while (!done && !input.atEnd()) {
713 int digit = asDigit(input.peek()); 1159 int digit = asDigit(input.peek());
714 if (digit != null) { 1160 if (digit != null) {
715 _normalized.writeCharCode(_zero + digit); 1161 _normalized.writeCharCode(_zero + digit);
716 input.next(); 1162 input.next();
717 } else { 1163 } else {
718 processNonDigit(); 1164 processNonDigit();
719 } 1165 }
720 checkSuffixes(); 1166 checkSuffixes();
721 } 1167 }
722 1168
723 var normalizedText = _normalized.toString(); 1169 var normalizedText = _normalized.toString();
724 num parsed = int.parse(normalizedText, onError: (message) => null); 1170 num parsed = int.parse(normalizedText, onError: (message) => null);
725 if (parsed == null) parsed = double.parse(normalizedText); 1171 if (parsed == null) parsed = double.parse(normalizedText);
726 return parsed / scale; 1172 return parsed / scale;
727 } 1173 }
728 } 1174 }
729 1175
730 /** 1176 /// Private class that parses the numeric formatting pattern and sets the
731 * Private class that parses the numeric formatting pattern and sets the 1177 /// variables in [format] to appropriate values. Instances of this are
732 * variables in [format] to appropriate values. Instances of this are 1178 /// transient and store parsing state in instance variables, so can only be used
733 * transient and store parsing state in instance variables, so can only be used 1179 /// to parse a single pattern.
734 * to parse a single pattern.
735 */
736 class _NumberFormatParser { 1180 class _NumberFormatParser {
737 1181 /// The special characters in the pattern language. All others are treated
738 /** 1182 /// as literals.
739 * The special characters in the pattern language. All others are treated
740 * as literals.
741 */
742 static const _PATTERN_SEPARATOR = ';'; 1183 static const _PATTERN_SEPARATOR = ';';
743 static const _QUOTE = "'"; 1184 static const _QUOTE = "'";
744 static const _PATTERN_DIGIT = '#'; 1185 static const _PATTERN_DIGIT = '#';
745 static const _PATTERN_ZERO_DIGIT = '0'; 1186 static const _PATTERN_ZERO_DIGIT = '0';
746 static const _PATTERN_GROUPING_SEPARATOR = ','; 1187 static const _PATTERN_GROUPING_SEPARATOR = ',';
747 static const _PATTERN_DECIMAL_SEPARATOR = '.'; 1188 static const _PATTERN_DECIMAL_SEPARATOR = '.';
748 static const _PATTERN_CURRENCY_SIGN = '\u00A4'; 1189 static const _PATTERN_CURRENCY_SIGN = '\u00A4';
749 static const _PATTERN_PER_MILLE = '\u2030'; 1190 static const _PATTERN_PER_MILLE = '\u2030';
750 static const _PER_MILLE_SCALE = 1000; 1191 static const _PER_MILLE_SCALE = 1000;
751 static const _PATTERN_PERCENT = '%'; 1192 static const _PATTERN_PERCENT = '%';
752 static const _PERCENT_SCALE = 100; 1193 static const _PERCENT_SCALE = 100;
753 static const _PATTERN_EXPONENT = 'E'; 1194 static const _PATTERN_EXPONENT = 'E';
754 static const _PATTERN_PLUS = '+'; 1195 static const _PATTERN_PLUS = '+';
755 1196
756 /** The format whose state we are setting. */ 1197 /// The format whose state we are setting.
757 final NumberFormat format; 1198 final NumberFormat format;
758 1199
759 /** The pattern we are parsing. */ 1200 /// The pattern we are parsing.
760 final _StringIterator pattern; 1201 final _StringIterator pattern;
761 1202
762 /** We can be passed a specific currency symbol, regardless of the locale. */ 1203 /// We can be passed a specific currency symbol, regardless of the locale.
763 String currencyName; 1204 String currencySymbol;
764 1205
765 /** 1206 /// We can be given a specific number of decimal places, overriding the
766 * Create a new [_NumberFormatParser] for a particular [NumberFormat] and 1207 /// default.
767 * [input] pattern. 1208 final int decimalDigits;
768 */ 1209
769 _NumberFormatParser(this.format, input, this.currencyName) 1210 /// Create a new [_NumberFormatParser] for a particular [NumberFormat] and
1211 /// [input] pattern.
1212 _NumberFormatParser(
1213 this.format, input, this.currencySymbol, this.decimalDigits)
770 : pattern = _iterator(input) { 1214 : pattern = _iterator(input) {
771 pattern.moveNext(); 1215 pattern.moveNext();
772 } 1216 }
773 1217
774 /** The [NumberSymbols] for the locale in which our [format] prints. */ 1218 /// The [NumberSymbols] for the locale in which our [format] prints.
775 NumberSymbols get symbols => format.symbols; 1219 NumberSymbols get symbols => format.symbols;
776 1220
777 /** Parse the input pattern and set the values. */ 1221 /// Parse the input pattern and set the values.
778 void parse() { 1222 void parse() {
779 format._positivePrefix = _parseAffix(); 1223 format._positivePrefix = _parseAffix();
780 var trunk = _parseTrunk(); 1224 var trunk = _parseTrunk();
781 format._positiveSuffix = _parseAffix(); 1225 format._positiveSuffix = _parseAffix();
782 // If we have separate positive and negative patterns, now parse the 1226 // If we have separate positive and negative patterns, now parse the
783 // the negative version. 1227 // the negative version.
784 if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) { 1228 if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) {
785 pattern.moveNext(); 1229 pattern.moveNext();
786 format._negativePrefix = _parseAffix(); 1230 format._negativePrefix = _parseAffix();
787 // Skip over the negative trunk, verifying that it's identical to the 1231 // Skip over the negative trunk, verifying that it's identical to the
788 // positive trunk. 1232 // positive trunk.
789 for (var each in _iterable(trunk)) { 1233 for (var each in _iterable(trunk)) {
790 if (pattern.current != each && pattern.current != null) { 1234 if (pattern.current != each && pattern.current != null) {
791 throw new FormatException( 1235 throw new FormatException(
792 "Positive and negative trunks must be the same"); 1236 "Positive and negative trunks must be the same");
793 } 1237 }
794 pattern.moveNext(); 1238 pattern.moveNext();
795 } 1239 }
796 format._negativeSuffix = _parseAffix(); 1240 format._negativeSuffix = _parseAffix();
797 } else { 1241 } else {
798 // If no negative affix is specified, they share the same positive affix. 1242 // If no negative affix is specified, they share the same positive affix.
799 format._negativePrefix = format._negativePrefix + format._positivePrefix; 1243 format._negativePrefix = format._negativePrefix + format._positivePrefix;
800 format._negativeSuffix = format._positiveSuffix + format._negativeSuffix; 1244 format._negativeSuffix = format._positiveSuffix + format._negativeSuffix;
801 } 1245 }
802 } 1246 }
803 1247
804 /** 1248 /// Variable used in parsing prefixes and suffixes to keep track of
805 * Variable used in parsing prefixes and suffixes to keep track of 1249 /// whether or not we are in a quoted region.
806 * whether or not we are in a quoted region.
807 */
808 bool inQuote = false; 1250 bool inQuote = false;
809 1251
810 /** 1252 /// Parse a prefix or suffix and return the prefix/suffix string. Note that
811 * Parse a prefix or suffix and return the prefix/suffix string. Note that 1253 /// this also may modify the state of [format].
812 * this also may modify the state of [format].
813 */
814 String _parseAffix() { 1254 String _parseAffix() {
815 var affix = new StringBuffer(); 1255 var affix = new StringBuffer();
816 inQuote = false; 1256 inQuote = false;
817 while (parseCharacterAffix(affix) && pattern.moveNext()); 1257 while (parseCharacterAffix(affix) && pattern.moveNext());
818 return affix.toString(); 1258 return affix.toString();
819 } 1259 }
820 1260
821 /** 1261 /// Parse an individual character as part of a prefix or suffix. Return true
822 * Parse an individual character as part of a prefix or suffix. Return true 1262 /// if we should continue to look for more affix characters, and false if
823 * if we should continue to look for more affix characters, and false if 1263 /// we have reached the end.
824 * we have reached the end.
825 */
826 bool parseCharacterAffix(StringBuffer affix) { 1264 bool parseCharacterAffix(StringBuffer affix) {
827 var ch = pattern.current; 1265 var ch = pattern.current;
828 if (ch == null) return false; 1266 if (ch == null) return false;
829 if (ch == _QUOTE) { 1267 if (ch == _QUOTE) {
830 if (pattern.peek == _QUOTE) { 1268 if (pattern.peek == _QUOTE) {
831 pattern.moveNext(); 1269 pattern.moveNext();
832 affix.write(_QUOTE); // 'don''t' 1270 affix.write(_QUOTE); // 'don''t'
833 } else { 1271 } else {
834 inQuote = !inQuote; 1272 inQuote = !inQuote;
835 } 1273 }
836 return true; 1274 return true;
837 } 1275 }
838 1276
839 if (inQuote) { 1277 if (inQuote) {
840 affix.write(ch); 1278 affix.write(ch);
841 } else { 1279 } else {
842 switch (ch) { 1280 switch (ch) {
843 case _PATTERN_DIGIT: 1281 case _PATTERN_DIGIT:
844 case _PATTERN_ZERO_DIGIT: 1282 case _PATTERN_ZERO_DIGIT:
845 case _PATTERN_GROUPING_SEPARATOR: 1283 case _PATTERN_GROUPING_SEPARATOR:
846 case _PATTERN_DECIMAL_SEPARATOR: 1284 case _PATTERN_DECIMAL_SEPARATOR:
847 case _PATTERN_SEPARATOR: 1285 case _PATTERN_SEPARATOR:
848 return false; 1286 return false;
849 case _PATTERN_CURRENCY_SIGN: 1287 case _PATTERN_CURRENCY_SIGN:
850 // TODO(alanknight): Handle the local/global/portable currency signs 1288 // TODO(alanknight): Handle the local/global/portable currency signs
851 affix.write(currencyName); 1289 affix.write(currencySymbol);
852 break; 1290 break;
853 case _PATTERN_PERCENT: 1291 case _PATTERN_PERCENT:
854 if (format._multiplier != 1 && format._multiplier != _PERCENT_SCALE) { 1292 if (format._multiplier != 1 && format._multiplier != _PERCENT_SCALE) {
855 throw new FormatException('Too many percent/permill'); 1293 throw new FormatException('Too many percent/permill');
856 } 1294 }
857 format._multiplier = _PERCENT_SCALE; 1295 format._multiplier = _PERCENT_SCALE;
858 affix.write(symbols.PERCENT); 1296 affix.write(symbols.PERCENT);
859 break; 1297 break;
860 case _PATTERN_PER_MILLE: 1298 case _PATTERN_PER_MILLE:
861 if (format._multiplier != 1 && 1299 if (format._multiplier != 1 &&
862 format._multiplier != _PER_MILLE_SCALE) { 1300 format._multiplier != _PER_MILLE_SCALE) {
863 throw new FormatException('Too many percent/permill'); 1301 throw new FormatException('Too many percent/permill');
864 } 1302 }
865 format._multiplier = _PER_MILLE_SCALE; 1303 format._multiplier = _PER_MILLE_SCALE;
866 affix.write(symbols.PERMILL); 1304 affix.write(symbols.PERMILL);
867 break; 1305 break;
868 default: 1306 default:
869 affix.write(ch); 1307 affix.write(ch);
870 } 1308 }
871 } 1309 }
872 return true; 1310 return true;
873 } 1311 }
874 1312
875 /** Variables used in [_parseTrunk] and [parseTrunkCharacter]. */ 1313 /// Variables used in [_parseTrunk] and [parseTrunkCharacter].
876 var decimalPos = -1; 1314 var decimalPos = -1;
877 var digitLeftCount = 0; 1315 var digitLeftCount = 0;
878 var zeroDigitCount = 0; 1316 var zeroDigitCount = 0;
879 var digitRightCount = 0; 1317 var digitRightCount = 0;
880 var groupingCount = -1; 1318 var groupingCount = -1;
881 1319
882 /** 1320 /// Parse the "trunk" portion of the pattern, the piece that doesn't include
883 * Parse the "trunk" portion of the pattern, the piece that doesn't include 1321 /// positive or negative prefixes or suffixes.
884 * positive or negative prefixes or suffixes.
885 */
886 String _parseTrunk() { 1322 String _parseTrunk() {
887 var loop = true; 1323 var loop = true;
888 var trunk = new StringBuffer(); 1324 var trunk = new StringBuffer();
889 while (pattern.current != null && loop) { 1325 while (pattern.current != null && loop) {
890 loop = parseTrunkCharacter(trunk); 1326 loop = parseTrunkCharacter(trunk);
891 } 1327 }
892 1328
893 if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { 1329 if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
894 // Handle '###.###' and '###.' and '.###' 1330 // Handle '###.###' and '###.' and '.###'
895 // Handle '.###' 1331 // Handle '.###'
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
938 format._finalGroupingSize = max(0, groupingCount); 1374 format._finalGroupingSize = max(0, groupingCount);
939 if (!format._groupingSizeSetExplicitly) { 1375 if (!format._groupingSizeSetExplicitly) {
940 format._groupingSize = format._finalGroupingSize; 1376 format._groupingSize = format._finalGroupingSize;
941 } 1377 }
942 format._decimalSeparatorAlwaysShown = 1378 format._decimalSeparatorAlwaysShown =
943 decimalPos == 0 || decimalPos == totalDigits; 1379 decimalPos == 0 || decimalPos == totalDigits;
944 1380
945 return trunk.toString(); 1381 return trunk.toString();
946 } 1382 }
947 1383
948 /** 1384 /// Parse an individual character of the trunk. Return true if we should
949 * Parse an individual character of the trunk. Return true if we should 1385 /// continue to look for additional trunk characters or false if we have
950 * continue to look for additional trunk characters or false if we have 1386 /// reached the end.
951 * reached the end.
952 */
953 bool parseTrunkCharacter(trunk) { 1387 bool parseTrunkCharacter(trunk) {
954 var ch = pattern.current; 1388 var ch = pattern.current;
955 switch (ch) { 1389 switch (ch) {
956 case _PATTERN_DIGIT: 1390 case _PATTERN_DIGIT:
957 if (zeroDigitCount > 0) { 1391 if (zeroDigitCount > 0) {
958 digitRightCount++; 1392 digitRightCount++;
959 } else { 1393 } else {
960 digitLeftCount++; 1394 digitLeftCount++;
961 } 1395 }
962 if (groupingCount >= 0 && decimalPos < 0) { 1396 if (groupingCount >= 0 && decimalPos < 0) {
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
1020 return false; 1454 return false;
1021 default: 1455 default:
1022 return false; 1456 return false;
1023 } 1457 }
1024 trunk.write(ch); 1458 trunk.write(ch);
1025 pattern.moveNext(); 1459 pattern.moveNext();
1026 return true; 1460 return true;
1027 } 1461 }
1028 } 1462 }
1029 1463
1030 /** 1464 /// Returns an [Iterable] on the string as a list of substrings.
1031 * Returns an [Iterable] on the string as a list of substrings.
1032 */
1033 Iterable _iterable(String s) => new _StringIterable(s); 1465 Iterable _iterable(String s) => new _StringIterable(s);
1034 1466
1035 /** 1467 /// Return an iterator on the string as a list of substrings.
1036 * Return an iterator on the string as a list of substrings. 1468 Iterator<String> _iterator(String s) => new _StringIterator(s);
1037 */
1038 Iterator _iterator(String s) => new _StringIterator(s);
1039 1469
1040 // TODO(nweiz): remove this when issue 3780 is fixed. 1470 // TODO(nweiz): remove this when issue 3780 is fixed.
1041 /** 1471 /// Provides an Iterable that wraps [_iterator] so it can be used in a `for`
1042 * Provides an Iterable that wraps [_iterator] so it can be used in a `for` 1472 /// loop.
1043 * loop.
1044 */
1045 class _StringIterable extends IterableBase<String> { 1473 class _StringIterable extends IterableBase<String> {
1046 final Iterator<String> iterator; 1474 final Iterator<String> iterator;
1047 1475
1048 _StringIterable(String s) : iterator = _iterator(s); 1476 _StringIterable(String s) : iterator = _iterator(s);
1049 } 1477 }
1050 1478
1051 /** 1479 /// Provides an iterator over a string as a list of substrings, and also
1052 * Provides an iterator over a string as a list of substrings, and also 1480 /// gives us a lookahead of one via the [peek] method.
1053 * gives us a lookahead of one via the [peek] method.
1054 */
1055 class _StringIterator implements Iterator<String> { 1481 class _StringIterator implements Iterator<String> {
1056 final String input; 1482 final String input;
1057 int nextIndex = 0; 1483 int nextIndex = 0;
1058 String _current = null; 1484 String _current = null;
1059 1485
1060 _StringIterator(input) : input = _validate(input); 1486 _StringIterator(input) : input = _validate(input);
1061 1487
1062 String get current => _current; 1488 String get current => _current;
1063 1489
1064 bool moveNext() { 1490 bool moveNext() {
1065 if (nextIndex >= input.length) { 1491 if (nextIndex >= input.length) {
1066 _current = null; 1492 _current = null;
1067 return false; 1493 return false;
1068 } 1494 }
1069 _current = input[nextIndex++]; 1495 _current = input[nextIndex++];
1070 return true; 1496 return true;
1071 } 1497 }
1072 1498
1073 String get peek => nextIndex >= input.length ? null : input[nextIndex]; 1499 String get peek => nextIndex >= input.length ? null : input[nextIndex];
1074 1500
1075 Iterator<String> get iterator => this; 1501 Iterator<String> get iterator => this;
1076 1502
1077 static String _validate(input) { 1503 static String _validate(input) {
1078 if (input is! String) throw new ArgumentError(input); 1504 if (input is! String) throw new ArgumentError(input);
1079 return input; 1505 return input;
1080 } 1506 }
1081 } 1507 }
1508
1509 /// Used primarily for currency formatting, this number-like class stores
1510 /// millionths of a currency unit, typically as an Int64.
1511 ///
1512 /// It supports no operations other than being used for Intl number formatting.
1513 abstract class MicroMoney {
1514 factory MicroMoney(micros) => new _MicroMoney(micros);
1515 }
1516
1517 /// Used primarily for currency formatting, this stores millionths of a
1518 /// currency unit, typically as an Int64.
1519 ///
1520 /// This private class provides the operations needed by the formatting code.
1521 class _MicroMoney implements MicroMoney {
1522 var _micros;
1523 _MicroMoney(this._micros);
1524 static const _multiplier = 1000000;
1525
1526 get _integerPart => _micros ~/ _multiplier;
1527 int get _fractionPart => (this - _integerPart)._micros.toInt().abs();
1528
1529 bool get isNegative => _micros.isNegative;
1530
1531 _MicroMoney abs() => isNegative ? new _MicroMoney(_micros.abs()) : this;
1532
1533 // Note that if this is done in a general way there's a risk of integer
1534 // overflow on JS when multiplying out the [other] parameter, which may be
1535 // an Int64. In formatting we only ever subtract out our own integer part.
1536 _MicroMoney operator -(other) {
1537 if (other is _MicroMoney) return new _MicroMoney(_micros - other._micros);
1538 return new _MicroMoney(_micros - (other * _multiplier));
1539 }
1540
1541 _MicroMoney operator +(other) {
1542 if (other is _MicroMoney) return new _MicroMoney(_micros + other._micros);
1543 return new _MicroMoney(_micros + (other * _multiplier));
1544 }
1545
1546 _MicroMoney operator ~/(divisor) {
1547 if (divisor is! int) {
1548 throw new ArgumentError.value(
1549 divisor, 'divisor', '_MicroMoney ~/ only supports int arguments.');
1550 }
1551 return new _MicroMoney((_integerPart ~/ divisor) * _multiplier);
1552 }
1553
1554 _MicroMoney operator *(other) {
1555 if (other is! int) {
1556 throw new ArgumentError.value(
1557 other, 'other', '_MicroMoney * only supports int arguments.');
1558 }
1559 return new _MicroMoney(
1560 (_integerPart * other) * _multiplier + (_fractionPart * other));
1561 }
1562
1563 /// Note that this only really supports remainder from an int,
1564 /// not division by another MicroMoney
1565 _MicroMoney remainder(other) {
1566 if (other is! int) {
1567 throw new ArgumentError.value(
1568 other, 'other', '_MicroMoney.remainder only supports int arguments.');
1569 }
1570 return new _MicroMoney(_micros.remainder(other * _multiplier));
1571 }
1572
1573 double toDouble() => _micros.toDouble() / _multiplier;
1574
1575 int toInt() => _integerPart.toInt();
1576
1577 String toString() {
1578 var beforeDecimal = _integerPart.toString();
1579 var decimalPart = '';
1580 var fractionPart = _fractionPart;
1581 if (fractionPart != 0) {
1582 decimalPart = '.' + fractionPart.toString();
1583 }
1584 return '$beforeDecimal$decimalPart';
1585 }
1586 }
OLDNEW
« no previous file with comments | « packages/intl/lib/src/intl/date_format_helpers.dart ('k') | packages/intl/lib/src/intl_helpers.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698