tizen beta release
[framework/web/web-ui-fw.git] / libs / js / globalize / lib / 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         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
452         if ( !format || !format.length || format === "i" ) {
453                 var ret;
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: return date.getFullYear();
514                         case 1: return date.getMonth();
515                         case 2: return date.getDate();
516                 }
517         }
518
519         if ( !sortable && convert ) {
520                 converted = convert.fromGregorian( value );
521         }
522
523         for ( ; ; ) {
524                 // Save the current index
525                 var index = tokenRegExp.lastIndex,
526                         // Look for the next pattern
527                         ar = tokenRegExp.exec( format );
528
529                 // Append the text before the pattern (or the end of the string if not found)
530                 var preMatch = format.slice( index, ar ? ar.index : format.length );
531                 quoteCount += appendPreOrPostMatch( preMatch, ret );
532
533                 if ( !ar ) {
534                         break;
535                 }
536
537                 // do not replace any matches that occur inside a string literal.
538                 if ( quoteCount % 2 ) {
539                         ret.push( ar[0] );
540                         continue;
541                 }
542
543                 var current = ar[ 0 ],
544                         clength = current.length;
545
546                 switch ( current ) {
547                         case "ddd":
548                                 //Day of the week, as a three-letter abbreviation
549                         case "dddd":
550                                 // Day of the week, using the full name
551                                 var names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names;
552                                 ret.push( names[value.getDay()] );
553                                 break;
554                         case "d":
555                                 // Day of month, without leading zero for single-digit days
556                         case "dd":
557                                 // Day of month, with leading zero for single-digit days
558                                 foundDay = true;
559                                 ret.push(
560                                         padZeros( getPart(value, 2), clength )
561                                 );
562                                 break;
563                         case "MMM":
564                                 // Month, as a three-letter abbreviation
565                         case "MMMM":
566                                 // Month, using the full name
567                                 var part = getPart( value, 1 );
568                                 ret.push(
569                                         ( cal.monthsGenitive && hasDay() )
570                                         ?
571                                         cal.monthsGenitive[ clength === 3 ? "namesAbbr" : "names" ][ part ]
572                                         :
573                                         cal.months[ clength === 3 ? "namesAbbr" : "names" ][ part ]
574                                 );
575                                 break;
576                         case "M":
577                                 // Month, as digits, with no leading zero for single-digit months
578                         case "MM":
579                                 // Month, as digits, with leading zero for single-digit months
580                                 ret.push(
581                                         padZeros( getPart(value, 1) + 1, clength )
582                                 );
583                                 break;
584                         case "y":
585                                 // Year, as two digits, but with no leading zero for years less than 10
586                         case "yy":
587                                 // Year, as two digits, with leading zero for years less than 10
588                         case "yyyy":
589                                 // Year represented by four full digits
590                                 part = converted ? converted[ 0 ] : getEraYear( value, cal, getEra(value, eras), sortable );
591                                 if ( clength < 4 ) {
592                                         part = part % 100;
593                                 }
594                                 ret.push(
595                                         padZeros( part, clength )
596                                 );
597                                 break;
598                         case "h":
599                                 // Hours with no leading zero for single-digit hours, using 12-hour clock
600                         case "hh":
601                                 // Hours with leading zero for single-digit hours, using 12-hour clock
602                                 hour = value.getHours() % 12;
603                                 if ( hour === 0 ) hour = 12;
604                                 ret.push(
605                                         padZeros( hour, clength )
606                                 );
607                                 break;
608                         case "H":
609                                 // Hours with no leading zero for single-digit hours, using 24-hour clock
610                         case "HH":
611                                 // Hours with leading zero for single-digit hours, using 24-hour clock
612                                 ret.push(
613                                         padZeros( value.getHours(), clength )
614                                 );
615                                 break;
616                         case "m":
617                                 // Minutes with no leading zero for single-digit minutes
618                         case "mm":
619                                 // Minutes with leading zero for single-digit minutes
620                                 ret.push(
621                                         padZeros( value.getMinutes(), clength )
622                                 );
623                                 break;
624                         case "s":
625                                 // Seconds with no leading zero for single-digit seconds
626                         case "ss":
627                                 // Seconds with leading zero for single-digit seconds
628                                 ret.push(
629                                         padZeros( value.getSeconds(), clength )
630                                 );
631                                 break;
632                         case "t":
633                                 // One character am/pm indicator ("a" or "p")
634                         case "tt":
635                                 // Multicharacter am/pm indicator
636                                 part = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : " " ) : ( cal.PM ? cal.PM[0] : " " );
637                                 ret.push( clength === 1 ? part.charAt(0) : part );
638                                 break;
639                         case "f":
640                                 // Deciseconds
641                         case "ff":
642                                 // Centiseconds
643                         case "fff":
644                                 // Milliseconds
645                                 ret.push(
646                                         padZeros( value.getMilliseconds(), 3 ).substr( 0, clength )
647                                 );
648                                 break;
649                         case "z":
650                                 // Time zone offset, no leading zero
651                         case "zz":
652                                 // Time zone offset with leading zero
653                                 hour = value.getTimezoneOffset() / 60;
654                                 ret.push(
655                                         ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), clength )
656                                 );
657                                 break;
658                         case "zzz":
659                                 // Time zone offset with leading zero
660                                 hour = value.getTimezoneOffset() / 60;
661                                 ret.push(
662                                         ( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), 2 )
663                                         // Hard coded ":" separator, rather than using cal.TimeSeparator
664                                         // Repeated here for consistency, plus ":" was already assumed in date parsing.
665                                         + ":" + padZeros( Math.abs(value.getTimezoneOffset() % 60), 2 )
666                                 );
667                                 break;
668                         case "g":
669                         case "gg":
670                                 if ( cal.eras ) {
671                                         ret.push(
672                                                 cal.eras[ getEra(value, eras) ].name
673                                         );
674                                 }
675                                 break;
676                 case "/":
677                         ret.push( cal["/"] );
678                         break;
679                 default:
680                         throw "Invalid date format pattern \'" + current + "\'.";
681                         break;
682                 }
683         }
684         return ret.join( "" );
685 };
686
687 // formatNumber
688 (function() {
689         var expandNumber;
690
691         expandNumber = function( number, precision, formatInfo ) {
692                 var groupSizes = formatInfo.groupSizes,
693                         curSize = groupSizes[ 0 ],
694                         curGroupIndex = 1,
695                         factor = Math.pow( 10, precision ),
696                         rounded = Math.round( number * factor ) / factor;
697
698                 if ( !isFinite(rounded) ) {
699                         rounded = number;
700                 }
701                 number = rounded;
702
703                 var numberString = number+"",
704                         right = "",
705                         split = numberString.split( /e/i ),
706                         exponent = split.length > 1 ? parseInt( split[1], 10 ) : 0;
707                 numberString = split[ 0 ];
708                 split = numberString.split( "." );
709                 numberString = split[ 0 ];
710                 right = split.length > 1 ? split[ 1 ] : "";
711
712                 var l;
713                 if ( exponent > 0 ) {
714                         right = zeroPad( right, exponent, false );
715                         numberString += right.slice( 0, exponent );
716                         right = right.substr( exponent );
717                 }
718                 else if ( exponent < 0 ) {
719                         exponent = -exponent;
720                         numberString = zeroPad( numberString, exponent + 1 );
721                         right = numberString.slice( -exponent, numberString.length ) + right;
722                         numberString = numberString.slice( 0, -exponent );
723                 }
724
725                 if ( precision > 0 ) {
726                         right = formatInfo[ "." ] +
727                                 ( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) );
728                 }
729                 else {
730                         right = "";
731                 }
732
733                 var stringIndex = numberString.length - 1,
734                         sep = formatInfo[ "," ],
735                         ret = "";
736
737                 while ( stringIndex >= 0 ) {
738                         if ( curSize === 0 || curSize > stringIndex ) {
739                                 return numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right );
740                         }
741                         ret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : "" );
742
743                         stringIndex -= curSize;
744
745                         if ( curGroupIndex < groupSizes.length ) {
746                                 curSize = groupSizes[ curGroupIndex ];
747                                 curGroupIndex++;
748                         }
749                 }
750
751                 return numberString.slice( 0, stringIndex + 1 ) + sep + ret + right;
752         };
753
754         formatNumber = function( value, format, culture ) {
755                 if ( !isFinite(value) ) {
756                         if ( value === Infinity ) {
757                                 return culture.numberFormat.positiveInfinity;
758                         }
759                         if ( value === -Infinity ) {
760                                 return culture.numberFormat.negativeInfinity;
761                         }
762                         return culture.numberFormat.NaN;
763                 }
764                 if ( !format || format === "i" ) {
765                         return culture.name.length ? value.toLocaleString() : value.toString();
766                 }
767                 format = format || "D";
768
769                 var nf = culture.numberFormat,
770                         number = Math.abs( value ),
771                         precision = -1,
772                         pattern;
773                 if ( format.length > 1 ) precision = parseInt( format.slice(1), 10 );
774
775                 var current = format.charAt( 0 ).toUpperCase(),
776                         formatInfo;
777
778                 switch ( current ) {
779                         case "D":
780                                 pattern = "n";
781                                 number = truncate( number );
782                                 if ( precision !== -1 ) {
783                                         number = zeroPad( "" + number, precision, true );
784                                 }
785                                 if ( value < 0 ) number = "-" + number;
786                                 break;
787                         case "N":
788                                 formatInfo = nf;
789                                 // fall through
790                         case "C":
791                                 formatInfo = formatInfo || nf.currency;
792                                 // fall through
793                         case "P":
794                                 formatInfo = formatInfo || nf.percent;
795                                 pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" );
796                                 if ( precision === -1 ) precision = formatInfo.decimals;
797                                 number = expandNumber( number * (current === "P" ? 100 : 1), precision, formatInfo );
798                                 break;
799                         default:
800                                 throw "Bad number format specifier: " + current;
801                 }
802
803                 var patternParts = /n|\$|-|%/g,
804                         ret = "";
805                 for ( ; ; ) {
806                         var index = patternParts.lastIndex,
807                                 ar = patternParts.exec( pattern );
808
809                         ret += pattern.slice( index, ar ? ar.index : pattern.length );
810
811                         if ( !ar ) {
812                                 break;
813                         }
814
815                         switch ( ar[0] ) {
816                                 case "n":
817                                         ret += number;
818                                         break;
819                                 case "$":
820                                         ret += nf.currency.symbol;
821                                         break;
822                                 case "-":
823                                         // don't make 0 negative
824                                         if ( /[1-9]/.test(number) ) {
825                                                 ret += nf[ "-" ];
826                                         }
827                                         break;
828                                 case "%":
829                                         ret += nf.percent.symbol;
830                                         break;
831                         }
832                 }
833
834                 return ret;
835         };
836
837 }());
838
839 getTokenRegExp = function() {
840         // regular expression for matching date and time tokens in format strings.
841         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;
842 };
843
844 getEra = function( date, eras ) {
845         if ( !eras ) return 0;
846         var start, ticks = date.getTime();
847         for ( var i = 0, l = eras.length; i < l; i++ ) {
848                 start = eras[ i ].start;
849                 if ( start === null || ticks >= start ) {
850                         return i;
851                 }
852         }
853         return 0;
854 };
855
856 getEraYear = function( date, cal, era, sortable ) {
857         var year = date.getFullYear();
858         if ( !sortable && cal.eras ) {
859                 // convert normal gregorian year to era-shifted gregorian
860                 // year by subtracting the era offset
861                 year -= cal.eras[ era ].offset;
862         }
863         return year;
864 };
865
866 // parseExact
867 (function() {
868         var expandYear,
869                 getDayIndex,
870                 getMonthIndex,
871                 getParseRegExp,
872                 outOfRange,
873                 toUpper,
874                 toUpperArray;
875
876         expandYear = function( cal, year ) {
877                 // expands 2-digit year into 4 digits.
878                 if ( year < 100 ) {
879                         var now = new Date(),
880                                 era = getEra( now ),
881                                 curr = getEraYear( now, cal, era ),
882                                 twoDigitYearMax = cal.twoDigitYearMax;
883                         twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax;
884                         year += curr - ( curr % 100 );
885                         if ( year > twoDigitYearMax ) {
886                                 year -= 100;
887                         }
888                 }
889                 return year;
890         };
891
892         getDayIndex = function  ( cal, value, abbr ) {
893                 var ret,
894                         days = cal.days,
895                         upperDays = cal._upperDays;
896                 if ( !upperDays ) {
897                         cal._upperDays = upperDays = [
898                                 toUpperArray( days.names ),
899                                 toUpperArray( days.namesAbbr ),
900                                 toUpperArray( days.namesShort )
901                         ];
902                 }
903                 value = toUpper( value );
904                 if ( abbr ) {
905                         ret = arrayIndexOf( upperDays[1], value );
906                         if ( ret === -1 ) {
907                                 ret = arrayIndexOf( upperDays[2], value );
908                         }
909                 }
910                 else {
911                         ret = arrayIndexOf( upperDays[0], value );
912                 }
913                 return ret;
914         };
915
916         getMonthIndex = function( cal, value, abbr ) {
917                 var months = cal.months,
918                         monthsGen = cal.monthsGenitive || cal.months,
919                         upperMonths = cal._upperMonths,
920                         upperMonthsGen = cal._upperMonthsGen;
921                 if ( !upperMonths ) {
922                         cal._upperMonths = upperMonths = [
923                                 toUpperArray( months.names ),
924                                 toUpperArray( months.namesAbbr )
925                         ];
926                         cal._upperMonthsGen = upperMonthsGen = [
927                                 toUpperArray( monthsGen.names ),
928                                 toUpperArray( monthsGen.namesAbbr )
929                         ];
930                 }
931                 value = toUpper( value );
932                 var i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value );
933                 if ( i < 0 ) {
934                         i = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value );
935                 }
936                 return i;
937         };
938
939         getParseRegExp = function( cal, format ) {
940                 // converts a format string into a regular expression with groups that
941                 // can be used to extract date fields from a date string.
942                 // check for a cached parse regex.
943                 var re = cal._parseRegExp;
944                 if ( !re ) {
945                         cal._parseRegExp = re = {};
946                 }
947                 else {
948                         var reFormat = re[ format ];
949                         if ( reFormat ) {
950                                 return reFormat;
951                         }
952                 }
953
954                 // expand single digit formats, then escape regular expression characters.
955                 var expFormat = expandFormat( cal, format ).replace( /([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1" ),
956                         regexp = [ "^" ],
957                         groups = [],
958                         index = 0,
959                         quoteCount = 0,
960                         tokenRegExp = getTokenRegExp(),
961                         match;
962
963                 // iterate through each date token found.
964                 while ( (match = tokenRegExp.exec(expFormat)) !== null ) {
965                         var preMatch = expFormat.slice( index, match.index );
966                         index = tokenRegExp.lastIndex;
967
968                         // don't replace any matches that occur inside a string literal.
969                         quoteCount += appendPreOrPostMatch( preMatch, regexp );
970                         if ( quoteCount % 2 ) {
971                                 regexp.push( match[0] );
972                                 continue;
973                         }
974
975                         // add a regex group for the token.
976                         var m = match[ 0 ],
977                                 len = m.length,
978                                 add;
979                         switch ( m ) {
980                                 case "dddd": case "ddd":
981                                 case "MMMM": case "MMM":
982                                 case "gg": case "g":
983                                         add = "(\\D+)";
984                                         break;
985                                 case "tt": case "t":
986                                         add = "(\\D*)";
987                                         break;
988                                 case "yyyy":
989                                 case "fff":
990                                 case "ff":
991                                 case "f":
992                                         add = "(\\d{" + len + "})";
993                                         break;
994                                 case "dd": case "d":
995                                 case "MM": case "M":
996                                 case "yy": case "y":
997                                 case "HH": case "H":
998                                 case "hh": case "h":
999                                 case "mm": case "m":
1000                                 case "ss": case "s":
1001                                         add = "(\\d\\d?)";
1002                                         break;
1003                                 case "zzz":
1004                                         add = "([+-]?\\d\\d?:\\d{2})";
1005                                         break;
1006                                 case "zz": case "z":
1007                                         add = "([+-]?\\d\\d?)";
1008                                         break;
1009                                 case "/":
1010                                         add = "(\\" + cal[ "/" ] + ")";
1011                                         break;
1012                                 default:
1013                                         throw "Invalid date format pattern \'" + m + "\'.";
1014                                         break;
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                         // fall 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                         // fall 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                         return a.pri < b.pri ? 1 : -1;
1368                 });
1369
1370                 // exact match
1371                 for ( i = 0; i < l; i++ ) {
1372                         lang = prioritized[ i ].lang;
1373                         match = cultures[ lang ];
1374                         if ( match ) {
1375                                 return match;
1376                         }
1377                 }
1378
1379                 // neutral language match
1380                 for ( i = 0; i < l; i++ ) {
1381                         lang = prioritized[ i ].lang;
1382                         do {
1383                                 var index = lang.lastIndexOf( "-" );
1384                                 if ( index === -1 ) {
1385                                         break;
1386                                 }
1387                                 // strip off the last part. e.g. en-US => en
1388                                 lang = lang.substr( 0, index );
1389                                 match = cultures[ lang ];
1390                                 if ( match ) {
1391                                         return match;
1392                                 }
1393                         }
1394                         while ( 1 );
1395                 }
1396
1397                 // last resort: match first culture using that language
1398                 for ( i = 0; i < l; i++ ) {
1399                         lang = prioritized[ i ].lang;
1400                         for ( var cultureKey in cultures ) {
1401                                 var culture = cultures[ cultureKey ];
1402                                 if ( culture.language == lang ) {
1403                                         return culture;
1404                                 }
1405                         }
1406                 }
1407         }
1408         else if ( typeof name === "object" ) {
1409                 return name;
1410         }
1411         return match || null;
1412 };
1413
1414 Globalize.format = function( value, format, cultureSelector ) {
1415         culture = this.findClosestCulture( cultureSelector );
1416         if ( value instanceof Date ) {
1417                 value = formatDate( value, format, culture );
1418         }
1419         else if ( typeof value === "number" ) {
1420                 value = formatNumber( value, format, culture );
1421         }
1422         return value;
1423 };
1424
1425 Globalize.localize = function( key, cultureSelector ) {
1426         return this.findClosestCulture( cultureSelector ).messages[ key ] ||
1427                 this.cultures[ "default" ].messages[ key ];
1428 };
1429
1430 Globalize.parseDate = function( value, formats, culture ) {
1431         culture = this.findClosestCulture( culture );
1432
1433         var date, prop, patterns;
1434         if ( formats ) {
1435                 if ( typeof formats === "string" ) {
1436                         formats = [ formats ];
1437                 }
1438                 if ( formats.length ) {
1439                         for ( var i = 0, l = formats.length; i < l; i++ ) {
1440                                 var format = formats[ i ];
1441                                 if ( format ) {
1442                                         date = parseExact( value, format, culture );
1443                                         if ( date ) {
1444                                                 break;
1445                                         }
1446                                 }
1447                         }
1448                 }
1449         } else {
1450                 patterns = culture.calendar.patterns;
1451                 for ( prop in patterns ) {
1452                         date = parseExact( value, patterns[prop], culture );
1453                         if ( date ) {
1454                                 break;
1455                         }
1456                 }
1457         }
1458
1459         return date || null;
1460 };
1461
1462 Globalize.parseInt = function( value, radix, cultureSelector ) {
1463         return truncate( Globalize.parseFloat(value, radix, cultureSelector) );
1464 };
1465
1466 Globalize.parseFloat = function( value, radix, cultureSelector ) {
1467         // radix argument is optional
1468         if ( typeof radix !== "number" ) {
1469                 cultureSelector = radix;
1470                 radix = 10;
1471         }
1472
1473         var culture = this.findClosestCulture( cultureSelector );
1474         var ret = NaN,
1475                 nf = culture.numberFormat;
1476
1477         if ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) {
1478                 // remove currency symbol
1479                 value = value.replace( culture.numberFormat.currency.symbol, "" );
1480                 // replace decimal seperator
1481                 value = value.replace( culture.numberFormat.currency["."], culture.numberFormat["."] );
1482         }
1483
1484         // trim leading and trailing whitespace
1485         value = trim( value );
1486
1487         // allow infinity or hexidecimal
1488         if ( regexInfinity.test(value) ) {
1489                 ret = parseFloat( value );
1490         }
1491         else if ( !radix && regexHex.test(value) ) {
1492                 ret = parseInt( value, 16 );
1493         }
1494         else {
1495
1496                 // determine sign and number
1497                 var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ),
1498                         sign = signInfo[ 0 ],
1499                         num = signInfo[ 1 ];
1500
1501                 // #44 - try parsing as "(n)"
1502                 if ( sign === "" && nf.pattern[0] !== "(n)" ) {
1503                         signInfo = parseNegativePattern( value, nf, "(n)" );
1504                         sign = signInfo[ 0 ];
1505                         num = signInfo[ 1 ];
1506                 }
1507
1508                 // try parsing as "-n"
1509                 if ( sign === "" && nf.pattern[0] !== "-n" ) {
1510                         signInfo = parseNegativePattern( value, nf, "-n" );
1511                         sign = signInfo[ 0 ];
1512                         num = signInfo[ 1 ];
1513                 }
1514
1515                 sign = sign || "+";
1516
1517                 // determine exponent and number
1518                 var exponent,
1519                         intAndFraction,
1520                         exponentPos = num.indexOf( "e" );
1521                 if ( exponentPos < 0 ) exponentPos = num.indexOf( "E" );
1522                 if ( exponentPos < 0 ) {
1523                         intAndFraction = num;
1524                         exponent = null;
1525                 }
1526                 else {
1527                         intAndFraction = num.substr( 0, exponentPos );
1528                         exponent = num.substr( exponentPos + 1 );
1529                 }
1530                 // determine decimal position
1531                 var integer,
1532                         fraction,
1533                         decSep = nf[ "." ],
1534                         decimalPos = intAndFraction.indexOf( decSep );
1535                 if ( decimalPos < 0 ) {
1536                         integer = intAndFraction;
1537                         fraction = null;
1538                 }
1539                 else {
1540                         integer = intAndFraction.substr( 0, decimalPos );
1541                         fraction = intAndFraction.substr( decimalPos + decSep.length );
1542                 }
1543                 // handle groups (e.g. 1,000,000)
1544                 var groupSep = nf[ "," ];
1545                 integer = integer.split( groupSep ).join( "" );
1546                 var altGroupSep = groupSep.replace( /\u00A0/g, " " );
1547                 if ( groupSep !== altGroupSep ) {
1548                         integer = integer.split( altGroupSep ).join( "" );
1549                 }
1550                 // build a natively parsable number string
1551                 var p = sign + integer;
1552                 if ( fraction !== null ) {
1553                         p += "." + fraction;
1554                 }
1555                 if ( exponent !== null ) {
1556                         // exponent itself may have a number patternd
1557                         var expSignInfo = parseNegativePattern( exponent, nf, "-n" );
1558                         p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ];
1559                 }
1560                 if ( regexParseFloat.test(p) ) {
1561                         ret = parseFloat( p );
1562                 }
1563         }
1564         return ret;
1565 };
1566
1567 Globalize.culture = function( cultureSelector ) {
1568         // setter
1569         if ( typeof cultureSelector !== "undefined" ) {
1570                 this.cultureSelector = cultureSelector;
1571         }
1572         // getter
1573         return this.findClosestCulture( cultureSelector ) || this.culture[ "default" ];
1574 };
1575
1576 }( this ));