imported the whole jquery-ui package, refreshed with 1.10.2
[myslice.git] / third-party / jquery-ui-1.10.2 / external / globalize.js
1 /*!
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( deep ) {
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         return value | 0;
378 };
379
380 zeroPad = function( str, count, left ) {
381         var l;
382         for ( l = str.length; l < count; l += 1 ) {
383                 str = ( left ? ("0" + str) : (str + "0") );
384         }
385         return str;
386 };
387
388 //
389 // private Globalization utility functions
390 //
391
392 appendPreOrPostMatch = function( preMatch, strings ) {
393         // appends pre- and post- token match strings while removing escaped characters.
394         // Returns a single quote count which is used to determine if the token occurs
395         // in a string literal.
396         var quoteCount = 0,
397                 escaped = false;
398         for ( var i = 0, il = preMatch.length; i < il; i++ ) {
399                 var c = preMatch.charAt( i );
400                 switch ( c ) {
401                         case "\'":
402                                 if ( escaped ) {
403                                         strings.push( "\'" );
404                                 }
405                                 else {
406                                         quoteCount++;
407                                 }
408                                 escaped = false;
409                                 break;
410                         case "\\":
411                                 if ( escaped ) {
412                                         strings.push( "\\" );
413                                 }
414                                 escaped = !escaped;
415                                 break;
416                         default:
417                                 strings.push( c );
418                                 escaped = false;
419                                 break;
420                 }
421         }
422         return quoteCount;
423 };
424
425 expandFormat = function( cal, format ) {
426         // expands unspecified or single character date formats into the full pattern.
427         format = format || "F";
428         var pattern,
429                 patterns = cal.patterns,
430                 len = format.length;
431         if ( len === 1 ) {
432                 pattern = patterns[ format ];
433                 if ( !pattern ) {
434                         throw "Invalid date format string \'" + format + "\'.";
435                 }
436                 format = pattern;
437         }
438         else if ( len === 2 && format.charAt(0) === "%" ) {
439                 // %X escape format -- intended as a custom format string that is only one character, not a built-in format.
440                 format = format.charAt( 1 );
441         }
442         return format;
443 };
444
445 formatDate = function( value, format, culture ) {
446         var cal = culture.calendar,
447                 convert = cal.convert;
448
449         if ( !format || !format.length || format === "i" ) {
450                 var ret;
451                 if ( culture && culture.name.length ) {
452                         if ( convert ) {
453                                 // non-gregorian calendar, so we cannot use built-in toLocaleString()
454                                 ret = formatDate( value, cal.patterns.F, culture );
455                         }
456                         else {
457                                 var eraDate = new Date( value.getTime() ),
458                                         era = getEra( value, cal.eras );
459                                 eraDate.setFullYear( getEraYear(value, cal, era) );
460                                 ret = eraDate.toLocaleString();
461                         }
462                 }
463                 else {
464                         ret = value.toString();
465                 }
466                 return ret;
467         }
468
469         var eras = cal.eras,
470                 sortable = format === "s";
471         format = expandFormat( cal, format );
472
473         // Start with an empty string
474         ret = [];
475         var hour,
476                 zeros = [ "0", "00", "000" ],
477                 foundDay,
478                 checkedDay,
479                 dayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g,
480                 quoteCount = 0,
481                 tokenRegExp = getTokenRegExp(),
482                 converted;
483
484         function padZeros( num, c ) {
485                 var r, s = num + "";
486                 if ( c > 1 && s.length < c ) {
487                         r = ( zeros[c - 2] + s);
488                         return r.substr( r.length - c, c );
489                 }
490                 else {
491                         r = s;
492                 }
493                 return r;
494         }
495
496         function hasDay() {
497                 if ( foundDay || checkedDay ) {
498                         return foundDay;
499                 }
500                 foundDay = dayPartRegExp.test( format );
501                 checkedDay = true;
502                 return foundDay;
503         }
504
505         function getPart( date, part ) {
506                 if ( converted ) {
507                         return converted[ part ];
508                 }
509                 switch ( part ) {
510                         case 0: return date.getFullYear();
511                         case 1: return date.getMonth();
512                         case 2: return date.getDate();
513                 }
514         }
515
516         if ( !sortable && convert ) {
517                 converted = convert.fromGregorian( value );
518         }
519
520         for ( ; ; ) {
521                 // Save the current index
522                 var index = tokenRegExp.lastIndex,
523                         // Look for the next pattern
524                         ar = tokenRegExp.exec( format );
525
526                 // Append the text before the pattern (or the end of the string if not found)
527                 var preMatch = format.slice( index, ar ? ar.index : format.length );
528                 quoteCount += appendPreOrPostMatch( preMatch, ret );
529
530                 if ( !ar ) {
531                         break;
532                 }
533
534                 // do not replace any matches that occur inside a string literal.
535                 if ( quoteCount % 2 ) {
536                         ret.push( ar[0] );
537                         continue;
538                 }
539
540                 var current = ar[ 0 ],
541                         clength = current.length;
542
543                 switch ( current ) {
544                         case "ddd":
545                                 //Day of the week, as a three-letter abbreviation
546                         case "dddd":
547                                 // Day of the week, using the full name
548                                 var names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names;
549                                 ret.push( names[value.getDay()] );
550                                 break;
551                         case "d":
552                                 // Day of month, without leading zero for single-digit days
553                         case "dd":
554                                 // Day of month, with leading zero for single-digit days
555                                 foundDay = true;
556                                 ret.push(
557                                         padZeros( getPart(value, 2), clength )
558                                 );
559                                 break;
560                         case "MMM":
561                                 // Month, as a three-letter abbreviation
562                         case "MMMM":
563                                 // Month, using the full name
564                                 var part = getPart( value, 1 );
565                                 ret.push(
566                                         ( cal.monthsGenitive && hasDay() )
567                                         ?
568                                         cal.monthsGenitive[ clength === 3 ? "namesAbbr" : "names" ][ part ]
569                                         :
570                                         cal.months[ clength === 3 ? "namesAbbr" : "names" ][ part ]
571                                 );
572                                 break;
573                         case "M":
574                                 // Month, as digits, with no leading zero for single-digit months
575                         case "MM":
576                                 // Month, as digits, with leading zero for single-digit months
577                                 ret.push(
578                                         padZeros( getPart(value, 1) + 1, clength )
579                                 );
580                                 break;
581                         case "y":
582                                 // Year, as two digits, but with no leading zero for years less than 10
583                         case "yy":
584                                 // Year, as two digits, with leading zero for years less than 10
585                         case "yyyy":
586                                 // Year represented by four full digits
587                                 part = converted ? converted[ 0 ] : getEraYear( value, cal, getEra(value, eras), sortable );
588                                 if ( clength < 4 ) {
589                                         part = part % 100;
590                                 }
591                                 ret.push(
592                                         padZeros( part, clength )
593                                 );
594                                 break;
595                         case "h":
596                                 // Hours with no leading zero for single-digit hours, using 12-hour clock
597                         case "hh":
598                                 // Hours with leading zero for single-digit hours, using 12-hour clock
599                                 hour = value.getHours() % 12;
600                                 if ( hour === 0 ) hour = 12;
601                                 ret.push(
602                                         padZeros( hour, clength )
603                                 );
604                                 break;
605                         case "H":
606                                 // Hours with no leading zero for single-digit hours, using 24-hour clock
607                         case "HH":
608                                 // Hours with leading zero for single-digit hours, using 24-hour clock
609                                 ret.push(
610                                         padZeros( value.getHours(), clength )
611                                 );
612                                 break;
613                         case "m":
614                                 // Minutes with no leading zero for single-digit minutes
615                         case "mm":
616                                 // Minutes with leading zero for single-digit minutes
617                                 ret.push(
618                                         padZeros( value.getMinutes(), clength )
619                                 );
620                                 break;
621                         case "s":
622                                 // Seconds with no leading zero for single-digit seconds
623                         case "ss":
624                                 // Seconds with leading zero for single-digit seconds
625                                 ret.push(
626                                         padZeros( value.getSeconds(), clength )
627                                 );
628                                 break;
629                         case "t":
630                                 // One character am/pm indicator ("a" or "p")
631                         case "tt":
632                                 // Multicharacter am/pm indicator
633                                 part = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : " " ) : ( cal.PM ? cal.PM[0] : " " );
634                                 ret.push( clength === 1 ? part.charAt(0) : part );
635                                 break;
636                         case "f":
637                                 // Deciseconds
638                         case "ff":
639                                 // Centiseconds
640                         case "fff":
641                                 // Milliseconds
642                                 ret.push(
643                                         padZeros( value.getMilliseconds(), 3 ).substr( 0, clength )
644                                 );
645                                 break;
646                         case "z":
647                                 // Time zone offset, no leading zero
648                         case "zz":
649                                 // Time zone offset with leading zero
650                                 hour = value.getTimezoneOffset() / 60;
651                                 ret.push(
652                                         ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), clength )
653                                 );
654                                 break;
655                         case "zzz":
656                                 // Time zone offset with leading zero
657                                 hour = value.getTimezoneOffset() / 60;
658                                 ret.push(
659                                         ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), 2 )
660                                         // Hard coded ":" separator, rather than using cal.TimeSeparator
661                                         // Repeated here for consistency, plus ":" was already assumed in date parsing.
662                                         + ":" + padZeros( Math.abs(value.getTimezoneOffset() % 60), 2 )
663                                 );
664                                 break;
665                         case "g":
666                         case "gg":
667                                 if ( cal.eras ) {
668                                         ret.push(
669                                                 cal.eras[ getEra(value, eras) ].name
670                                         );
671                                 }
672                                 break;
673                 case "/":
674                         ret.push( cal["/"] );
675                         break;
676                 default:
677                         throw "Invalid date format pattern \'" + current + "\'.";
678                         break;
679                 }
680         }
681         return ret.join( "" );
682 };
683
684 // formatNumber
685 (function() {
686         var expandNumber;
687
688         expandNumber = function( number, precision, formatInfo ) {
689                 var groupSizes = formatInfo.groupSizes,
690                         curSize = groupSizes[ 0 ],
691                         curGroupIndex = 1,
692                         factor = Math.pow( 10, precision ),
693                         rounded = Math.round( number * factor ) / factor;
694
695                 if ( !isFinite(rounded) ) {
696                         rounded = number;
697                 }
698                 number = rounded;
699
700                 var numberString = number+"",
701                         right = "",
702                         split = numberString.split( /e/i ),
703                         exponent = split.length > 1 ? parseInt( split[1], 10 ) : 0;
704                 numberString = split[ 0 ];
705                 split = numberString.split( "." );
706                 numberString = split[ 0 ];
707                 right = split.length > 1 ? split[ 1 ] : "";
708
709                 var l;
710                 if ( exponent > 0 ) {
711                         right = zeroPad( right, exponent, false );
712                         numberString += right.slice( 0, exponent );
713                         right = right.substr( exponent );
714                 }
715                 else if ( exponent < 0 ) {
716                         exponent = -exponent;
717                         numberString = zeroPad( numberString, exponent + 1 );
718                         right = numberString.slice( -exponent, numberString.length ) + right;
719                         numberString = numberString.slice( 0, -exponent );
720                 }
721
722                 if ( precision > 0 ) {
723                         right = formatInfo[ "." ] +
724                                 ( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) );
725                 }
726                 else {
727                         right = "";
728                 }
729
730                 var stringIndex = numberString.length - 1,
731                         sep = formatInfo[ "," ],
732                         ret = "";
733
734                 while ( stringIndex >= 0 ) {
735                         if ( curSize === 0 || curSize > stringIndex ) {
736                                 return numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right );
737                         }
738                         ret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : "" );
739
740                         stringIndex -= curSize;
741
742                         if ( curGroupIndex < groupSizes.length ) {
743                                 curSize = groupSizes[ curGroupIndex ];
744                                 curGroupIndex++;
745                         }
746                 }
747
748                 return numberString.slice( 0, stringIndex + 1 ) + sep + ret + right;
749         };
750
751         formatNumber = function( value, format, culture ) {
752                 if ( !isFinite(value) ) {
753                         if ( value === Infinity ) {
754                                 return culture.numberFormat.positiveInfinity;
755                         }
756                         if ( value === -Infinity ) {
757                                 return culture.numberFormat.negativeInfinity;
758                         }
759                         return culture.numberFormat.NaN;
760                 }
761                 if ( !format || format === "i" ) {
762                         return culture.name.length ? value.toLocaleString() : value.toString();
763                 }
764                 format = format || "D";
765
766                 var nf = culture.numberFormat,
767                         number = Math.abs( value ),
768                         precision = -1,
769                         pattern;
770                 if ( format.length > 1 ) precision = parseInt( format.slice(1), 10 );
771
772                 var current = format.charAt( 0 ).toUpperCase(),
773                         formatInfo;
774
775                 switch ( current ) {
776                         case "D":
777                                 pattern = "n";
778                                 number = truncate( number );
779                                 if ( precision !== -1 ) {
780                                         number = zeroPad( "" + number, precision, true );
781                                 }
782                                 if ( value < 0 ) number = "-" + number;
783                                 break;
784                         case "N":
785                                 formatInfo = nf;
786                                 // fall through
787                         case "C":
788                                 formatInfo = formatInfo || nf.currency;
789                                 // fall through
790                         case "P":
791                                 formatInfo = formatInfo || nf.percent;
792                                 pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" );
793                                 if ( precision === -1 ) precision = formatInfo.decimals;
794                                 number = expandNumber( number * (current === "P" ? 100 : 1), precision, formatInfo );
795                                 break;
796                         default:
797                                 throw "Bad number format specifier: " + current;
798                 }
799
800                 var patternParts = /n|\$|-|%/g,
801                         ret = "";
802                 for ( ; ; ) {
803                         var index = patternParts.lastIndex,
804                                 ar = patternParts.exec( pattern );
805
806                         ret += pattern.slice( index, ar ? ar.index : pattern.length );
807
808                         if ( !ar ) {
809                                 break;
810                         }
811
812                         switch ( ar[0] ) {
813                                 case "n":
814                                         ret += number;
815                                         break;
816                                 case "$":
817                                         ret += nf.currency.symbol;
818                                         break;
819                                 case "-":
820                                         // don't make 0 negative
821                                         if ( /[1-9]/.test(number) ) {
822                                                 ret += nf[ "-" ];
823                                         }
824                                         break;
825                                 case "%":
826                                         ret += nf.percent.symbol;
827                                         break;
828                         }
829                 }
830
831                 return ret;
832         };
833
834 }());
835
836 getTokenRegExp = function() {
837         // regular expression for matching date and time tokens in format strings.
838         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;
839 };
840
841 getEra = function( date, eras ) {
842         if ( !eras ) return 0;
843         var start, ticks = date.getTime();
844         for ( var i = 0, l = eras.length; i < l; i++ ) {
845                 start = eras[ i ].start;
846                 if ( start === null || ticks >= start ) {
847                         return i;
848                 }
849         }
850         return 0;
851 };
852
853 getEraYear = function( date, cal, era, sortable ) {
854         var year = date.getFullYear();
855         if ( !sortable && cal.eras ) {
856                 // convert normal gregorian year to era-shifted gregorian
857                 // year by subtracting the era offset
858                 year -= cal.eras[ era ].offset;
859         }
860         return year;
861 };
862
863 // parseExact
864 (function() {
865         var expandYear,
866                 getDayIndex,
867                 getMonthIndex,
868                 getParseRegExp,
869                 outOfRange,
870                 toUpper,
871                 toUpperArray;
872
873         expandYear = function( cal, year ) {
874                 // expands 2-digit year into 4 digits.
875                 var now = new Date(),
876                         era = getEra( now );
877                 if ( year < 100 ) {
878                         var twoDigitYearMax = cal.twoDigitYearMax;
879                         twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax;
880                         var curr = getEraYear( now, cal, era );
881                         year += curr - ( curr % 100 );
882                         if ( year > twoDigitYearMax ) {
883                                 year -= 100;
884                         }
885                 }
886                 return year;
887         };
888
889         getDayIndex = function  ( cal, value, abbr ) {
890                 var ret,
891                         days = cal.days,
892                         upperDays = cal._upperDays;
893                 if ( !upperDays ) {
894                         cal._upperDays = upperDays = [
895                                 toUpperArray( days.names ),
896                                 toUpperArray( days.namesAbbr ),
897                                 toUpperArray( days.namesShort )
898                         ];
899                 }
900                 value = toUpper( value );
901                 if ( abbr ) {
902                         ret = arrayIndexOf( upperDays[1], value );
903                         if ( ret === -1 ) {
904                                 ret = arrayIndexOf( upperDays[2], value );
905                         }
906                 }
907                 else {
908                         ret = arrayIndexOf( upperDays[0], value );
909                 }
910                 return ret;
911         };
912
913         getMonthIndex = function( cal, value, abbr ) {
914                 var months = cal.months,
915                         monthsGen = cal.monthsGenitive || cal.months,
916                         upperMonths = cal._upperMonths,
917                         upperMonthsGen = cal._upperMonthsGen;
918                 if ( !upperMonths ) {
919                         cal._upperMonths = upperMonths = [
920                                 toUpperArray( months.names ),
921                                 toUpperArray( months.namesAbbr )
922                         ];
923                         cal._upperMonthsGen = upperMonthsGen = [
924                                 toUpperArray( monthsGen.names ),
925                                 toUpperArray( monthsGen.namesAbbr )
926                         ];
927                 }
928                 value = toUpper( value );
929                 var i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value );
930                 if ( i < 0 ) {
931                         i = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value );
932                 }
933                 return i;
934         };
935
936         getParseRegExp = function( cal, format ) {
937                 // converts a format string into a regular expression with groups that
938                 // can be used to extract date fields from a date string.
939                 // check for a cached parse regex.
940                 var re = cal._parseRegExp;
941                 if ( !re ) {
942                         cal._parseRegExp = re = {};
943                 }
944                 else {
945                         var reFormat = re[ format ];
946                         if ( reFormat ) {
947                                 return reFormat;
948                         }
949                 }
950
951                 // expand single digit formats, then escape regular expression characters.
952                 var expFormat = expandFormat( cal, format ).replace( /([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1" ),
953                         regexp = [ "^" ],
954                         groups = [],
955                         index = 0,
956                         quoteCount = 0,
957                         tokenRegExp = getTokenRegExp(),
958                         match;
959
960                 // iterate through each date token found.
961                 while ( (match = tokenRegExp.exec(expFormat)) !== null ) {
962                         var preMatch = expFormat.slice( index, match.index );
963                         index = tokenRegExp.lastIndex;
964
965                         // don't replace any matches that occur inside a string literal.
966                         quoteCount += appendPreOrPostMatch( preMatch, regexp );
967                         if ( quoteCount % 2 ) {
968                                 regexp.push( match[0] );
969                                 continue;
970                         }
971
972                         // add a regex group for the token.
973                         var m = match[ 0 ],
974                                 len = m.length,
975                                 add;
976                         switch ( m ) {
977                                 case "dddd": case "ddd":
978                                 case "MMMM": case "MMM":
979                                 case "gg": case "g":
980                                         add = "(\\D+)";
981                                         break;
982                                 case "tt": case "t":
983                                         add = "(\\D*)";
984                                         break;
985                                 case "yyyy":
986                                 case "fff":
987                                 case "ff":
988                                 case "f":
989                                         add = "(\\d{" + len + "})";
990                                         break;
991                                 case "dd": case "d":
992                                 case "MM": case "M":
993                                 case "yy": case "y":
994                                 case "HH": case "H":
995                                 case "hh": case "h":
996                                 case "mm": case "m":
997                                 case "ss": case "s":
998                                         add = "(\\d\\d?)";
999                                         break;
1000                                 case "zzz":
1001                                         add = "([+-]?\\d\\d?:\\d{2})";
1002                                         break;
1003                                 case "zz": case "z":
1004                                         add = "([+-]?\\d\\d?)";
1005                                         break;
1006                                 case "/":
1007                                         add = "(\\" + cal[ "/" ] + ")";
1008                                         break;
1009                                 default:
1010                                         throw "Invalid date format pattern \'" + m + "\'.";
1011                                         break;
1012                         }
1013                         if ( add ) {
1014                                 regexp.push( add );
1015                         }
1016                         groups.push( match[0] );
1017                 }
1018                 appendPreOrPostMatch( expFormat.slice(index), regexp );
1019                 regexp.push( "$" );
1020
1021                 // allow whitespace to differ when matching formats.
1022                 var regexpStr = regexp.join( "" ).replace( /\s+/g, "\\s+" ),
1023                         parseRegExp = { "regExp": regexpStr, "groups": groups };
1024
1025                 // cache the regex for this format.
1026                 return re[ format ] = parseRegExp;
1027         };
1028
1029         outOfRange = function( value, low, high ) {
1030                 return value < low || value > high;
1031         };
1032
1033         toUpper = function( value ) {
1034                 // "he-IL" has non-breaking space in weekday names.
1035                 return value.split( "\u00A0" ).join( " " ).toUpperCase();
1036         };
1037
1038         toUpperArray = function( arr ) {
1039                 var results = [];
1040                 for ( var i = 0, l = arr.length; i < l; i++ ) {
1041                         results[ i ] = toUpper( arr[i] );
1042                 }
1043                 return results;
1044         };
1045
1046         parseExact = function( value, format, culture ) {
1047                 // try to parse the date string by matching against the format string
1048                 // while using the specified culture for date field names.
1049                 value = trim( value );
1050                 var cal = culture.calendar,
1051                         // convert date formats into regular expressions with groupings.
1052                         // use the regexp to determine the input format and extract the date fields.
1053                         parseInfo = getParseRegExp( cal, format ),
1054                         match = new RegExp( parseInfo.regExp ).exec( value );
1055                 if ( match === null ) {
1056                         return null;
1057                 }
1058                 // found a date format that matches the input.
1059                 var groups = parseInfo.groups,
1060                         era = null, year = null, month = null, date = null, weekDay = null,
1061                         hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null,
1062                         pmHour = false;
1063                 // iterate the format groups to extract and set the date fields.
1064                 for ( var j = 0, jl = groups.length; j < jl; j++ ) {
1065                         var matchGroup = match[ j + 1 ];
1066                         if ( matchGroup ) {
1067                                 var current = groups[ j ],
1068                                         clength = current.length,
1069                                         matchInt = parseInt( matchGroup, 10 );
1070                                 switch ( current ) {
1071                                         case "dd": case "d":
1072                                                 // Day of month.
1073                                                 date = matchInt;
1074                                                 // check that date is generally in valid range, also checking overflow below.
1075                                                 if ( outOfRange(date, 1, 31) ) return null;
1076                                                 break;
1077                                         case "MMM": case "MMMM":
1078                                                 month = getMonthIndex( cal, matchGroup, clength === 3 );
1079                                                 if ( outOfRange(month, 0, 11) ) return null;
1080                                                 break;
1081                                         case "M": case "MM":
1082                                                 // Month.
1083                                                 month = matchInt - 1;
1084                                                 if ( outOfRange(month, 0, 11) ) return null;
1085                                                 break;
1086                                         case "y": case "yy":
1087                                         case "yyyy":
1088                                                 year = clength < 4 ? expandYear( cal, matchInt ) : matchInt;
1089                                                 if ( outOfRange(year, 0, 9999) ) return null;
1090                                                 break;
1091                                         case "h": case "hh":
1092                                                 // Hours (12-hour clock).
1093                                                 hour = matchInt;
1094                                                 if ( hour === 12 ) hour = 0;
1095                                                 if ( outOfRange(hour, 0, 11) ) return null;
1096                                                 break;
1097                                         case "H": case "HH":
1098                                                 // Hours (24-hour clock).
1099                                                 hour = matchInt;
1100                                                 if ( outOfRange(hour, 0, 23) ) return null;
1101                                                 break;
1102                                         case "m": case "mm":
1103                                                 // Minutes.
1104                                                 min = matchInt;
1105                                                 if ( outOfRange(min, 0, 59) ) return null;
1106                                                 break;
1107                                         case "s": case "ss":
1108                                                 // Seconds.
1109                                                 sec = matchInt;
1110                                                 if ( outOfRange(sec, 0, 59) ) return null;
1111                                                 break;
1112                                         case "tt": case "t":
1113                                                 // AM/PM designator.
1114                                                 // see if it is standard, upper, or lower case PM. If not, ensure it is at least one of
1115                                                 // the AM tokens. If not, fail the parse for this format.
1116                                                 pmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] );
1117                                                 if (
1118                                                         !pmHour && (
1119                                                                 !cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] )
1120                                                         )
1121                                                 ) return null;
1122                                                 break;
1123                                         case "f":
1124                                                 // Deciseconds.
1125                                         case "ff":
1126                                                 // Centiseconds.
1127                                         case "fff":
1128                                                 // Milliseconds.
1129                                                 msec = matchInt * Math.pow( 10, 3 - clength );
1130                                                 if ( outOfRange(msec, 0, 999) ) return null;
1131                                                 break;
1132                                         case "ddd":
1133                                                 // Day of week.
1134                                         case "dddd":
1135                                                 // Day of week.
1136                                                 weekDay = getDayIndex( cal, matchGroup, clength === 3 );
1137                                                 if ( outOfRange(weekDay, 0, 6) ) return null;
1138                                                 break;
1139                                         case "zzz":
1140                                                 // Time zone offset in +/- hours:min.
1141                                                 var offsets = matchGroup.split( /:/ );
1142                                                 if ( offsets.length !== 2 ) return null;
1143                                                 hourOffset = parseInt( offsets[0], 10 );
1144                                                 if ( outOfRange(hourOffset, -12, 13) ) return null;
1145                                                 var minOffset = parseInt( offsets[1], 10 );
1146                                                 if ( outOfRange(minOffset, 0, 59) ) return null;
1147                                                 tzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, "-") ? -minOffset : minOffset );
1148                                                 break;
1149                                         case "z": case "zz":
1150                                                 // Time zone offset in +/- hours.
1151                                                 hourOffset = matchInt;
1152                                                 if ( outOfRange(hourOffset, -12, 13) ) return null;
1153                                                 tzMinOffset = hourOffset * 60;
1154                                                 break;
1155                                         case "g": case "gg":
1156                                                 var eraName = matchGroup;
1157                                                 if ( !eraName || !cal.eras ) return null;
1158                                                 eraName = trim( eraName.toLowerCase() );
1159                                                 for ( var i = 0, l = cal.eras.length; i < l; i++ ) {
1160                                                         if ( eraName === cal.eras[i].name.toLowerCase() ) {
1161                                                                 era = i;
1162                                                                 break;
1163                                                         }
1164                                                 }
1165                                                 // could not find an era with that name
1166                                                 if ( era === null ) return null;
1167                                                 break;
1168                                 }
1169                         }
1170                 }
1171                 var result = new Date(), defaultYear, convert = cal.convert;
1172                 defaultYear = convert ? convert.fromGregorian( result )[ 0 ] : result.getFullYear();
1173                 if ( year === null ) {
1174                         year = defaultYear;
1175                 }
1176                 else if ( cal.eras ) {
1177                         // year must be shifted to normal gregorian year
1178                         // but not if year was not specified, its already normal gregorian
1179                         // per the main if clause above.
1180                         year += cal.eras[( era || 0 )].offset;
1181                 }
1182                 // set default day and month to 1 and January, so if unspecified, these are the defaults
1183                 // instead of the current day/month.
1184                 if ( month === null ) {
1185                         month = 0;
1186                 }
1187                 if ( date === null ) {
1188                         date = 1;
1189                 }
1190                 // now have year, month, and date, but in the culture's calendar.
1191                 // convert to gregorian if necessary
1192                 if ( convert ) {
1193                         result = convert.toGregorian( year, month, date );
1194                         // conversion failed, must be an invalid match
1195                         if ( result === null ) return null;
1196                 }
1197                 else {
1198                         // have to set year, month and date together to avoid overflow based on current date.
1199                         result.setFullYear( year, month, date );
1200                         // check to see if date overflowed for specified month (only checked 1-31 above).
1201                         if ( result.getDate() !== date ) return null;
1202                         // invalid day of week.
1203                         if ( weekDay !== null && result.getDay() !== weekDay ) {
1204                                 return null;
1205                         }
1206                 }
1207                 // if pm designator token was found make sure the hours fit the 24-hour clock.
1208                 if ( pmHour && hour < 12 ) {
1209                         hour += 12;
1210                 }
1211                 result.setHours( hour, min, sec, msec );
1212                 if ( tzMinOffset !== null ) {
1213                         // adjust timezone to utc before applying local offset.
1214                         var adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() );
1215                         // Safari limits hours and minutes to the range of -127 to 127.  We need to use setHours
1216                         // to ensure both these fields will not exceed this range.      adjustedMin will range
1217                         // somewhere between -1440 and 1500, so we only need to split this into hours.
1218                         result.setHours( result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60 );
1219                 }
1220                 return result;
1221         };
1222 }());
1223
1224 parseNegativePattern = function( value, nf, negativePattern ) {
1225         var neg = nf[ "-" ],
1226                 pos = nf[ "+" ],
1227                 ret;
1228         switch ( negativePattern ) {
1229                 case "n -":
1230                         neg = " " + neg;
1231                         pos = " " + pos;
1232                         // fall through
1233                 case "n-":
1234                         if ( endsWith(value, neg) ) {
1235                                 ret = [ "-", value.substr(0, value.length - neg.length) ];
1236                         }
1237                         else if ( endsWith(value, pos) ) {
1238                                 ret = [ "+", value.substr(0, value.length - pos.length) ];
1239                         }
1240                         break;
1241                 case "- n":
1242                         neg += " ";
1243                         pos += " ";
1244                         // fall through
1245                 case "-n":
1246                         if ( startsWith(value, neg) ) {
1247                                 ret = [ "-", value.substr(neg.length) ];
1248                         }
1249                         else if ( startsWith(value, pos) ) {
1250                                 ret = [ "+", value.substr(pos.length) ];
1251                         }
1252                         break;
1253                 case "(n)":
1254                         if ( startsWith(value, "(") && endsWith(value, ")") ) {
1255                                 ret = [ "-", value.substr(1, value.length - 2) ];
1256                         }
1257                         break;
1258         }
1259         return ret || [ "", value ];
1260 };
1261
1262 //
1263 // public instance functions
1264 //
1265
1266 Globalize.prototype.findClosestCulture = function( cultureSelector ) {
1267         return Globalize.findClosestCulture.call( this, cultureSelector );
1268 };
1269
1270 Globalize.prototype.format = function( value, format, cultureSelector ) {
1271         return Globalize.format.call( this, value, format, cultureSelector );
1272 };
1273
1274 Globalize.prototype.localize = function( key, cultureSelector ) {
1275         return Globalize.localize.call( this, key, cultureSelector );
1276 };
1277
1278 Globalize.prototype.parseInt = function( value, radix, cultureSelector ) {
1279         return Globalize.parseInt.call( this, value, radix, cultureSelector );
1280 };
1281
1282 Globalize.prototype.parseFloat = function( value, radix, cultureSelector ) {
1283         return Globalize.parseFloat.call( this, value, radix, cultureSelector );
1284 };
1285
1286 Globalize.prototype.culture = function( cultureSelector ) {
1287         return Globalize.culture.call( this, cultureSelector );
1288 };
1289
1290 //
1291 // public singleton functions
1292 //
1293
1294 Globalize.addCultureInfo = function( cultureName, baseCultureName, info ) {
1295
1296         var base = {},
1297                 isNew = false;
1298
1299         if ( typeof cultureName !== "string" ) {
1300                 // cultureName argument is optional string. If not specified, assume info is first
1301                 // and only argument. Specified info deep-extends current culture.
1302                 info = cultureName;
1303                 cultureName = this.culture().name;
1304                 base = this.cultures[ cultureName ];
1305         } else if ( typeof baseCultureName !== "string" ) {
1306                 // baseCultureName argument is optional string. If not specified, assume info is second
1307                 // argument. Specified info deep-extends specified culture.
1308                 // If specified culture does not exist, create by deep-extending default
1309                 info = baseCultureName;
1310                 isNew = ( this.cultures[ cultureName ] == null );
1311                 base = this.cultures[ cultureName ] || this.cultures[ "default" ];
1312         } else {
1313                 // cultureName and baseCultureName specified. Assume a new culture is being created
1314                 // by deep-extending an specified base culture
1315                 isNew = true;
1316                 base = this.cultures[ baseCultureName ];
1317         }
1318
1319         this.cultures[ cultureName ] = extend(true, {},
1320                 base,
1321                 info
1322         );
1323         // Make the standard calendar the current culture if it's a new culture
1324         if ( isNew ) {
1325                 this.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard;
1326         }
1327 };
1328
1329 Globalize.findClosestCulture = function( name ) {
1330         var match;
1331         if ( !name ) {
1332                 return this.cultures[ this.cultureSelector ] || this.cultures[ "default" ];
1333         }
1334         if ( typeof name === "string" ) {
1335                 name = name.split( "," );
1336         }
1337         if ( isArray(name) ) {
1338                 var lang,
1339                         cultures = this.cultures,
1340                         list = name,
1341                         i, l = list.length,
1342                         prioritized = [];
1343                 for ( i = 0; i < l; i++ ) {
1344                         name = trim( list[i] );
1345                         var pri, parts = name.split( ";" );
1346                         lang = trim( parts[0] );
1347                         if ( parts.length === 1 ) {
1348                                 pri = 1;
1349                         }
1350                         else {
1351                                 name = trim( parts[1] );
1352                                 if ( name.indexOf("q=") === 0 ) {
1353                                         name = name.substr( 2 );
1354                                         pri = parseFloat( name );
1355                                         pri = isNaN( pri ) ? 0 : pri;
1356                                 }
1357                                 else {
1358                                         pri = 1;
1359                                 }
1360                         }
1361                         prioritized.push({ lang: lang, pri: pri });
1362                 }
1363                 prioritized.sort(function( a, b ) {
1364                         return a.pri < b.pri ? 1 : -1;
1365                 });
1366
1367                 // exact match
1368                 for ( i = 0; i < l; i++ ) {
1369                         lang = prioritized[ i ].lang;
1370                         match = cultures[ lang ];
1371                         if ( match ) {
1372                                 return match;
1373                         }
1374                 }
1375
1376                 // neutral language match
1377                 for ( i = 0; i < l; i++ ) {
1378                         lang = prioritized[ i ].lang;
1379                         do {
1380                                 var index = lang.lastIndexOf( "-" );
1381                                 if ( index === -1 ) {
1382                                         break;
1383                                 }
1384                                 // strip off the last part. e.g. en-US => en
1385                                 lang = lang.substr( 0, index );
1386                                 match = cultures[ lang ];
1387                                 if ( match ) {
1388                                         return match;
1389                                 }
1390                         }
1391                         while ( 1 );
1392                 }
1393
1394                 // last resort: match first culture using that language
1395                 for ( i = 0; i < l; i++ ) {
1396                         lang = prioritized[ i ].lang;
1397                         for ( var cultureKey in cultures ) {
1398                                 var culture = cultures[ cultureKey ];
1399                                 if ( culture.language == lang ) {
1400                                         return culture;
1401                                 }
1402                         }
1403                 }
1404         }
1405         else if ( typeof name === "object" ) {
1406                 return name;
1407         }
1408         return match || null;
1409 };
1410
1411 Globalize.format = function( value, format, cultureSelector ) {
1412         culture = this.findClosestCulture( cultureSelector );
1413         if ( value instanceof Date ) {
1414                 value = formatDate( value, format, culture );
1415         }
1416         else if ( typeof value === "number" ) {
1417                 value = formatNumber( value, format, culture );
1418         }
1419         return value;
1420 };
1421
1422 Globalize.localize = function( key, cultureSelector ) {
1423         return this.findClosestCulture( cultureSelector ).messages[ key ] ||
1424                 this.cultures[ "default" ].messages[ key ];
1425 };
1426
1427 Globalize.parseDate = function( value, formats, culture ) {
1428         culture = this.findClosestCulture( culture );
1429
1430         var date, prop, patterns;
1431         if ( formats ) {
1432                 if ( typeof formats === "string" ) {
1433                         formats = [ formats ];
1434                 }
1435                 if ( formats.length ) {
1436                         for ( var i = 0, l = formats.length; i < l; i++ ) {
1437                                 var format = formats[ i ];
1438                                 if ( format ) {
1439                                         date = parseExact( value, format, culture );
1440                                         if ( date ) {
1441                                                 break;
1442                                         }
1443                                 }
1444                         }
1445                 }
1446         } else {
1447                 patterns = culture.calendar.patterns;
1448                 for ( prop in patterns ) {
1449                         date = parseExact( value, patterns[prop], culture );
1450                         if ( date ) {
1451                                 break;
1452                         }
1453                 }
1454         }
1455
1456         return date || null;
1457 };
1458
1459 Globalize.parseInt = function( value, radix, cultureSelector ) {
1460         return truncate( Globalize.parseFloat(value, radix, cultureSelector) );
1461 };
1462
1463 Globalize.parseFloat = function( value, radix, cultureSelector ) {
1464         // radix argument is optional
1465         if ( typeof radix !== "number" ) {
1466                 cultureSelector = radix;
1467                 radix = 10;
1468         }
1469
1470         var culture = this.findClosestCulture( cultureSelector );
1471         var ret = NaN,
1472                 nf = culture.numberFormat;
1473
1474         if ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) {
1475                 // remove currency symbol
1476                 value = value.replace( culture.numberFormat.currency.symbol, "" );
1477                 // replace decimal seperator
1478                 value = value.replace( culture.numberFormat.currency["."], culture.numberFormat["."] );
1479         }
1480
1481         // trim leading and trailing whitespace
1482         value = trim( value );
1483
1484         // allow infinity or hexidecimal
1485         if ( regexInfinity.test(value) ) {
1486                 ret = parseFloat( value );
1487         }
1488         else if ( !radix && regexHex.test(value) ) {
1489                 ret = parseInt( value, 16 );
1490         }
1491         else {
1492
1493                 // determine sign and number
1494                 var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ),
1495                         sign = signInfo[ 0 ],
1496                         num = signInfo[ 1 ];
1497
1498                 // #44 - try parsing as "(n)"
1499                 if ( sign === "" && nf.pattern[0] !== "(n)" ) {
1500                         signInfo = parseNegativePattern( value, nf, "(n)" );
1501                         sign = signInfo[ 0 ];
1502                         num = signInfo[ 1 ];
1503                 }
1504
1505                 // try parsing as "-n"
1506                 if ( sign === "" && nf.pattern[0] !== "-n" ) {
1507                         signInfo = parseNegativePattern( value, nf, "-n" );
1508                         sign = signInfo[ 0 ];
1509                         num = signInfo[ 1 ];
1510                 }
1511
1512                 sign = sign || "+";
1513
1514                 // determine exponent and number
1515                 var exponent,
1516                         intAndFraction,
1517                         exponentPos = num.indexOf( "e" );
1518                 if ( exponentPos < 0 ) exponentPos = num.indexOf( "E" );
1519                 if ( exponentPos < 0 ) {
1520                         intAndFraction = num;
1521                         exponent = null;
1522                 }
1523                 else {
1524                         intAndFraction = num.substr( 0, exponentPos );
1525                         exponent = num.substr( exponentPos + 1 );
1526                 }
1527                 // determine decimal position
1528                 var integer,
1529                         fraction,
1530                         decSep = nf[ "." ],
1531                         decimalPos = intAndFraction.indexOf( decSep );
1532                 if ( decimalPos < 0 ) {
1533                         integer = intAndFraction;
1534                         fraction = null;
1535                 }
1536                 else {
1537                         integer = intAndFraction.substr( 0, decimalPos );
1538                         fraction = intAndFraction.substr( decimalPos + decSep.length );
1539                 }
1540                 // handle groups (e.g. 1,000,000)
1541                 var groupSep = nf[ "," ];
1542                 integer = integer.split( groupSep ).join( "" );
1543                 var altGroupSep = groupSep.replace( /\u00A0/g, " " );
1544                 if ( groupSep !== altGroupSep ) {
1545                         integer = integer.split( altGroupSep ).join( "" );
1546                 }
1547                 // build a natively parsable number string
1548                 var p = sign + integer;
1549                 if ( fraction !== null ) {
1550                         p += "." + fraction;
1551                 }
1552                 if ( exponent !== null ) {
1553                         // exponent itself may have a number patternd
1554                         var expSignInfo = parseNegativePattern( exponent, nf, "-n" );
1555                         p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ];
1556                 }
1557                 if ( regexParseFloat.test(p) ) {
1558                         ret = parseFloat( p );
1559                 }
1560         }
1561         return ret;
1562 };
1563
1564 Globalize.culture = function( cultureSelector ) {
1565         // setter
1566         if ( typeof cultureSelector !== "undefined" ) {
1567                 this.cultureSelector = cultureSelector;
1568         }
1569         // getter
1570         return this.findClosestCulture( cultureSelector ) || this.culture[ "default" ];
1571 };
1572
1573 }( this ));