Projet

Général

Profil

Paste
Télécharger (44,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / jquery_update / replace / ui / external / globalize.js @ 503b3f7b

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 ));