hjg
2023-11-17 3780c5e65b05bf23020810798babc6d20311fa79
提交 | 用户 | 时间
58d006 1 /*!
A 2  * Globalize
3  *
4  * http://github.com/jquery/globalize
5  *
6  * Copyright Software Freedom Conservancy, Inc.
7  * Dual licensed under the MIT or GPL Version 2 licenses.
8  * http://jquery.org/license
9  */
10
11 (function( window, undefined ) {
12
13 var Globalize,
14     // private variables
15     regexHex,
16     regexInfinity,
17     regexParseFloat,
18     regexTrim,
19     // private JavaScript utility functions
20     arrayIndexOf,
21     endsWith,
22     extend,
23     isArray,
24     isFunction,
25     isObject,
26     startsWith,
27     trim,
28     truncate,
29     zeroPad,
30     // private Globalization utility functions
31     appendPreOrPostMatch,
32     expandFormat,
33     formatDate,
34     formatNumber,
35     getTokenRegExp,
36     getEra,
37     getEraYear,
38     parseExact,
39     parseNegativePattern;
40
41 // Global variable (Globalize) or CommonJS module (globalize)
42 Globalize = function( cultureSelector ) {
43     return new Globalize.prototype.init( cultureSelector );
44 };
45
46 if ( typeof require !== "undefined" &&
47     typeof exports !== "undefined" &&
48     typeof module !== "undefined" ) {
49     // Assume CommonJS
50     module.exports = Globalize;
51 } else {
52     // Export as global variable
53     window.Globalize = Globalize;
54 }
55
56 Globalize.cultures = {};
57
58 Globalize.prototype = {
59     constructor: Globalize,
60     init: function( cultureSelector ) {
61         this.cultures = Globalize.cultures;
62         this.cultureSelector = cultureSelector;
63
64         return this;
65     }
66 };
67 Globalize.prototype.init.prototype = Globalize.prototype;
68
69 // 1. When defining a culture, all fields are required except the ones stated as optional.
70 // 2. Each culture should have a ".calendars" object with at least one calendar named "standard"
71 //    which serves as the default calendar in use by that culture.
72 // 3. Each culture should have a ".calendar" object which is the current calendar being used,
73 //    it may be dynamically changed at any time to one of the calendars in ".calendars".
74 Globalize.cultures[ "default" ] = {
75     // A unique name for the culture in the form <language code>-<country/region code>
76     name: "en",
77     // the name of the culture in the english language
78     englishName: "English",
79     // the name of the culture in its own language
80     nativeName: "English",
81     // whether the culture uses right-to-left text
82     isRTL: false,
83     // "language" is used for so-called "specific" cultures.
84     // For example, the culture "es-CL" means "Spanish, in Chili".
85     // It represents the Spanish-speaking culture as it is in Chili,
86     // which might have different formatting rules or even translations
87     // than Spanish in Spain. A "neutral" culture is one that is not
88     // specific to a region. For example, the culture "es" is the generic
89     // Spanish culture, which may be a more generalized version of the language
90     // that may or may not be what a specific culture expects.
91     // For a specific culture like "es-CL", the "language" field refers to the
92     // neutral, generic culture information for the language it is using.
93     // This is not always a simple matter of the string before the dash.
94     // For example, the "zh-Hans" culture is netural (Simplified Chinese).
95     // And the "zh-SG" culture is Simplified Chinese in Singapore, whose lanugage
96     // field is "zh-CHS", not "zh".
97     // This field should be used to navigate from a specific culture to it's
98     // more general, neutral culture. If a culture is already as general as it
99     // can get, the language may refer to itself.
100     language: "en",
101     // numberFormat defines general number formatting rules, like the digits in
102     // each grouping, the group separator, and how negative numbers are displayed.
103     numberFormat: {
104         // [negativePattern]
105         // Note, numberFormat.pattern has no "positivePattern" unlike percent and currency,
106         // but is still defined as an array for consistency with them.
107         //   negativePattern: one of "(n)|-n|- n|n-|n -"
108         pattern: [ "-n" ],
109         // number of decimal places normally shown
110         decimals: 2,
111         // string that separates number groups, as in 1,000,000
112         ",": ",",
113         // string that separates a number from the fractional portion, as in 1.99
114         ".": ".",
115         // array of numbers indicating the size of each number group.
116         // TODO: more detailed description and example
117         groupSizes: [ 3 ],
118         // symbol used for positive numbers
119         "+": "+",
120         // symbol used for negative numbers
121         "-": "-",
122         // symbol used for NaN (Not-A-Number)
123         "NaN": "NaN",
124         // symbol used for Negative Infinity
125         negativeInfinity: "-Infinity",
126         // symbol used for Positive Infinity
127         positiveInfinity: "Infinity",
128         percent: {
129             // [negativePattern, positivePattern]
130             //   negativePattern: one of "-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %"
131             //   positivePattern: one of "n %|n%|%n|% n"
132             pattern: [ "-n %", "n %" ],
133             // number of decimal places normally shown
134             decimals: 2,
135             // array of numbers indicating the size of each number group.
136             // TODO: more detailed description and example
137             groupSizes: [ 3 ],
138             // string that separates number groups, as in 1,000,000
139             ",": ",",
140             // string that separates a number from the fractional portion, as in 1.99
141             ".": ".",
142             // symbol used to represent a percentage
143             symbol: "%"
144         },
145         currency: {
146             // [negativePattern, positivePattern]
147             //   negativePattern: one of "($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)"
148             //   positivePattern: one of "$n|n$|$ n|n $"
149             pattern: [ "($n)", "$n" ],
150             // number of decimal places normally shown
151             decimals: 2,
152             // array of numbers indicating the size of each number group.
153             // TODO: more detailed description and example
154             groupSizes: [ 3 ],
155             // string that separates number groups, as in 1,000,000
156             ",": ",",
157             // string that separates a number from the fractional portion, as in 1.99
158             ".": ".",
159             // symbol used to represent currency
160             symbol: "$"
161         }
162     },
163     // calendars defines all the possible calendars used by this culture.
164     // There should be at least one defined with name "standard", and is the default
165     // calendar used by the culture.
166     // A calendar contains information about how dates are formatted, information about
167     // the calendar's eras, a standard set of the date formats,
168     // translations for day and month names, and if the calendar is not based on the Gregorian
169     // calendar, conversion functions to and from the Gregorian calendar.
170     calendars: {
171         standard: {
172             // name that identifies the type of calendar this is
173             name: "Gregorian_USEnglish",
174             // separator of parts of a date (e.g. "/" in 11/05/1955)
175             "/": "/",
176             // separator of parts of a time (e.g. ":" in 05:44 PM)
177             ":": ":",
178             // the first day of the week (0 = Sunday, 1 = Monday, etc)
179             firstDay: 0,
180             days: {
181                 // full day names
182                 names: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
183                 // abbreviated day names
184                 namesAbbr: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
185                 // shortest day names
186                 namesShort: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ]
187             },
188             months: {
189                 // full month names (13 months for lunar calendards -- 13th month should be "" if not lunar)
190                 names: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" ],
191                 // abbreviated month names
192                 namesAbbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" ]
193             },
194             // AM and PM designators in one of these forms:
195             // The usual view, and the upper and lower case versions
196             //   [ standard, lowercase, uppercase ]
197             // The culture does not use AM or PM (likely all standard date formats use 24 hour time)
198             //   null
199             AM: [ "AM", "am", "AM" ],
200             PM: [ "PM", "pm", "PM" ],
201             eras: [
202                 // eras in reverse chronological order.
203                 // name: the name of the era in this culture (e.g. A.D., C.E.)
204                 // start: when the era starts in ticks (gregorian, gmt), null if it is the earliest supported era.
205                 // offset: offset in years from gregorian calendar
206                 {
207                     "name": "A.D.",
208                     "start": null,
209                     "offset": 0
210                 }
211             ],
212             // when a two digit year is given, it will never be parsed as a four digit
213             // year greater than this year (in the appropriate era for the culture)
214             // Set it as a full year (e.g. 2029) or use an offset format starting from
215             // the current year: "+19" would correspond to 2029 if the current year 2010.
216             twoDigitYearMax: 2029,
217             // set of predefined date and time patterns used by the culture
218             // these represent the format someone in this culture would expect
219             // to see given the portions of the date that are shown.
220             patterns: {
221                 // short date pattern
222                 d: "M/d/yyyy",
223                 // long date pattern
224                 D: "dddd, MMMM dd, yyyy",
225                 // short time pattern
226                 t: "h:mm tt",
227                 // long time pattern
228                 T: "h:mm:ss tt",
229                 // long date, short time pattern
230                 f: "dddd, MMMM dd, yyyy h:mm tt",
231                 // long date, long time pattern
232                 F: "dddd, MMMM dd, yyyy h:mm:ss tt",
233                 // month/day pattern
234                 M: "MMMM dd",
235                 // month/year pattern
236                 Y: "yyyy MMMM",
237                 // S is a sortable format that does not vary by culture
238                 S: "yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss"
239             }
240             // optional fields for each calendar:
241             /*
242             monthsGenitive:
243                 Same as months but used when the day preceeds the month.
244                 Omit if the culture has no genitive distinction in month names.
245                 For an explaination of genitive months, see http://blogs.msdn.com/michkap/archive/2004/12/25/332259.aspx
246             convert:
247                 Allows for the support of non-gregorian based calendars. This convert object is used to
248                 to convert a date to and from a gregorian calendar date to handle parsing and formatting.
249                 The two functions:
250                     fromGregorian( date )
251                         Given the date as a parameter, return an array with parts [ year, month, day ]
252                         corresponding to the non-gregorian based year, month, and day for the calendar.
253                     toGregorian( year, month, day )
254                         Given the non-gregorian year, month, and day, return a new Date() object
255                         set to the corresponding date in the gregorian calendar.
256             */
257         }
258     },
259     // For localized strings
260     messages: {}
261 };
262
263 Globalize.cultures[ "default" ].calendar = Globalize.cultures[ "default" ].calendars.standard;
264
265 Globalize.cultures.en = Globalize.cultures[ "default" ];
266
267 Globalize.cultureSelector = "en";
268
269 //
270 // private variables
271 //
272
273 regexHex = /^0x[a-f0-9]+$/i;
274 regexInfinity = /^[+\-]?infinity$/i;
275 regexParseFloat = /^[+\-]?\d*\.?\d*(e[+\-]?\d+)?$/;
276 regexTrim = /^\s+|\s+$/g;
277
278 //
279 // private JavaScript utility functions
280 //
281
282 arrayIndexOf = function( array, item ) {
283     if ( array.indexOf ) {
284         return array.indexOf( item );
285     }
286     for ( var i = 0, length = array.length; i < length; i++ ) {
287         if ( array[i] === item ) {
288             return i;
289         }
290     }
291     return -1;
292 };
293
294 endsWith = function( value, pattern ) {
295     return value.substr( value.length - pattern.length ) === pattern;
296 };
297
298 extend = function() {
299     var options, name, src, copy, copyIsArray, clone,
300         target = arguments[0] || {},
301         i = 1,
302         length = arguments.length,
303         deep = false;
304
305     // Handle a deep copy situation
306     if ( typeof target === "boolean" ) {
307         deep = target;
308         target = arguments[1] || {};
309         // skip the boolean and the target
310         i = 2;
311     }
312
313     // Handle case when target is a string or something (possible in deep copy)
314     if ( typeof target !== "object" && !isFunction(target) ) {
315         target = {};
316     }
317
318     for ( ; i < length; i++ ) {
319         // Only deal with non-null/undefined values
320         if ( (options = arguments[ i ]) != null ) {
321             // Extend the base object
322             for ( name in options ) {
323                 src = target[ name ];
324                 copy = options[ name ];
325
326                 // Prevent never-ending loop
327                 if ( target === copy ) {
328                     continue;
329                 }
330
331                 // Recurse if we're merging plain objects or arrays
332                 if ( deep && copy && ( isObject(copy) || (copyIsArray = isArray(copy)) ) ) {
333                     if ( copyIsArray ) {
334                         copyIsArray = false;
335                         clone = src && isArray(src) ? src : [];
336
337                     } else {
338                         clone = src && isObject(src) ? src : {};
339                     }
340
341                     // Never move original objects, clone them
342                     target[ name ] = extend( deep, clone, copy );
343
344                 // Don't bring in undefined values
345                 } else if ( copy !== undefined ) {
346                     target[ name ] = copy;
347                 }
348             }
349         }
350     }
351
352     // Return the modified object
353     return target;
354 };
355
356 isArray = Array.isArray || function( obj ) {
357     return Object.prototype.toString.call( obj ) === "[object Array]";
358 };
359
360 isFunction = function( obj ) {
361     return Object.prototype.toString.call( obj ) === "[object Function]";
362 };
363
364 isObject = function( obj ) {
365     return Object.prototype.toString.call( obj ) === "[object Object]";
366 };
367
368 startsWith = function( value, pattern ) {
369     return value.indexOf( pattern ) === 0;
370 };
371
372 trim = function( value ) {
373     return ( value + "" ).replace( regexTrim, "" );
374 };
375
376 truncate = function( value ) {
377     if ( isNaN( value ) ) {
378         return NaN;
379     }
380     return Math[ value < 0 ? "ceil" : "floor" ]( value );
381 };
382
383 zeroPad = function( str, count, left ) {
384     var l;
385     for ( l = str.length; l < count; l += 1 ) {
386         str = ( left ? ("0" + str) : (str + "0") );
387     }
388     return str;
389 };
390
391 //
392 // private Globalization utility functions
393 //
394
395 appendPreOrPostMatch = function( preMatch, strings ) {
396     // appends pre- and post- token match strings while removing escaped characters.
397     // Returns a single quote count which is used to determine if the token occurs
398     // in a string literal.
399     var quoteCount = 0,
400         escaped = false;
401     for ( var i = 0, il = preMatch.length; i < il; i++ ) {
402         var c = preMatch.charAt( i );
403         switch ( c ) {
404             case "\'":
405                 if ( escaped ) {
406                     strings.push( "\'" );
407                 }
408                 else {
409                     quoteCount++;
410                 }
411                 escaped = false;
412                 break;
413             case "\\":
414                 if ( escaped ) {
415                     strings.push( "\\" );
416                 }
417                 escaped = !escaped;
418                 break;
419             default:
420                 strings.push( c );
421                 escaped = false;
422                 break;
423         }
424     }
425     return quoteCount;
426 };
427
428 expandFormat = function( cal, format ) {
429     // expands unspecified or single character date formats into the full pattern.
430     format = format || "F";
431     var pattern,
432         patterns = cal.patterns,
433         len = format.length;
434     if ( len === 1 ) {
435         pattern = patterns[ format ];
436         if ( !pattern ) {
437             throw "Invalid date format string \'" + format + "\'.";
438         }
439         format = pattern;
440     }
441     else if ( len === 2 && format.charAt(0) === "%" ) {
442         // %X escape format -- intended as a custom format string that is only one character, not a built-in format.
443         format = format.charAt( 1 );
444     }
445     return format;
446 };
447
448 formatDate = function( value, format, culture ) {
449     var cal = culture.calendar,
450         convert = cal.convert,
451         ret;
452
453     if ( !format || !format.length || format === "i" ) {
454         if ( culture && culture.name.length ) {
455             if ( convert ) {
456                 // non-gregorian calendar, so we cannot use built-in toLocaleString()
457                 ret = formatDate( value, cal.patterns.F, culture );
458             }
459             else {
460                 var eraDate = new Date( value.getTime() ),
461                     era = getEra( value, cal.eras );
462                 eraDate.setFullYear( getEraYear(value, cal, era) );
463                 ret = eraDate.toLocaleString();
464             }
465         }
466         else {
467             ret = value.toString();
468         }
469         return ret;
470     }
471
472     var eras = cal.eras,
473         sortable = format === "s";
474     format = expandFormat( cal, format );
475
476     // Start with an empty string
477     ret = [];
478     var hour,
479         zeros = [ "0", "00", "000" ],
480         foundDay,
481         checkedDay,
482         dayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g,
483         quoteCount = 0,
484         tokenRegExp = getTokenRegExp(),
485         converted;
486
487     function padZeros( num, c ) {
488         var r, s = num + "";
489         if ( c > 1 && s.length < c ) {
490             r = ( zeros[c - 2] + s);
491             return r.substr( r.length - c, c );
492         }
493         else {
494             r = s;
495         }
496         return r;
497     }
498
499     function hasDay() {
500         if ( foundDay || checkedDay ) {
501             return foundDay;
502         }
503         foundDay = dayPartRegExp.test( format );
504         checkedDay = true;
505         return foundDay;
506     }
507
508     function getPart( date, part ) {
509         if ( converted ) {
510             return converted[ part ];
511         }
512         switch ( part ) {
513             case 0:
514                 return date.getFullYear();
515             case 1:
516                 return date.getMonth();
517             case 2:
518                 return date.getDate();
519             default:
520                 throw "Invalid part value " + part;
521         }
522     }
523
524     if ( !sortable && convert ) {
525         converted = convert.fromGregorian( value );
526     }
527
528     for ( ; ; ) {
529         // Save the current index
530         var index = tokenRegExp.lastIndex,
531             // Look for the next pattern
532             ar = tokenRegExp.exec( format );
533
534         // Append the text before the pattern (or the end of the string if not found)
535         var preMatch = format.slice( index, ar ? ar.index : format.length );
536         quoteCount += appendPreOrPostMatch( preMatch, ret );
537
538         if ( !ar ) {
539             break;
540         }
541
542         // do not replace any matches that occur inside a string literal.
543         if ( quoteCount % 2 ) {
544             ret.push( ar[0] );
545             continue;
546         }
547
548         var current = ar[ 0 ],
549             clength = current.length;
550
551         switch ( current ) {
552             case "ddd":
553                 //Day of the week, as a three-letter abbreviation
554             case "dddd":
555                 // Day of the week, using the full name
556                 var names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names;
557                 ret.push( names[value.getDay()] );
558                 break;
559             case "d":
560                 // Day of month, without leading zero for single-digit days
561             case "dd":
562                 // Day of month, with leading zero for single-digit days
563                 foundDay = true;
564                 ret.push(
565                     padZeros( getPart(value, 2), clength )
566                 );
567                 break;
568             case "MMM":
569                 // Month, as a three-letter abbreviation
570             case "MMMM":
571                 // Month, using the full name
572                 var part = getPart( value, 1 );
573                 ret.push(
574                     ( cal.monthsGenitive && hasDay() ) ?
575                     ( cal.monthsGenitive[ clength === 3 ? "namesAbbr" : "names" ][ part ] ) :
576                     ( cal.months[ clength === 3 ? "namesAbbr" : "names" ][ part ] )
577                 );
578                 break;
579             case "M":
580                 // Month, as digits, with no leading zero for single-digit months
581             case "MM":
582                 // Month, as digits, with leading zero for single-digit months
583                 ret.push(
584                     padZeros( getPart(value, 1) + 1, clength )
585                 );
586                 break;
587             case "y":
588                 // Year, as two digits, but with no leading zero for years less than 10
589             case "yy":
590                 // Year, as two digits, with leading zero for years less than 10
591             case "yyyy":
592                 // Year represented by four full digits
593                 part = converted ? converted[ 0 ] : getEraYear( value, cal, getEra(value, eras), sortable );
594                 if ( clength < 4 ) {
595                     part = part % 100;
596                 }
597                 ret.push(
598                     padZeros( part, clength )
599                 );
600                 break;
601             case "h":
602                 // Hours with no leading zero for single-digit hours, using 12-hour clock
603             case "hh":
604                 // Hours with leading zero for single-digit hours, using 12-hour clock
605                 hour = value.getHours() % 12;
606                 if ( hour === 0 ) hour = 12;
607                 ret.push(
608                     padZeros( hour, clength )
609                 );
610                 break;
611             case "H":
612                 // Hours with no leading zero for single-digit hours, using 24-hour clock
613             case "HH":
614                 // Hours with leading zero for single-digit hours, using 24-hour clock
615                 ret.push(
616                     padZeros( value.getHours(), clength )
617                 );
618                 break;
619             case "m":
620                 // Minutes with no leading zero for single-digit minutes
621             case "mm":
622                 // Minutes with leading zero for single-digit minutes
623                 ret.push(
624                     padZeros( value.getMinutes(), clength )
625                 );
626                 break;
627             case "s":
628                 // Seconds with no leading zero for single-digit seconds
629             case "ss":
630                 // Seconds with leading zero for single-digit seconds
631                 ret.push(
632                     padZeros( value.getSeconds(), clength )
633                 );
634                 break;
635             case "t":
636                 // One character am/pm indicator ("a" or "p")
637             case "tt":
638                 // Multicharacter am/pm indicator
639                 part = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : " " ) : ( cal.PM ? cal.PM[0] : " " );
640                 ret.push( clength === 1 ? part.charAt(0) : part );
641                 break;
642             case "f":
643                 // Deciseconds
644             case "ff":
645                 // Centiseconds
646             case "fff":
647                 // Milliseconds
648                 ret.push(
649                     padZeros( value.getMilliseconds(), 3 ).substr( 0, clength )
650                 );
651                 break;
652             case "z":
653                 // Time zone offset, no leading zero
654             case "zz":
655                 // Time zone offset with leading zero
656                 hour = value.getTimezoneOffset() / 60;
657                 ret.push(
658                     ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), clength )
659                 );
660                 break;
661             case "zzz":
662                 // Time zone offset with leading zero
663                 hour = value.getTimezoneOffset() / 60;
664                 ret.push(
665                     ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), 2 ) +
666                     // Hard coded ":" separator, rather than using cal.TimeSeparator
667                     // Repeated here for consistency, plus ":" was already assumed in date parsing.
668                     ":" + padZeros( Math.abs(value.getTimezoneOffset() % 60), 2 )
669                 );
670                 break;
671             case "g":
672             case "gg":
673                 if ( cal.eras ) {
674                     ret.push(
675                         cal.eras[ getEra(value, eras) ].name
676                     );
677                 }
678                 break;
679         case "/":
680             ret.push( cal["/"] );
681             break;
682         default:
683             throw "Invalid date format pattern \'" + current + "\'.";
684         }
685     }
686     return ret.join( "" );
687 };
688
689 // formatNumber
690 (function() {
691     var expandNumber;
692
693     expandNumber = function( number, precision, formatInfo ) {
694         var groupSizes = formatInfo.groupSizes,
695             curSize = groupSizes[ 0 ],
696             curGroupIndex = 1,
697             factor = Math.pow( 10, precision ),
698             rounded = Math.round( number * factor ) / factor;
699
700         if ( !isFinite(rounded) ) {
701             rounded = number;
702         }
703         number = rounded;
704
705         var numberString = number+"",
706             right = "",
707             split = numberString.split( /e/i ),
708             exponent = split.length > 1 ? parseInt( split[1], 10 ) : 0;
709         numberString = split[ 0 ];
710         split = numberString.split( "." );
711         numberString = split[ 0 ];
712         right = split.length > 1 ? split[ 1 ] : "";
713
714         if ( exponent > 0 ) {
715             right = zeroPad( right, exponent, false );
716             numberString += right.slice( 0, exponent );
717             right = right.substr( exponent );
718         }
719         else if ( exponent < 0 ) {
720             exponent = -exponent;
721             numberString = zeroPad( numberString, exponent + 1, true );
722             right = numberString.slice( -exponent, numberString.length ) + right;
723             numberString = numberString.slice( 0, -exponent );
724         }
725
726         if ( precision > 0 ) {
727             right = formatInfo[ "." ] +
728                 ( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) );
729         }
730         else {
731             right = "";
732         }
733
734         var stringIndex = numberString.length - 1,
735             sep = formatInfo[ "," ],
736             ret = "";
737
738         while ( stringIndex >= 0 ) {
739             if ( curSize === 0 || curSize > stringIndex ) {
740                 return numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right );
741             }
742             ret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : "" );
743
744             stringIndex -= curSize;
745
746             if ( curGroupIndex < groupSizes.length ) {
747                 curSize = groupSizes[ curGroupIndex ];
748                 curGroupIndex++;
749             }
750         }
751
752         return numberString.slice( 0, stringIndex + 1 ) + sep + ret + right;
753     };
754
755     formatNumber = function( value, format, culture ) {
756         if ( !isFinite(value) ) {
757             if ( value === Infinity ) {
758                 return culture.numberFormat.positiveInfinity;
759             }
760             if ( value === -Infinity ) {
761                 return culture.numberFormat.negativeInfinity;
762             }
763             return culture.numberFormat.NaN;
764         }
765         if ( !format || format === "i" ) {
766             return culture.name.length ? value.toLocaleString() : value.toString();
767         }
768         format = format || "D";
769
770         var nf = culture.numberFormat,
771             number = Math.abs( value ),
772             precision = -1,
773             pattern;
774         if ( format.length > 1 ) precision = parseInt( format.slice(1), 10 );
775
776         var current = format.charAt( 0 ).toUpperCase(),
777             formatInfo;
778
779         switch ( current ) {
780             case "D":
781                 pattern = "n";
782                 number = truncate( number );
783                 if ( precision !== -1 ) {
784                     number = zeroPad( "" + number, precision, true );
785                 }
786                 if ( value < 0 ) number = "-" + number;
787                 break;
788             case "N":
789                 formatInfo = nf;
790                 /* falls through */
791             case "C":
792                 formatInfo = formatInfo || nf.currency;
793                 /* falls through */
794             case "P":
795                 formatInfo = formatInfo || nf.percent;
796                 pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" );
797                 if ( precision === -1 ) precision = formatInfo.decimals;
798                 number = expandNumber( number * (current === "P" ? 100 : 1), precision, formatInfo );
799                 break;
800             default:
801                 throw "Bad number format specifier: " + current;
802         }
803
804         var patternParts = /n|\$|-|%/g,
805             ret = "";
806         for ( ; ; ) {
807             var index = patternParts.lastIndex,
808                 ar = patternParts.exec( pattern );
809
810             ret += pattern.slice( index, ar ? ar.index : pattern.length );
811
812             if ( !ar ) {
813                 break;
814             }
815
816             switch ( ar[0] ) {
817                 case "n":
818                     ret += number;
819                     break;
820                 case "$":
821                     ret += nf.currency.symbol;
822                     break;
823                 case "-":
824                     // don't make 0 negative
825                     if ( /[1-9]/.test(number) ) {
826                         ret += nf[ "-" ];
827                     }
828                     break;
829                 case "%":
830                     ret += nf.percent.symbol;
831                     break;
832             }
833         }
834
835         return ret;
836     };
837
838 }());
839
840 getTokenRegExp = function() {
841     // regular expression for matching date and time tokens in format strings.
842     return (/\/|dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|y|hh|h|HH|H|mm|m|ss|s|tt|t|fff|ff|f|zzz|zz|z|gg|g/g);
843 };
844
845 getEra = function( date, eras ) {
846     if ( !eras ) return 0;
847     var start, ticks = date.getTime();
848     for ( var i = 0, l = eras.length; i < l; i++ ) {
849         start = eras[ i ].start;
850         if ( start === null || ticks >= start ) {
851             return i;
852         }
853     }
854     return 0;
855 };
856
857 getEraYear = function( date, cal, era, sortable ) {
858     var year = date.getFullYear();
859     if ( !sortable && cal.eras ) {
860         // convert normal gregorian year to era-shifted gregorian
861         // year by subtracting the era offset
862         year -= cal.eras[ era ].offset;
863     }
864     return year;
865 };
866
867 // parseExact
868 (function() {
869     var expandYear,
870         getDayIndex,
871         getMonthIndex,
872         getParseRegExp,
873         outOfRange,
874         toUpper,
875         toUpperArray;
876
877     expandYear = function( cal, year ) {
878         // expands 2-digit year into 4 digits.
879         if ( year < 100 ) {
880             var now = new Date(),
881                 era = getEra( now ),
882                 curr = getEraYear( now, cal, era ),
883                 twoDigitYearMax = cal.twoDigitYearMax;
884             twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax;
885             year += curr - ( curr % 100 );
886             if ( year > twoDigitYearMax ) {
887                 year -= 100;
888             }
889         }
890         return year;
891     };
892
893     getDayIndex = function    ( cal, value, abbr ) {
894         var ret,
895             days = cal.days,
896             upperDays = cal._upperDays;
897         if ( !upperDays ) {
898             cal._upperDays = upperDays = [
899                 toUpperArray( days.names ),
900                 toUpperArray( days.namesAbbr ),
901                 toUpperArray( days.namesShort )
902             ];
903         }
904         value = toUpper( value );
905         if ( abbr ) {
906             ret = arrayIndexOf( upperDays[1], value );
907             if ( ret === -1 ) {
908                 ret = arrayIndexOf( upperDays[2], value );
909             }
910         }
911         else {
912             ret = arrayIndexOf( upperDays[0], value );
913         }
914         return ret;
915     };
916
917     getMonthIndex = function( cal, value, abbr ) {
918         var months = cal.months,
919             monthsGen = cal.monthsGenitive || cal.months,
920             upperMonths = cal._upperMonths,
921             upperMonthsGen = cal._upperMonthsGen;
922         if ( !upperMonths ) {
923             cal._upperMonths = upperMonths = [
924                 toUpperArray( months.names ),
925                 toUpperArray( months.namesAbbr )
926             ];
927             cal._upperMonthsGen = upperMonthsGen = [
928                 toUpperArray( monthsGen.names ),
929                 toUpperArray( monthsGen.namesAbbr )
930             ];
931         }
932         value = toUpper( value );
933         var i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value );
934         if ( i < 0 ) {
935             i = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value );
936         }
937         return i;
938     };
939
940     getParseRegExp = function( cal, format ) {
941         // converts a format string into a regular expression with groups that
942         // can be used to extract date fields from a date string.
943         // check for a cached parse regex.
944         var re = cal._parseRegExp;
945         if ( !re ) {
946             cal._parseRegExp = re = {};
947         }
948         else {
949             var reFormat = re[ format ];
950             if ( reFormat ) {
951                 return reFormat;
952             }
953         }
954
955         // expand single digit formats, then escape regular expression characters.
956         var expFormat = expandFormat( cal, format ).replace( /([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1" ),
957             regexp = [ "^" ],
958             groups = [],
959             index = 0,
960             quoteCount = 0,
961             tokenRegExp = getTokenRegExp(),
962             match;
963
964         // iterate through each date token found.
965         while ( (match = tokenRegExp.exec(expFormat)) !== null ) {
966             var preMatch = expFormat.slice( index, match.index );
967             index = tokenRegExp.lastIndex;
968
969             // don't replace any matches that occur inside a string literal.
970             quoteCount += appendPreOrPostMatch( preMatch, regexp );
971             if ( quoteCount % 2 ) {
972                 regexp.push( match[0] );
973                 continue;
974             }
975
976             // add a regex group for the token.
977             var m = match[ 0 ],
978                 len = m.length,
979                 add;
980             switch ( m ) {
981                 case "dddd": case "ddd":
982                 case "MMMM": case "MMM":
983                 case "gg": case "g":
984                     add = "(\\D+)";
985                     break;
986                 case "tt": case "t":
987                     add = "(\\D*)";
988                     break;
989                 case "yyyy":
990                 case "fff":
991                 case "ff":
992                 case "f":
993                     add = "(\\d{" + len + "})";
994                     break;
995                 case "dd": case "d":
996                 case "MM": case "M":
997                 case "yy": case "y":
998                 case "HH": case "H":
999                 case "hh": case "h":
1000                 case "mm": case "m":
1001                 case "ss": case "s":
1002                     add = "(\\d\\d?)";
1003                     break;
1004                 case "zzz":
1005                     add = "([+-]?\\d\\d?:\\d{2})";
1006                     break;
1007                 case "zz": case "z":
1008                     add = "([+-]?\\d\\d?)";
1009                     break;
1010                 case "/":
1011                     add = "(\\/)";
1012                     break;
1013                 default:
1014                     throw "Invalid date format pattern \'" + m + "\'.";
1015             }
1016             if ( add ) {
1017                 regexp.push( add );
1018             }
1019             groups.push( match[0] );
1020         }
1021         appendPreOrPostMatch( expFormat.slice(index), regexp );
1022         regexp.push( "$" );
1023
1024         // allow whitespace to differ when matching formats.
1025         var regexpStr = regexp.join( "" ).replace( /\s+/g, "\\s+" ),
1026             parseRegExp = { "regExp": regexpStr, "groups": groups };
1027
1028         // cache the regex for this format.
1029         return re[ format ] = parseRegExp;
1030     };
1031
1032     outOfRange = function( value, low, high ) {
1033         return value < low || value > high;
1034     };
1035
1036     toUpper = function( value ) {
1037         // "he-IL" has non-breaking space in weekday names.
1038         return value.split( "\u00A0" ).join( " " ).toUpperCase();
1039     };
1040
1041     toUpperArray = function( arr ) {
1042         var results = [];
1043         for ( var i = 0, l = arr.length; i < l; i++ ) {
1044             results[ i ] = toUpper( arr[i] );
1045         }
1046         return results;
1047     };
1048
1049     parseExact = function( value, format, culture ) {
1050         // try to parse the date string by matching against the format string
1051         // while using the specified culture for date field names.
1052         value = trim( value );
1053         var cal = culture.calendar,
1054             // convert date formats into regular expressions with groupings.
1055             // use the regexp to determine the input format and extract the date fields.
1056             parseInfo = getParseRegExp( cal, format ),
1057             match = new RegExp( parseInfo.regExp ).exec( value );
1058         if ( match === null ) {
1059             return null;
1060         }
1061         // found a date format that matches the input.
1062         var groups = parseInfo.groups,
1063             era = null, year = null, month = null, date = null, weekDay = null,
1064             hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null,
1065             pmHour = false;
1066         // iterate the format groups to extract and set the date fields.
1067         for ( var j = 0, jl = groups.length; j < jl; j++ ) {
1068             var matchGroup = match[ j + 1 ];
1069             if ( matchGroup ) {
1070                 var current = groups[ j ],
1071                     clength = current.length,
1072                     matchInt = parseInt( matchGroup, 10 );
1073                 switch ( current ) {
1074                     case "dd": case "d":
1075                         // Day of month.
1076                         date = matchInt;
1077                         // check that date is generally in valid range, also checking overflow below.
1078                         if ( outOfRange(date, 1, 31) ) return null;
1079                         break;
1080                     case "MMM": case "MMMM":
1081                         month = getMonthIndex( cal, matchGroup, clength === 3 );
1082                         if ( outOfRange(month, 0, 11) ) return null;
1083                         break;
1084                     case "M": case "MM":
1085                         // Month.
1086                         month = matchInt - 1;
1087                         if ( outOfRange(month, 0, 11) ) return null;
1088                         break;
1089                     case "y": case "yy":
1090                     case "yyyy":
1091                         year = clength < 4 ? expandYear( cal, matchInt ) : matchInt;
1092                         if ( outOfRange(year, 0, 9999) ) return null;
1093                         break;
1094                     case "h": case "hh":
1095                         // Hours (12-hour clock).
1096                         hour = matchInt;
1097                         if ( hour === 12 ) hour = 0;
1098                         if ( outOfRange(hour, 0, 11) ) return null;
1099                         break;
1100                     case "H": case "HH":
1101                         // Hours (24-hour clock).
1102                         hour = matchInt;
1103                         if ( outOfRange(hour, 0, 23) ) return null;
1104                         break;
1105                     case "m": case "mm":
1106                         // Minutes.
1107                         min = matchInt;
1108                         if ( outOfRange(min, 0, 59) ) return null;
1109                         break;
1110                     case "s": case "ss":
1111                         // Seconds.
1112                         sec = matchInt;
1113                         if ( outOfRange(sec, 0, 59) ) return null;
1114                         break;
1115                     case "tt": case "t":
1116                         // AM/PM designator.
1117                         // see if it is standard, upper, or lower case PM. If not, ensure it is at least one of
1118                         // the AM tokens. If not, fail the parse for this format.
1119                         pmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] );
1120                         if (
1121                             !pmHour && (
1122                                 !cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] )
1123                             )
1124                         ) return null;
1125                         break;
1126                     case "f":
1127                         // Deciseconds.
1128                     case "ff":
1129                         // Centiseconds.
1130                     case "fff":
1131                         // Milliseconds.
1132                         msec = matchInt * Math.pow( 10, 3 - clength );
1133                         if ( outOfRange(msec, 0, 999) ) return null;
1134                         break;
1135                     case "ddd":
1136                         // Day of week.
1137                     case "dddd":
1138                         // Day of week.
1139                         weekDay = getDayIndex( cal, matchGroup, clength === 3 );
1140                         if ( outOfRange(weekDay, 0, 6) ) return null;
1141                         break;
1142                     case "zzz":
1143                         // Time zone offset in +/- hours:min.
1144                         var offsets = matchGroup.split( /:/ );
1145                         if ( offsets.length !== 2 ) return null;
1146                         hourOffset = parseInt( offsets[0], 10 );
1147                         if ( outOfRange(hourOffset, -12, 13) ) return null;
1148                         var minOffset = parseInt( offsets[1], 10 );
1149                         if ( outOfRange(minOffset, 0, 59) ) return null;
1150                         tzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, "-") ? -minOffset : minOffset );
1151                         break;
1152                     case "z": case "zz":
1153                         // Time zone offset in +/- hours.
1154                         hourOffset = matchInt;
1155                         if ( outOfRange(hourOffset, -12, 13) ) return null;
1156                         tzMinOffset = hourOffset * 60;
1157                         break;
1158                     case "g": case "gg":
1159                         var eraName = matchGroup;
1160                         if ( !eraName || !cal.eras ) return null;
1161                         eraName = trim( eraName.toLowerCase() );
1162                         for ( var i = 0, l = cal.eras.length; i < l; i++ ) {
1163                             if ( eraName === cal.eras[i].name.toLowerCase() ) {
1164                                 era = i;
1165                                 break;
1166                             }
1167                         }
1168                         // could not find an era with that name
1169                         if ( era === null ) return null;
1170                         break;
1171                 }
1172             }
1173         }
1174         var result = new Date(), defaultYear, convert = cal.convert;
1175         defaultYear = convert ? convert.fromGregorian( result )[ 0 ] : result.getFullYear();
1176         if ( year === null ) {
1177             year = defaultYear;
1178         }
1179         else if ( cal.eras ) {
1180             // year must be shifted to normal gregorian year
1181             // but not if year was not specified, its already normal gregorian
1182             // per the main if clause above.
1183             year += cal.eras[( era || 0 )].offset;
1184         }
1185         // set default day and month to 1 and January, so if unspecified, these are the defaults
1186         // instead of the current day/month.
1187         if ( month === null ) {
1188             month = 0;
1189         }
1190         if ( date === null ) {
1191             date = 1;
1192         }
1193         // now have year, month, and date, but in the culture's calendar.
1194         // convert to gregorian if necessary
1195         if ( convert ) {
1196             result = convert.toGregorian( year, month, date );
1197             // conversion failed, must be an invalid match
1198             if ( result === null ) return null;
1199         }
1200         else {
1201             // have to set year, month and date together to avoid overflow based on current date.
1202             result.setFullYear( year, month, date );
1203             // check to see if date overflowed for specified month (only checked 1-31 above).
1204             if ( result.getDate() !== date ) return null;
1205             // invalid day of week.
1206             if ( weekDay !== null && result.getDay() !== weekDay ) {
1207                 return null;
1208             }
1209         }
1210         // if pm designator token was found make sure the hours fit the 24-hour clock.
1211         if ( pmHour && hour < 12 ) {
1212             hour += 12;
1213         }
1214         result.setHours( hour, min, sec, msec );
1215         if ( tzMinOffset !== null ) {
1216             // adjust timezone to utc before applying local offset.
1217             var adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() );
1218             // Safari limits hours and minutes to the range of -127 to 127.  We need to use setHours
1219             // to ensure both these fields will not exceed this range.    adjustedMin will range
1220             // somewhere between -1440 and 1500, so we only need to split this into hours.
1221             result.setHours( result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60 );
1222         }
1223         return result;
1224     };
1225 }());
1226
1227 parseNegativePattern = function( value, nf, negativePattern ) {
1228     var neg = nf[ "-" ],
1229         pos = nf[ "+" ],
1230         ret;
1231     switch ( negativePattern ) {
1232         case "n -":
1233             neg = " " + neg;
1234             pos = " " + pos;
1235             /* falls through */
1236         case "n-":
1237             if ( endsWith(value, neg) ) {
1238                 ret = [ "-", value.substr(0, value.length - neg.length) ];
1239             }
1240             else if ( endsWith(value, pos) ) {
1241                 ret = [ "+", value.substr(0, value.length - pos.length) ];
1242             }
1243             break;
1244         case "- n":
1245             neg += " ";
1246             pos += " ";
1247             /* falls through */
1248         case "-n":
1249             if ( startsWith(value, neg) ) {
1250                 ret = [ "-", value.substr(neg.length) ];
1251             }
1252             else if ( startsWith(value, pos) ) {
1253                 ret = [ "+", value.substr(pos.length) ];
1254             }
1255             break;
1256         case "(n)":
1257             if ( startsWith(value, "(") && endsWith(value, ")") ) {
1258                 ret = [ "-", value.substr(1, value.length - 2) ];
1259             }
1260             break;
1261     }
1262     return ret || [ "", value ];
1263 };
1264
1265 //
1266 // public instance functions
1267 //
1268
1269 Globalize.prototype.findClosestCulture = function( cultureSelector ) {
1270     return Globalize.findClosestCulture.call( this, cultureSelector );
1271 };
1272
1273 Globalize.prototype.format = function( value, format, cultureSelector ) {
1274     return Globalize.format.call( this, value, format, cultureSelector );
1275 };
1276
1277 Globalize.prototype.localize = function( key, cultureSelector ) {
1278     return Globalize.localize.call( this, key, cultureSelector );
1279 };
1280
1281 Globalize.prototype.parseInt = function( value, radix, cultureSelector ) {
1282     return Globalize.parseInt.call( this, value, radix, cultureSelector );
1283 };
1284
1285 Globalize.prototype.parseFloat = function( value, radix, cultureSelector ) {
1286     return Globalize.parseFloat.call( this, value, radix, cultureSelector );
1287 };
1288
1289 Globalize.prototype.culture = function( cultureSelector ) {
1290     return Globalize.culture.call( this, cultureSelector );
1291 };
1292
1293 //
1294 // public singleton functions
1295 //
1296
1297 Globalize.addCultureInfo = function( cultureName, baseCultureName, info ) {
1298
1299     var base = {},
1300         isNew = false;
1301
1302     if ( typeof cultureName !== "string" ) {
1303         // cultureName argument is optional string. If not specified, assume info is first
1304         // and only argument. Specified info deep-extends current culture.
1305         info = cultureName;
1306         cultureName = this.culture().name;
1307         base = this.cultures[ cultureName ];
1308     } else if ( typeof baseCultureName !== "string" ) {
1309         // baseCultureName argument is optional string. If not specified, assume info is second
1310         // argument. Specified info deep-extends specified culture.
1311         // If specified culture does not exist, create by deep-extending default
1312         info = baseCultureName;
1313         isNew = ( this.cultures[ cultureName ] == null );
1314         base = this.cultures[ cultureName ] || this.cultures[ "default" ];
1315     } else {
1316         // cultureName and baseCultureName specified. Assume a new culture is being created
1317         // by deep-extending an specified base culture
1318         isNew = true;
1319         base = this.cultures[ baseCultureName ];
1320     }
1321
1322     this.cultures[ cultureName ] = extend(true, {},
1323         base,
1324         info
1325     );
1326     // Make the standard calendar the current culture if it's a new culture
1327     if ( isNew ) {
1328         this.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard;
1329     }
1330 };
1331
1332 Globalize.findClosestCulture = function( name ) {
1333     var match;
1334     if ( !name ) {
1335         return this.findClosestCulture( this.cultureSelector ) || this.cultures[ "default" ];
1336     }
1337     if ( typeof name === "string" ) {
1338         name = name.split( "," );
1339     }
1340     if ( isArray(name) ) {
1341         var lang,
1342             cultures = this.cultures,
1343             list = name,
1344             i, l = list.length,
1345             prioritized = [];
1346         for ( i = 0; i < l; i++ ) {
1347             name = trim( list[i] );
1348             var pri, parts = name.split( ";" );
1349             lang = trim( parts[0] );
1350             if ( parts.length === 1 ) {
1351                 pri = 1;
1352             }
1353             else {
1354                 name = trim( parts[1] );
1355                 if ( name.indexOf("q=") === 0 ) {
1356                     name = name.substr( 2 );
1357                     pri = parseFloat( name );
1358                     pri = isNaN( pri ) ? 0 : pri;
1359                 }
1360                 else {
1361                     pri = 1;
1362                 }
1363             }
1364             prioritized.push({ lang: lang, pri: pri });
1365         }
1366         prioritized.sort(function( a, b ) {
1367             if ( a.pri < b.pri ) {
1368                 return 1;
1369             } else if ( a.pri > b.pri ) {
1370                 return -1;
1371             }
1372             return 0;
1373         });
1374         // exact match
1375         for ( i = 0; i < l; i++ ) {
1376             lang = prioritized[ i ].lang;
1377             match = cultures[ lang ];
1378             if ( match ) {
1379                 return match;
1380             }
1381         }
1382
1383         // neutral language match
1384         for ( i = 0; i < l; i++ ) {
1385             lang = prioritized[ i ].lang;
1386             do {
1387                 var index = lang.lastIndexOf( "-" );
1388                 if ( index === -1 ) {
1389                     break;
1390                 }
1391                 // strip off the last part. e.g. en-US => en
1392                 lang = lang.substr( 0, index );
1393                 match = cultures[ lang ];
1394                 if ( match ) {
1395                     return match;
1396                 }
1397             }
1398             while ( 1 );
1399         }
1400
1401         // last resort: match first culture using that language
1402         for ( i = 0; i < l; i++ ) {
1403             lang = prioritized[ i ].lang;
1404             for ( var cultureKey in cultures ) {
1405                 var culture = cultures[ cultureKey ];
1406                 if ( culture.language === lang ) {
1407                     return culture;
1408                 }
1409             }
1410         }
1411     }
1412     else if ( typeof name === "object" ) {
1413         return name;
1414     }
1415     return match || null;
1416 };
1417
1418 Globalize.format = function( value, format, cultureSelector ) {
1419     var culture = this.findClosestCulture( cultureSelector );
1420     if ( value instanceof Date ) {
1421         value = formatDate( value, format, culture );
1422     }
1423     else if ( typeof value === "number" ) {
1424         value = formatNumber( value, format, culture );
1425     }
1426     return value;
1427 };
1428
1429 Globalize.localize = function( key, cultureSelector ) {
1430     return this.findClosestCulture( cultureSelector ).messages[ key ] ||
1431         this.cultures[ "default" ].messages[ key ];
1432 };
1433
1434 Globalize.parseDate = function( value, formats, culture ) {
1435     culture = this.findClosestCulture( culture );
1436
1437     var date, prop, patterns;
1438     if ( formats ) {
1439         if ( typeof formats === "string" ) {
1440             formats = [ formats ];
1441         }
1442         if ( formats.length ) {
1443             for ( var i = 0, l = formats.length; i < l; i++ ) {
1444                 var format = formats[ i ];
1445                 if ( format ) {
1446                     date = parseExact( value, format, culture );
1447                     if ( date ) {
1448                         break;
1449                     }
1450                 }
1451             }
1452         }
1453     } else {
1454         patterns = culture.calendar.patterns;
1455         for ( prop in patterns ) {
1456             date = parseExact( value, patterns[prop], culture );
1457             if ( date ) {
1458                 break;
1459             }
1460         }
1461     }
1462
1463     return date || null;
1464 };
1465
1466 Globalize.parseInt = function( value, radix, cultureSelector ) {
1467     return truncate( Globalize.parseFloat(value, radix, cultureSelector) );
1468 };
1469
1470 Globalize.parseFloat = function( value, radix, cultureSelector ) {
1471     // radix argument is optional
1472     if ( typeof radix !== "number" ) {
1473         cultureSelector = radix;
1474         radix = 10;
1475     }
1476
1477     var culture = this.findClosestCulture( cultureSelector );
1478     var ret = NaN,
1479         nf = culture.numberFormat;
1480
1481     if ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) {
1482         // remove currency symbol
1483         value = value.replace( culture.numberFormat.currency.symbol, "" );
1484         // replace decimal seperator
1485         value = value.replace( culture.numberFormat.currency["."], culture.numberFormat["."] );
1486     }
1487
1488     //Remove percentage character from number string before parsing
1489     if ( value.indexOf(culture.numberFormat.percent.symbol) > -1){
1490         value = value.replace( culture.numberFormat.percent.symbol, "" );
1491     }
1492
1493     // remove spaces: leading, trailing and between - and number. Used for negative currency pt-BR
1494     value = value.replace( / /g, "" );
1495
1496     // allow infinity or hexidecimal
1497     if ( regexInfinity.test(value) ) {
1498         ret = parseFloat( value );
1499     }
1500     else if ( !radix && regexHex.test(value) ) {
1501         ret = parseInt( value, 16 );
1502     }
1503     else {
1504
1505         // determine sign and number
1506         var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ),
1507             sign = signInfo[ 0 ],
1508             num = signInfo[ 1 ];
1509
1510         // #44 - try parsing as "(n)"
1511         if ( sign === "" && nf.pattern[0] !== "(n)" ) {
1512             signInfo = parseNegativePattern( value, nf, "(n)" );
1513             sign = signInfo[ 0 ];
1514             num = signInfo[ 1 ];
1515         }
1516
1517         // try parsing as "-n"
1518         if ( sign === "" && nf.pattern[0] !== "-n" ) {
1519             signInfo = parseNegativePattern( value, nf, "-n" );
1520             sign = signInfo[ 0 ];
1521             num = signInfo[ 1 ];
1522         }
1523
1524         sign = sign || "+";
1525
1526         // determine exponent and number
1527         var exponent,
1528             intAndFraction,
1529             exponentPos = num.indexOf( "e" );
1530         if ( exponentPos < 0 ) exponentPos = num.indexOf( "E" );
1531         if ( exponentPos < 0 ) {
1532             intAndFraction = num;
1533             exponent = null;
1534         }
1535         else {
1536             intAndFraction = num.substr( 0, exponentPos );
1537             exponent = num.substr( exponentPos + 1 );
1538         }
1539         // determine decimal position
1540         var integer,
1541             fraction,
1542             decSep = nf[ "." ],
1543             decimalPos = intAndFraction.indexOf( decSep );
1544         if ( decimalPos < 0 ) {
1545             integer = intAndFraction;
1546             fraction = null;
1547         }
1548         else {
1549             integer = intAndFraction.substr( 0, decimalPos );
1550             fraction = intAndFraction.substr( decimalPos + decSep.length );
1551         }
1552         // handle groups (e.g. 1,000,000)
1553         var groupSep = nf[ "," ];
1554         integer = integer.split( groupSep ).join( "" );
1555         var altGroupSep = groupSep.replace( /\u00A0/g, " " );
1556         if ( groupSep !== altGroupSep ) {
1557             integer = integer.split( altGroupSep ).join( "" );
1558         }
1559         // build a natively parsable number string
1560         var p = sign + integer;
1561         if ( fraction !== null ) {
1562             p += "." + fraction;
1563         }
1564         if ( exponent !== null ) {
1565             // exponent itself may have a number patternd
1566             var expSignInfo = parseNegativePattern( exponent, nf, "-n" );
1567             p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ];
1568         }
1569         if ( regexParseFloat.test(p) ) {
1570             ret = parseFloat( p );
1571         }
1572     }
1573     return ret;
1574 };
1575
1576 Globalize.culture = function( cultureSelector ) {
1577     // setter
1578     if ( typeof cultureSelector !== "undefined" ) {
1579         this.cultureSelector = cultureSelector;
1580     }
1581     // getter
1582     return this.findClosestCulture( cultureSelector ) || this.cultures[ "default" ];
1583 };
1584
1585 }( this ));