PageSourceSearch

https://northernforum.org/modules/mod_gcalendarfeed/js/globalize.js

js northernforum.org collected 2026-10-02 18:28:54 UTC 47,381 bytes, 1,593 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.