1 if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojo.number"] = true;
3 dojo.provide("dojo.number");
5 dojo.require("dojo.i18n");
6 dojo.requireLocalization("dojo.cldr", "number", null, "zh-cn,zh,ko-kr,pt,en-us,en-gb,de,ja,ja-jp,en,ROOT,en-au,fr,es,ko,zh-tw,it,es-es,de-de");
7 dojo.require("dojo.string");
8 dojo.require("dojo.regexp");
13 // summary: localized formatting and parsing routines for Number
16 dojo.number.__FormatOptions = function(){
18 // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
21 // choose a format type based on the locale from the following:
22 // decimal, scientific, percent, currency. decimal by default.
24 // fixed number of decimal places to show. This overrides any
25 // information in the provided pattern.
27 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
30 // an [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD"
32 // localized currency symbol
34 // override the locale used to determine formatting rules
35 this.pattern = pattern;
39 this.currency = currency;
45 dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){
47 // Format a Number as a String, using locale-specific settings
49 // Create a string from a Number using a known localized pattern.
50 // Formatting patterns appropriate to the locale are chosen from the
51 // [CLDR](http://unicode.org/cldr) as well as the appropriate symbols and
52 // delimiters. See <http://www.unicode.org/reports/tr35/#Number_Elements>
54 // the number to be formatted. If not a valid JavaScript number,
57 options = dojo.mixin({}, options || {});
58 var locale = dojo.i18n.normalizeLocale(options.locale);
59 var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
60 options.customs = bundle;
61 var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
62 if(isNaN(value)){ return null; } // null
63 return dojo.number._applyPattern(value, pattern, options); // String
66 //dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
67 dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough
69 dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){
71 // Apply pattern to format value as a string using options. Gives no
72 // consideration to local customs.
74 // the number to be formatted.
76 // a pattern string as described by
77 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
78 // options: dojo.number.__FormatOptions?
79 // _applyPattern is usually called via `dojo.number.format()` which
80 // populates an extra property in the options parameter, "customs".
81 // The customs object specifies group and decimal parameters if set.
83 //TODO: support escapes
84 options = options || {};
85 var group = options.customs.group;
86 var decimal = options.customs.decimal;
88 var patternList = pattern.split(';');
89 var positivePattern = patternList[0];
90 pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern);
92 //TODO: only test against unescaped
93 if(pattern.indexOf('%') != -1){
95 }else if(pattern.indexOf('\u2030') != -1){
96 value *= 1000; // per mille
97 }else if(pattern.indexOf('\u00a4') != -1){
98 group = options.customs.currencyGroup || group;//mixins instead?
99 decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead?
100 pattern = pattern.replace(/\u00a4{1,3}/, function(match){
101 var prop = ["symbol", "currency", "displayName"][match.length-1];
102 return options[prop] || options.currency || "";
104 }else if(pattern.indexOf('E') != -1){
105 throw new Error("exponential notation not supported");
108 //TODO: support @ sig figs?
109 var numberPatternRE = dojo.number._numberPatternRE;
110 var numberPattern = positivePattern.match(numberPatternRE);
112 throw new Error("unable to find a number expression in pattern: "+pattern);
114 return pattern.replace(numberPatternRE,
115 dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places}));
118 dojo.number.round = function(/*Number*/value, /*Number*/places, /*Number?*/multiple){
120 // Rounds the number at the given number of places
122 // the number to round
124 // the number of decimal places where rounding takes place
126 // rounds next place to nearest multiple
128 var pieces = String(value).split(".");
129 var length = (pieces[1] && pieces[1].length) || 0;
131 var factor = Math.pow(10, places);
132 if(multiple > 0){factor *= 10/multiple;places++;} //FIXME
133 value = Math.round(value * factor)/factor;
135 // truncate to remove any residual floating point values
136 pieces = String(value).split(".");
137 length = (pieces[1] && pieces[1].length) || 0;
139 pieces[1] = pieces[1].substr(0, places);
140 value = Number(pieces.join("."));
143 return value; //Number
147 dojo.number.__FormatAbsoluteOptions = function(){
149 // the decimal separator
151 // the group separator
153 // number of decimal places
155 // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
156 // means don't round.
157 this.decimal = decimal;
159 this.places = places;
164 dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){
166 // Apply numeric pattern to absolute value using options. Gives no
167 // consideration to local customs.
169 // the number to be formatted, ignores sign
171 // the number portion of a pattern (e.g. `#,##0.00`)
172 options = options || {};
173 if(options.places === true){options.places=0;}
174 if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit
176 var patternParts = pattern.split(".");
177 var maxPlaces = (options.places >= 0) ? options.places : (patternParts[1] && patternParts[1].length) || 0;
178 if(!(options.round < 0)){
179 value = dojo.number.round(value, maxPlaces, options.round);
182 var valueParts = String(Math.abs(value)).split(".");
183 var fractional = valueParts[1] || "";
185 valueParts[1] = dojo.string.pad(fractional.substr(0, options.places), options.places, '0', true);
186 }else if(patternParts[1] && options.places !== 0){
187 // Pad fractional with trailing zeros
188 var pad = patternParts[1].lastIndexOf("0") + 1;
189 if(pad > fractional.length){
190 valueParts[1] = dojo.string.pad(fractional, pad, '0', true);
193 // Truncate fractional
194 var places = patternParts[1].length;
195 if(places < fractional.length){
196 valueParts[1] = fractional.substr(0, places);
199 if(valueParts[1]){ valueParts.pop(); }
202 // Pad whole with leading zeros
203 var patternDigits = patternParts[0].replace(',', '');
204 pad = patternDigits.indexOf("0");
206 pad = patternDigits.length - pad;
207 if(pad > valueParts[0].length){
208 valueParts[0] = dojo.string.pad(valueParts[0], pad);
212 if(patternDigits.indexOf("#") == -1){
213 valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
217 // Add group separators
218 var index = patternParts[0].lastIndexOf(',');
219 var groupSize, groupSize2;
221 groupSize = patternParts[0].length - index - 1;
222 var remainder = patternParts[0].substr(0, index);
223 index = remainder.lastIndexOf(',');
225 groupSize2 = remainder.length - index - 1;
229 for(var whole = valueParts[0]; whole;){
230 var off = whole.length - groupSize;
231 pieces.push((off > 0) ? whole.substr(off) : whole);
232 whole = (off > 0) ? whole.slice(0, off) : "";
234 groupSize = groupSize2;
238 valueParts[0] = pieces.reverse().join(options.group || ",");
240 return valueParts.join(options.decimal || ".");
244 dojo.number.__RegexpOptions = function(){
246 // override pattern with this string. Default is provided based on
249 // choose a format type based on the locale from the following:
250 // decimal, scientific, percent, currency. decimal by default.
252 // override the locale used to determine formatting rules
254 // strict parsing, false by default
255 // places: Number|String?
256 // number of decimal places to accept: Infinity, a positive number, or
257 // a range "n,m". By default, defined by pattern.
258 this.pattern = pattern;
260 this.locale = locale;
261 this.strict = strict;
262 this.places = places;
265 dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){
267 // Builds the regular needed to parse a number
269 // Returns regular expression with positive and negative match, group
270 // and decimal separators
271 return dojo.number._parseInfo(options).regexp; // String
274 dojo.number._parseInfo = function(/*Object?*/options){
275 options = options || {};
276 var locale = dojo.i18n.normalizeLocale(options.locale);
277 var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
278 var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
280 var group = bundle.group;
281 var decimal = bundle.decimal;
284 if(pattern.indexOf('%') != -1){
286 }else if(pattern.indexOf('\u2030') != -1){
287 factor /= 1000; // per mille
289 var isCurrency = pattern.indexOf('\u00a4') != -1;
291 group = bundle.currencyGroup || group;
292 decimal = bundle.currencyDecimal || decimal;
296 //TODO: handle quoted escapes
297 var patternList = pattern.split(';');
298 if(patternList.length == 1){
299 patternList.push("-" + patternList[0]);
302 var re = dojo.regexp.buildGroupRE(patternList, function(pattern){
303 pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")";
304 return pattern.replace(dojo.number._numberPatternRE, function(format){
307 separator: options.strict ? group : [group,""],
308 fractional: options.fractional,
311 var parts = format.split('.');
312 var places = options.places;
313 if(parts.length == 1 || places === 0){flags.fractional = false;}
315 if(places === undefined){ places = parts[1].lastIndexOf('0')+1; }
316 if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified
317 if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; }
318 flags.places = places;
320 var groups = parts[0].split(',');
322 flags.groupSize = groups.pop().length;
324 flags.groupSize2 = groups.pop().length;
327 return "("+dojo.number._realNumberRegexp(flags)+")";
332 // substitute the currency symbol for the placeholder in the pattern
333 re = re.replace(/(\s*)(\u00a4{1,3})(\s*)/g, function(match, before, target, after){
334 var prop = ["symbol", "currency", "displayName"][target.length-1];
335 var symbol = dojo.regexp.escapeString(options[prop] || options.currency || "");
336 before = before ? "\\s" : "";
337 after = after ? "\\s" : "";
339 if(before){before += "*";}
340 if(after){after += "*";}
341 return "(?:"+before+symbol+after+")?";
343 return before+symbol+after;
347 //TODO: substitute localized sign/percent/permille/etc.?
349 // normalize whitespace and return
350 return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
354 dojo.number.__ParseOptions = function(){
356 // override pattern with this string. Default is provided based on
359 // choose a format type based on the locale from the following:
360 // decimal, scientific, percent, currency. decimal by default.
362 // override the locale used to determine formatting rules
364 // strict parsing, false by default
366 // object with currency information
367 this.pattern = pattern;
369 this.locale = locale;
370 this.strict = strict;
371 this.currency = currency;
374 dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){
376 // Convert a properly formatted string to a primitive Number, using
377 // locale-specific settings.
379 // Create a Number from a string using a known localized pattern.
380 // Formatting patterns are chosen appropriate to the locale
381 // and follow the syntax described by
382 // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns)
384 // A string representation of a Number
385 var info = dojo.number._parseInfo(options);
386 var results = (new RegExp("^"+info.regexp+"$")).exec(expression);
390 var absoluteMatch = results[1]; // match for the positive expression
395 // matched the negative pattern
396 absoluteMatch =results[2];
400 // Transform it to something Javascript can parse as a number. Normalize
401 // decimal point and strip out group separators or alternate forms of whitespace
402 absoluteMatch = absoluteMatch.
403 replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), "").
404 replace(info.decimal, ".");
405 // Adjust for negative sign, percent, etc. as necessary
406 return Number(absoluteMatch) * info.factor; //Number
410 dojo.number.__RealNumberRegexpFlags = function(){
412 // The integer number of decimal places or a range given as "n,m". If
413 // not given, the decimal part is optional and the number of places is
416 // A string for the character used as the decimal point. Default
418 // fractional: Boolean|Array?
419 // Whether decimal places are allowed. Can be true, false, or [true,
420 // false]. Default is [true, false]
421 // exponent: Boolean|Array?
422 // Express in exponential notation. Can be true, false, or [true,
423 // false]. Default is [true, false], (i.e. will match if the
424 // exponential part is present are not).
425 // eSigned: Boolean|Array?
426 // The leading plus-or-minus sign on the exponent. Can be true,
427 // false, or [true, false]. Default is [true, false], (i.e. will
428 // match if it is signed or unsigned). flags in regexp.integer can be
430 this.places = places;
431 this.decimal = decimal;
432 this.fractional = fractional;
433 this.exponent = exponent;
434 this.eSigned = eSigned;
438 dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){
440 // Builds a regular expression to match a real number in exponential
443 // assign default values to missing paramters
445 //TODO: use mixin instead?
446 if(!("places" in flags)){ flags.places = Infinity; }
447 if(typeof flags.decimal != "string"){ flags.decimal = "."; }
448 if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; }
449 if(!("exponent" in flags)){ flags.exponent = [true, false]; }
450 if(!("eSigned" in flags)){ flags.eSigned = [true, false]; }
453 var integerRE = dojo.number._integerRegexp(flags);
456 var decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
459 if(q && (flags.places!==0)){
460 re = "\\" + flags.decimal;
461 if(flags.places == Infinity){
462 re = "(?:" + re + "\\d+)?";
464 re += "\\d{" + flags.places + "}";
473 var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
475 if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
481 var realRE = integerRE + decimalRE;
482 // allow for decimals without integers, e.g. .25
483 if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";}
484 return realRE + exponentRE; // String
488 dojo.number.__IntegerRegexpFlags = function(){
490 // The leading plus-or-minus sign. Can be true, false, or `[true,false]`.
491 // Default is `[true, false]`, (i.e. will match if it is signed
493 // separator: String?
494 // The character used as the thousands separator. Default is no
495 // separator. For more than one symbol use an array, e.g. `[",", ""]`,
496 // makes ',' optional.
497 // groupSize: Number?
498 // group size between separators
499 // groupSize2: Number?
500 // second grouping, where separators 2..n have a different interval than the first separator (for India)
501 this.signed = signed;
502 this.separator = separator;
503 this.groupSize = groupSize;
504 this.groupSize2 = groupSize2;
508 dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){
510 // Builds a regular expression that matches an integer
512 // assign default values to missing paramters
514 if(!("signed" in flags)){ flags.signed = [true, false]; }
515 if(!("separator" in flags)){
516 flags.separator = "";
517 }else if(!("groupSize" in flags)){
521 var signRE = dojo.regexp.buildGroupRE(flags.signed,
522 function(q) { return q ? "[-+]" : ""; },
527 var numberRE = dojo.regexp.buildGroupRE(flags.separator,
530 return "(?:0|[1-9]\\d*)";
533 sep = dojo.regexp.escapeString(sep);
534 if(sep == " "){ sep = "\\s"; }
535 else if(sep == "\xa0"){ sep = "\\s\\xa0"; }
537 var grp = flags.groupSize, grp2 = flags.groupSize2;
539 var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
540 return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
542 return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
548 return signRE + numberRE; // String