You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1581 lines
44 KiB
1581 lines
44 KiB
/*! |
|
* Globalize |
|
* |
|
* http://github.com/jquery/globalize |
|
* |
|
* Copyright Software Freedom Conservancy, Inc. |
|
* Dual licensed under the MIT or GPL Version 2 licenses. |
|
* http://jquery.org/license |
|
*/ |
|
|
|
(function( window, undefined ) { |
|
|
|
var Globalize, |
|
// private variables |
|
regexHex, |
|
regexInfinity, |
|
regexParseFloat, |
|
regexTrim, |
|
// private JavaScript utility functions |
|
arrayIndexOf, |
|
endsWith, |
|
extend, |
|
isArray, |
|
isFunction, |
|
isObject, |
|
startsWith, |
|
trim, |
|
truncate, |
|
zeroPad, |
|
// private Globalization utility functions |
|
appendPreOrPostMatch, |
|
expandFormat, |
|
formatDate, |
|
formatNumber, |
|
getTokenRegExp, |
|
getEra, |
|
getEraYear, |
|
parseExact, |
|
parseNegativePattern; |
|
|
|
// Global variable (Globalize) or CommonJS module (globalize) |
|
Globalize = function( cultureSelector ) { |
|
return new Globalize.prototype.init( cultureSelector ); |
|
}; |
|
|
|
if ( typeof require !== "undefined" && |
|
typeof exports !== "undefined" && |
|
typeof module !== "undefined" ) { |
|
// Assume CommonJS |
|
module.exports = Globalize; |
|
} else { |
|
// Export as global variable |
|
window.Globalize = Globalize; |
|
} |
|
|
|
Globalize.cultures = {}; |
|
|
|
Globalize.prototype = { |
|
constructor: Globalize, |
|
init: function( cultureSelector ) { |
|
this.cultures = Globalize.cultures; |
|
this.cultureSelector = cultureSelector; |
|
|
|
return this; |
|
} |
|
}; |
|
Globalize.prototype.init.prototype = Globalize.prototype; |
|
|
|
// 1. When defining a culture, all fields are required except the ones stated as optional. |
|
// 2. Each culture should have a ".calendars" object with at least one calendar named "standard" |
|
// which serves as the default calendar in use by that culture. |
|
// 3. Each culture should have a ".calendar" object which is the current calendar being used, |
|
// it may be dynamically changed at any time to one of the calendars in ".calendars". |
|
Globalize.cultures[ "default" ] = { |
|
// A unique name for the culture in the form <language code>-<country/region code> |
|
name: "en", |
|
// the name of the culture in the english language |
|
englishName: "English", |
|
// the name of the culture in its own language |
|
nativeName: "English", |
|
// whether the culture uses right-to-left text |
|
isRTL: false, |
|
// "language" is used for so-called "specific" cultures. |
|
// For example, the culture "es-CL" means "Spanish, in Chili". |
|
// It represents the Spanish-speaking culture as it is in Chili, |
|
// which might have different formatting rules or even translations |
|
// than Spanish in Spain. A "neutral" culture is one that is not |
|
// specific to a region. For example, the culture "es" is the generic |
|
// Spanish culture, which may be a more generalized version of the language |
|
// that may or may not be what a specific culture expects. |
|
// For a specific culture like "es-CL", the "language" field refers to the |
|
// neutral, generic culture information for the language it is using. |
|
// This is not always a simple matter of the string before the dash. |
|
// For example, the "zh-Hans" culture is netural (Simplified Chinese). |
|
// And the "zh-SG" culture is Simplified Chinese in Singapore, whose lanugage |
|
// field is "zh-CHS", not "zh". |
|
// This field should be used to navigate from a specific culture to it's |
|
// more general, neutral culture. If a culture is already as general as it |
|
// can get, the language may refer to itself. |
|
language: "en", |
|
// numberFormat defines general number formatting rules, like the digits in |
|
// each grouping, the group separator, and how negative numbers are displayed. |
|
numberFormat: { |
|
// [negativePattern] |
|
// Note, numberFormat.pattern has no "positivePattern" unlike percent and currency, |
|
// but is still defined as an array for consistency with them. |
|
// negativePattern: one of "(n)|-n|- n|n-|n -" |
|
pattern: [ "-n" ], |
|
// number of decimal places normally shown |
|
decimals: 2, |
|
// string that separates number groups, as in 1,000,000 |
|
",": ",", |
|
// string that separates a number from the fractional portion, as in 1.99 |
|
".": ".", |
|
// array of numbers indicating the size of each number group. |
|
// TODO: more detailed description and example |
|
groupSizes: [ 3 ], |
|
// symbol used for positive numbers |
|
"+": "+", |
|
// symbol used for negative numbers |
|
"-": "-", |
|
// symbol used for NaN (Not-A-Number) |
|
"NaN": "NaN", |
|
// symbol used for Negative Infinity |
|
negativeInfinity: "-Infinity", |
|
// symbol used for Positive Infinity |
|
positiveInfinity: "Infinity", |
|
percent: { |
|
// [negativePattern, positivePattern] |
|
// negativePattern: one of "-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %" |
|
// positivePattern: one of "n %|n%|%n|% n" |
|
pattern: [ "-n %", "n %" ], |
|
// number of decimal places normally shown |
|
decimals: 2, |
|
// array of numbers indicating the size of each number group. |
|
// TODO: more detailed description and example |
|
groupSizes: [ 3 ], |
|
// string that separates number groups, as in 1,000,000 |
|
",": ",", |
|
// string that separates a number from the fractional portion, as in 1.99 |
|
".": ".", |
|
// symbol used to represent a percentage |
|
symbol: "%" |
|
}, |
|
currency: { |
|
// [negativePattern, positivePattern] |
|
// negativePattern: one of "($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)" |
|
// positivePattern: one of "$n|n$|$ n|n $" |
|
pattern: [ "($n)", "$n" ], |
|
// number of decimal places normally shown |
|
decimals: 2, |
|
// array of numbers indicating the size of each number group. |
|
// TODO: more detailed description and example |
|
groupSizes: [ 3 ], |
|
// string that separates number groups, as in 1,000,000 |
|
",": ",", |
|
// string that separates a number from the fractional portion, as in 1.99 |
|
".": ".", |
|
// symbol used to represent currency |
|
symbol: "$" |
|
} |
|
}, |
|
// calendars defines all the possible calendars used by this culture. |
|
// There should be at least one defined with name "standard", and is the default |
|
// calendar used by the culture. |
|
// A calendar contains information about how dates are formatted, information about |
|
// the calendar's eras, a standard set of the date formats, |
|
// translations for day and month names, and if the calendar is not based on the Gregorian |
|
// calendar, conversion functions to and from the Gregorian calendar. |
|
calendars: { |
|
standard: { |
|
// name that identifies the type of calendar this is |
|
name: "Gregorian_USEnglish", |
|
// separator of parts of a date (e.g. "/" in 11/05/1955) |
|
"/": "/", |
|
// separator of parts of a time (e.g. ":" in 05:44 PM) |
|
":": ":", |
|
// the first day of the week (0 = Sunday, 1 = Monday, etc) |
|
firstDay: 0, |
|
days: { |
|
// full day names |
|
names: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], |
|
// abbreviated day names |
|
namesAbbr: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], |
|
// shortest day names |
|
namesShort: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ] |
|
}, |
|
months: { |
|
// full month names (13 months for lunar calendards -- 13th month should be "" if not lunar) |
|
names: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" ], |
|
// abbreviated month names |
|
namesAbbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" ] |
|
}, |
|
// AM and PM designators in one of these forms: |
|
// The usual view, and the upper and lower case versions |
|
// [ standard, lowercase, uppercase ] |
|
// The culture does not use AM or PM (likely all standard date formats use 24 hour time) |
|
// null |
|
AM: [ "AM", "am", "AM" ], |
|
PM: [ "PM", "pm", "PM" ], |
|
eras: [ |
|
// eras in reverse chronological order. |
|
// name: the name of the era in this culture (e.g. A.D., C.E.) |
|
// start: when the era starts in ticks (gregorian, gmt), null if it is the earliest supported era. |
|
// offset: offset in years from gregorian calendar |
|
{ |
|
"name": "A.D.", |
|
"start": null, |
|
"offset": 0 |
|
} |
|
], |
|
// when a two digit year is given, it will never be parsed as a four digit |
|
// year greater than this year (in the appropriate era for the culture) |
|
// Set it as a full year (e.g. 2029) or use an offset format starting from |
|
// the current year: "+19" would correspond to 2029 if the current year 2010. |
|
twoDigitYearMax: 2029, |
|
// set of predefined date and time patterns used by the culture |
|
// these represent the format someone in this culture would expect |
|
// to see given the portions of the date that are shown. |
|
patterns: { |
|
// short date pattern |
|
d: "M/d/yyyy", |
|
// long date pattern |
|
D: "dddd, MMMM dd, yyyy", |
|
// short time pattern |
|
t: "h:mm tt", |
|
// long time pattern |
|
T: "h:mm:ss tt", |
|
// long date, short time pattern |
|
f: "dddd, MMMM dd, yyyy h:mm tt", |
|
// long date, long time pattern |
|
F: "dddd, MMMM dd, yyyy h:mm:ss tt", |
|
// month/day pattern |
|
M: "MMMM dd", |
|
// month/year pattern |
|
Y: "yyyy MMMM", |
|
// S is a sortable format that does not vary by culture |
|
S: "yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss" |
|
} |
|
// optional fields for each calendar: |
|
/* |
|
monthsGenitive: |
|
Same as months but used when the day preceeds the month. |
|
Omit if the culture has no genitive distinction in month names. |
|
For an explaination of genitive months, see http://blogs.msdn.com/michkap/archive/2004/12/25/332259.aspx |
|
convert: |
|
Allows for the support of non-gregorian based calendars. This convert object is used to |
|
to convert a date to and from a gregorian calendar date to handle parsing and formatting. |
|
The two functions: |
|
fromGregorian( date ) |
|
Given the date as a parameter, return an array with parts [ year, month, day ] |
|
corresponding to the non-gregorian based year, month, and day for the calendar. |
|
toGregorian( year, month, day ) |
|
Given the non-gregorian year, month, and day, return a new Date() object |
|
set to the corresponding date in the gregorian calendar. |
|
*/ |
|
} |
|
}, |
|
// For localized strings |
|
messages: {} |
|
}; |
|
|
|
Globalize.cultures[ "default" ].calendar = Globalize.cultures[ "default" ].calendars.standard; |
|
|
|
Globalize.cultures.en = Globalize.cultures[ "default" ]; |
|
|
|
Globalize.cultureSelector = "en"; |
|
|
|
// |
|
// private variables |
|
// |
|
|
|
regexHex = /^0x[a-f0-9]+$/i; |
|
regexInfinity = /^[+\-]?infinity$/i; |
|
regexParseFloat = /^[+\-]?\d*\.?\d*(e[+\-]?\d+)?$/; |
|
regexTrim = /^\s+|\s+$/g; |
|
|
|
// |
|
// private JavaScript utility functions |
|
// |
|
|
|
arrayIndexOf = function( array, item ) { |
|
if ( array.indexOf ) { |
|
return array.indexOf( item ); |
|
} |
|
for ( var i = 0, length = array.length; i < length; i++ ) { |
|
if ( array[i] === item ) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
}; |
|
|
|
endsWith = function( value, pattern ) { |
|
return value.substr( value.length - pattern.length ) === pattern; |
|
}; |
|
|
|
extend = function() { |
|
var options, name, src, copy, copyIsArray, clone, |
|
target = arguments[0] || {}, |
|
i = 1, |
|
length = arguments.length, |
|
deep = false; |
|
|
|
// Handle a deep copy situation |
|
if ( typeof target === "boolean" ) { |
|
deep = target; |
|
target = arguments[1] || {}; |
|
// skip the boolean and the target |
|
i = 2; |
|
} |
|
|
|
// Handle case when target is a string or something (possible in deep copy) |
|
if ( typeof target !== "object" && !isFunction(target) ) { |
|
target = {}; |
|
} |
|
|
|
for ( ; i < length; i++ ) { |
|
// Only deal with non-null/undefined values |
|
if ( (options = arguments[ i ]) != null ) { |
|
// Extend the base object |
|
for ( name in options ) { |
|
src = target[ name ]; |
|
copy = options[ name ]; |
|
|
|
// Prevent never-ending loop |
|
if ( target === copy ) { |
|
continue; |
|
} |
|
|
|
// Recurse if we're merging plain objects or arrays |
|
if ( deep && copy && ( isObject(copy) || (copyIsArray = isArray(copy)) ) ) { |
|
if ( copyIsArray ) { |
|
copyIsArray = false; |
|
clone = src && isArray(src) ? src : []; |
|
|
|
} else { |
|
clone = src && isObject(src) ? src : {}; |
|
} |
|
|
|
// Never move original objects, clone them |
|
target[ name ] = extend( deep, clone, copy ); |
|
|
|
// Don't bring in undefined values |
|
} else if ( copy !== undefined ) { |
|
target[ name ] = copy; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Return the modified object |
|
return target; |
|
}; |
|
|
|
isArray = Array.isArray || function( obj ) { |
|
return Object.prototype.toString.call( obj ) === "[object Array]"; |
|
}; |
|
|
|
isFunction = function( obj ) { |
|
return Object.prototype.toString.call( obj ) === "[object Function]"; |
|
}; |
|
|
|
isObject = function( obj ) { |
|
return Object.prototype.toString.call( obj ) === "[object Object]"; |
|
}; |
|
|
|
startsWith = function( value, pattern ) { |
|
return value.indexOf( pattern ) === 0; |
|
}; |
|
|
|
trim = function( value ) { |
|
return ( value + "" ).replace( regexTrim, "" ); |
|
}; |
|
|
|
truncate = function( value ) { |
|
if ( isNaN( value ) ) { |
|
return NaN; |
|
} |
|
return Math[ value < 0 ? "ceil" : "floor" ]( value ); |
|
}; |
|
|
|
zeroPad = function( str, count, left ) { |
|
var l; |
|
for ( l = str.length; l < count; l += 1 ) { |
|
str = ( left ? ("0" + str) : (str + "0") ); |
|
} |
|
return str; |
|
}; |
|
|
|
// |
|
// private Globalization utility functions |
|
// |
|
|
|
appendPreOrPostMatch = function( preMatch, strings ) { |
|
// appends pre- and post- token match strings while removing escaped characters. |
|
// Returns a single quote count which is used to determine if the token occurs |
|
// in a string literal. |
|
var quoteCount = 0, |
|
escaped = false; |
|
for ( var i = 0, il = preMatch.length; i < il; i++ ) { |
|
var c = preMatch.charAt( i ); |
|
switch ( c ) { |
|
case "\'": |
|
if ( escaped ) { |
|
strings.push( "\'" ); |
|
} |
|
else { |
|
quoteCount++; |
|
} |
|
escaped = false; |
|
break; |
|
case "\\": |
|
if ( escaped ) { |
|
strings.push( "\\" ); |
|
} |
|
escaped = !escaped; |
|
break; |
|
default: |
|
strings.push( c ); |
|
escaped = false; |
|
break; |
|
} |
|
} |
|
return quoteCount; |
|
}; |
|
|
|
expandFormat = function( cal, format ) { |
|
// expands unspecified or single character date formats into the full pattern. |
|
format = format || "F"; |
|
var pattern, |
|
patterns = cal.patterns, |
|
len = format.length; |
|
if ( len === 1 ) { |
|
pattern = patterns[ format ]; |
|
if ( !pattern ) { |
|
throw "Invalid date format string \'" + format + "\'."; |
|
} |
|
format = pattern; |
|
} |
|
else if ( len === 2 && format.charAt(0) === "%" ) { |
|
// %X escape format -- intended as a custom format string that is only one character, not a built-in format. |
|
format = format.charAt( 1 ); |
|
} |
|
return format; |
|
}; |
|
|
|
formatDate = function( value, format, culture ) { |
|
var cal = culture.calendar, |
|
convert = cal.convert, |
|
ret; |
|
|
|
if ( !format || !format.length || format === "i" ) { |
|
if ( culture && culture.name.length ) { |
|
if ( convert ) { |
|
// non-gregorian calendar, so we cannot use built-in toLocaleString() |
|
ret = formatDate( value, cal.patterns.F, culture ); |
|
} |
|
else { |
|
var eraDate = new Date( value.getTime() ), |
|
era = getEra( value, cal.eras ); |
|
eraDate.setFullYear( getEraYear(value, cal, era) ); |
|
ret = eraDate.toLocaleString(); |
|
} |
|
} |
|
else { |
|
ret = value.toString(); |
|
} |
|
return ret; |
|
} |
|
|
|
var eras = cal.eras, |
|
sortable = format === "s"; |
|
format = expandFormat( cal, format ); |
|
|
|
// Start with an empty string |
|
ret = []; |
|
var hour, |
|
zeros = [ "0", "00", "000" ], |
|
foundDay, |
|
checkedDay, |
|
dayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g, |
|
quoteCount = 0, |
|
tokenRegExp = getTokenRegExp(), |
|
converted; |
|
|
|
function padZeros( num, c ) { |
|
var r, s = num + ""; |
|
if ( c > 1 && s.length < c ) { |
|
r = ( zeros[c - 2] + s); |
|
return r.substr( r.length - c, c ); |
|
} |
|
else { |
|
r = s; |
|
} |
|
return r; |
|
} |
|
|
|
function hasDay() { |
|
if ( foundDay || checkedDay ) { |
|
return foundDay; |
|
} |
|
foundDay = dayPartRegExp.test( format ); |
|
checkedDay = true; |
|
return foundDay; |
|
} |
|
|
|
function getPart( date, part ) { |
|
if ( converted ) { |
|
return converted[ part ]; |
|
} |
|
switch ( part ) { |
|
case 0: |
|
return date.getFullYear(); |
|
case 1: |
|
return date.getMonth(); |
|
case 2: |
|
return date.getDate(); |
|
default: |
|
throw "Invalid part value " + part; |
|
} |
|
} |
|
|
|
if ( !sortable && convert ) { |
|
converted = convert.fromGregorian( value ); |
|
} |
|
|
|
for ( ; ; ) { |
|
// Save the current index |
|
var index = tokenRegExp.lastIndex, |
|
// Look for the next pattern |
|
ar = tokenRegExp.exec( format ); |
|
|
|
// Append the text before the pattern (or the end of the string if not found) |
|
var preMatch = format.slice( index, ar ? ar.index : format.length ); |
|
quoteCount += appendPreOrPostMatch( preMatch, ret ); |
|
|
|
if ( !ar ) { |
|
break; |
|
} |
|
|
|
// do not replace any matches that occur inside a string literal. |
|
if ( quoteCount % 2 ) { |
|
ret.push( ar[0] ); |
|
continue; |
|
} |
|
|
|
var current = ar[ 0 ], |
|
clength = current.length; |
|
|
|
switch ( current ) { |
|
case "ddd": |
|
//Day of the week, as a three-letter abbreviation |
|
case "dddd": |
|
// Day of the week, using the full name |
|
var names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names; |
|
ret.push( names[value.getDay()] ); |
|
break; |
|
case "d": |
|
// Day of month, without leading zero for single-digit days |
|
case "dd": |
|
// Day of month, with leading zero for single-digit days |
|
foundDay = true; |
|
ret.push( |
|
padZeros( getPart(value, 2), clength ) |
|
); |
|
break; |
|
case "MMM": |
|
// Month, as a three-letter abbreviation |
|
case "MMMM": |
|
// Month, using the full name |
|
var part = getPart( value, 1 ); |
|
ret.push( |
|
( cal.monthsGenitive && hasDay() ) ? |
|
( cal.monthsGenitive[ clength === 3 ? "namesAbbr" : "names" ][ part ] ) : |
|
( cal.months[ clength === 3 ? "namesAbbr" : "names" ][ part ] ) |
|
); |
|
break; |
|
case "M": |
|
// Month, as digits, with no leading zero for single-digit months |
|
case "MM": |
|
// Month, as digits, with leading zero for single-digit months |
|
ret.push( |
|
padZeros( getPart(value, 1) + 1, clength ) |
|
); |
|
break; |
|
case "y": |
|
// Year, as two digits, but with no leading zero for years less than 10 |
|
case "yy": |
|
// Year, as two digits, with leading zero for years less than 10 |
|
case "yyyy": |
|
// Year represented by four full digits |
|
part = converted ? converted[ 0 ] : getEraYear( value, cal, getEra(value, eras), sortable ); |
|
if ( clength < 4 ) { |
|
part = part % 100; |
|
} |
|
ret.push( |
|
padZeros( part, clength ) |
|
); |
|
break; |
|
case "h": |
|
// Hours with no leading zero for single-digit hours, using 12-hour clock |
|
case "hh": |
|
// Hours with leading zero for single-digit hours, using 12-hour clock |
|
hour = value.getHours() % 12; |
|
if ( hour === 0 ) hour = 12; |
|
ret.push( |
|
padZeros( hour, clength ) |
|
); |
|
break; |
|
case "H": |
|
// Hours with no leading zero for single-digit hours, using 24-hour clock |
|
case "HH": |
|
// Hours with leading zero for single-digit hours, using 24-hour clock |
|
ret.push( |
|
padZeros( value.getHours(), clength ) |
|
); |
|
break; |
|
case "m": |
|
// Minutes with no leading zero for single-digit minutes |
|
case "mm": |
|
// Minutes with leading zero for single-digit minutes |
|
ret.push( |
|
padZeros( value.getMinutes(), clength ) |
|
); |
|
break; |
|
case "s": |
|
// Seconds with no leading zero for single-digit seconds |
|
case "ss": |
|
// Seconds with leading zero for single-digit seconds |
|
ret.push( |
|
padZeros( value.getSeconds(), clength ) |
|
); |
|
break; |
|
case "t": |
|
// One character am/pm indicator ("a" or "p") |
|
case "tt": |
|
// Multicharacter am/pm indicator |
|
part = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : " " ) : ( cal.PM ? cal.PM[0] : " " ); |
|
ret.push( clength === 1 ? part.charAt(0) : part ); |
|
break; |
|
case "f": |
|
// Deciseconds |
|
case "ff": |
|
// Centiseconds |
|
case "fff": |
|
// Milliseconds |
|
ret.push( |
|
padZeros( value.getMilliseconds(), 3 ).substr( 0, clength ) |
|
); |
|
break; |
|
case "z": |
|
// Time zone offset, no leading zero |
|
case "zz": |
|
// Time zone offset with leading zero |
|
hour = value.getTimezoneOffset() / 60; |
|
ret.push( |
|
( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), clength ) |
|
); |
|
break; |
|
case "zzz": |
|
// Time zone offset with leading zero |
|
hour = value.getTimezoneOffset() / 60; |
|
ret.push( |
|
( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), 2 ) + |
|
// Hard coded ":" separator, rather than using cal.TimeSeparator |
|
// Repeated here for consistency, plus ":" was already assumed in date parsing. |
|
":" + padZeros( Math.abs(value.getTimezoneOffset() % 60), 2 ) |
|
); |
|
break; |
|
case "g": |
|
case "gg": |
|
if ( cal.eras ) { |
|
ret.push( |
|
cal.eras[ getEra(value, eras) ].name |
|
); |
|
} |
|
break; |
|
case "/": |
|
ret.push( cal["/"] ); |
|
break; |
|
default: |
|
throw "Invalid date format pattern \'" + current + "\'."; |
|
} |
|
} |
|
return ret.join( "" ); |
|
}; |
|
|
|
// formatNumber |
|
(function() { |
|
var expandNumber; |
|
|
|
expandNumber = function( number, precision, formatInfo ) { |
|
var groupSizes = formatInfo.groupSizes, |
|
curSize = groupSizes[ 0 ], |
|
curGroupIndex = 1, |
|
factor = Math.pow( 10, precision ), |
|
rounded = Math.round( number * factor ) / factor; |
|
|
|
if ( !isFinite(rounded) ) { |
|
rounded = number; |
|
} |
|
number = rounded; |
|
|
|
var numberString = number+"", |
|
right = "", |
|
split = numberString.split( /e/i ), |
|
exponent = split.length > 1 ? parseInt( split[1], 10 ) : 0; |
|
numberString = split[ 0 ]; |
|
split = numberString.split( "." ); |
|
numberString = split[ 0 ]; |
|
right = split.length > 1 ? split[ 1 ] : ""; |
|
|
|
var l; |
|
if ( exponent > 0 ) { |
|
right = zeroPad( right, exponent, false ); |
|
numberString += right.slice( 0, exponent ); |
|
right = right.substr( exponent ); |
|
} |
|
else if ( exponent < 0 ) { |
|
exponent = -exponent; |
|
numberString = zeroPad( numberString, exponent + 1, true ); |
|
right = numberString.slice( -exponent, numberString.length ) + right; |
|
numberString = numberString.slice( 0, -exponent ); |
|
} |
|
|
|
if ( precision > 0 ) { |
|
right = formatInfo[ "." ] + |
|
( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) ); |
|
} |
|
else { |
|
right = ""; |
|
} |
|
|
|
var stringIndex = numberString.length - 1, |
|
sep = formatInfo[ "," ], |
|
ret = ""; |
|
|
|
while ( stringIndex >= 0 ) { |
|
if ( curSize === 0 || curSize > stringIndex ) { |
|
return numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right ); |
|
} |
|
ret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : "" ); |
|
|
|
stringIndex -= curSize; |
|
|
|
if ( curGroupIndex < groupSizes.length ) { |
|
curSize = groupSizes[ curGroupIndex ]; |
|
curGroupIndex++; |
|
} |
|
} |
|
|
|
return numberString.slice( 0, stringIndex + 1 ) + sep + ret + right; |
|
}; |
|
|
|
formatNumber = function( value, format, culture ) { |
|
if ( !isFinite(value) ) { |
|
if ( value === Infinity ) { |
|
return culture.numberFormat.positiveInfinity; |
|
} |
|
if ( value === -Infinity ) { |
|
return culture.numberFormat.negativeInfinity; |
|
} |
|
return culture.numberFormat[ "NaN" ]; |
|
} |
|
if ( !format || format === "i" ) { |
|
return culture.name.length ? value.toLocaleString() : value.toString(); |
|
} |
|
format = format || "D"; |
|
|
|
var nf = culture.numberFormat, |
|
number = Math.abs( value ), |
|
precision = -1, |
|
pattern; |
|
if ( format.length > 1 ) precision = parseInt( format.slice(1), 10 ); |
|
|
|
var current = format.charAt( 0 ).toUpperCase(), |
|
formatInfo; |
|
|
|
switch ( current ) { |
|
case "D": |
|
pattern = "n"; |
|
number = truncate( number ); |
|
if ( precision !== -1 ) { |
|
number = zeroPad( "" + number, precision, true ); |
|
} |
|
if ( value < 0 ) number = "-" + number; |
|
break; |
|
case "N": |
|
formatInfo = nf; |
|
/* falls through */ |
|
case "C": |
|
formatInfo = formatInfo || nf.currency; |
|
/* falls through */ |
|
case "P": |
|
formatInfo = formatInfo || nf.percent; |
|
pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" ); |
|
if ( precision === -1 ) precision = formatInfo.decimals; |
|
number = expandNumber( number * (current === "P" ? 100 : 1), precision, formatInfo ); |
|
break; |
|
default: |
|
throw "Bad number format specifier: " + current; |
|
} |
|
|
|
var patternParts = /n|\$|-|%/g, |
|
ret = ""; |
|
for ( ; ; ) { |
|
var index = patternParts.lastIndex, |
|
ar = patternParts.exec( pattern ); |
|
|
|
ret += pattern.slice( index, ar ? ar.index : pattern.length ); |
|
|
|
if ( !ar ) { |
|
break; |
|
} |
|
|
|
switch ( ar[0] ) { |
|
case "n": |
|
ret += number; |
|
break; |
|
case "$": |
|
ret += nf.currency.symbol; |
|
break; |
|
case "-": |
|
// don't make 0 negative |
|
if ( /[1-9]/.test(number) ) { |
|
ret += nf[ "-" ]; |
|
} |
|
break; |
|
case "%": |
|
ret += nf.percent.symbol; |
|
break; |
|
} |
|
} |
|
|
|
return ret; |
|
}; |
|
|
|
}()); |
|
|
|
getTokenRegExp = function() { |
|
// regular expression for matching date and time tokens in format strings. |
|
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); |
|
}; |
|
|
|
getEra = function( date, eras ) { |
|
if ( !eras ) return 0; |
|
var start, ticks = date.getTime(); |
|
for ( var i = 0, l = eras.length; i < l; i++ ) { |
|
start = eras[ i ].start; |
|
if ( start === null || ticks >= start ) { |
|
return i; |
|
} |
|
} |
|
return 0; |
|
}; |
|
|
|
getEraYear = function( date, cal, era, sortable ) { |
|
var year = date.getFullYear(); |
|
if ( !sortable && cal.eras ) { |
|
// convert normal gregorian year to era-shifted gregorian |
|
// year by subtracting the era offset |
|
year -= cal.eras[ era ].offset; |
|
} |
|
return year; |
|
}; |
|
|
|
// parseExact |
|
(function() { |
|
var expandYear, |
|
getDayIndex, |
|
getMonthIndex, |
|
getParseRegExp, |
|
outOfRange, |
|
toUpper, |
|
toUpperArray; |
|
|
|
expandYear = function( cal, year ) { |
|
// expands 2-digit year into 4 digits. |
|
if ( year < 100 ) { |
|
var now = new Date(), |
|
era = getEra( now ), |
|
curr = getEraYear( now, cal, era ), |
|
twoDigitYearMax = cal.twoDigitYearMax; |
|
twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax; |
|
year += curr - ( curr % 100 ); |
|
if ( year > twoDigitYearMax ) { |
|
year -= 100; |
|
} |
|
} |
|
return year; |
|
}; |
|
|
|
getDayIndex = function ( cal, value, abbr ) { |
|
var ret, |
|
days = cal.days, |
|
upperDays = cal._upperDays; |
|
if ( !upperDays ) { |
|
cal._upperDays = upperDays = [ |
|
toUpperArray( days.names ), |
|
toUpperArray( days.namesAbbr ), |
|
toUpperArray( days.namesShort ) |
|
]; |
|
} |
|
value = toUpper( value ); |
|
if ( abbr ) { |
|
ret = arrayIndexOf( upperDays[1], value ); |
|
if ( ret === -1 ) { |
|
ret = arrayIndexOf( upperDays[2], value ); |
|
} |
|
} |
|
else { |
|
ret = arrayIndexOf( upperDays[0], value ); |
|
} |
|
return ret; |
|
}; |
|
|
|
getMonthIndex = function( cal, value, abbr ) { |
|
var months = cal.months, |
|
monthsGen = cal.monthsGenitive || cal.months, |
|
upperMonths = cal._upperMonths, |
|
upperMonthsGen = cal._upperMonthsGen; |
|
if ( !upperMonths ) { |
|
cal._upperMonths = upperMonths = [ |
|
toUpperArray( months.names ), |
|
toUpperArray( months.namesAbbr ) |
|
]; |
|
cal._upperMonthsGen = upperMonthsGen = [ |
|
toUpperArray( monthsGen.names ), |
|
toUpperArray( monthsGen.namesAbbr ) |
|
]; |
|
} |
|
value = toUpper( value ); |
|
var i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value ); |
|
if ( i < 0 ) { |
|
i = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value ); |
|
} |
|
return i; |
|
}; |
|
|
|
getParseRegExp = function( cal, format ) { |
|
// converts a format string into a regular expression with groups that |
|
// can be used to extract date fields from a date string. |
|
// check for a cached parse regex. |
|
var re = cal._parseRegExp; |
|
if ( !re ) { |
|
cal._parseRegExp = re = {}; |
|
} |
|
else { |
|
var reFormat = re[ format ]; |
|
if ( reFormat ) { |
|
return reFormat; |
|
} |
|
} |
|
|
|
// expand single digit formats, then escape regular expression characters. |
|
var expFormat = expandFormat( cal, format ).replace( /([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1" ), |
|
regexp = [ "^" ], |
|
groups = [], |
|
index = 0, |
|
quoteCount = 0, |
|
tokenRegExp = getTokenRegExp(), |
|
match; |
|
|
|
// iterate through each date token found. |
|
while ( (match = tokenRegExp.exec(expFormat)) !== null ) { |
|
var preMatch = expFormat.slice( index, match.index ); |
|
index = tokenRegExp.lastIndex; |
|
|
|
// don't replace any matches that occur inside a string literal. |
|
quoteCount += appendPreOrPostMatch( preMatch, regexp ); |
|
if ( quoteCount % 2 ) { |
|
regexp.push( match[0] ); |
|
continue; |
|
} |
|
|
|
// add a regex group for the token. |
|
var m = match[ 0 ], |
|
len = m.length, |
|
add; |
|
switch ( m ) { |
|
case "dddd": case "ddd": |
|
case "MMMM": case "MMM": |
|
case "gg": case "g": |
|
add = "(\\D+)"; |
|
break; |
|
case "tt": case "t": |
|
add = "(\\D*)"; |
|
break; |
|
case "yyyy": |
|
case "fff": |
|
case "ff": |
|
case "f": |
|
add = "(\\d{" + len + "})"; |
|
break; |
|
case "dd": case "d": |
|
case "MM": case "M": |
|
case "yy": case "y": |
|
case "HH": case "H": |
|
case "hh": case "h": |
|
case "mm": case "m": |
|
case "ss": case "s": |
|
add = "(\\d\\d?)"; |
|
break; |
|
case "zzz": |
|
add = "([+-]?\\d\\d?:\\d{2})"; |
|
break; |
|
case "zz": case "z": |
|
add = "([+-]?\\d\\d?)"; |
|
break; |
|
case "/": |
|
add = "(\\/)"; |
|
break; |
|
default: |
|
throw "Invalid date format pattern \'" + m + "\'."; |
|
} |
|
if ( add ) { |
|
regexp.push( add ); |
|
} |
|
groups.push( match[0] ); |
|
} |
|
appendPreOrPostMatch( expFormat.slice(index), regexp ); |
|
regexp.push( "$" ); |
|
|
|
// allow whitespace to differ when matching formats. |
|
var regexpStr = regexp.join( "" ).replace( /\s+/g, "\\s+" ), |
|
parseRegExp = { "regExp": regexpStr, "groups": groups }; |
|
|
|
// cache the regex for this format. |
|
return re[ format ] = parseRegExp; |
|
}; |
|
|
|
outOfRange = function( value, low, high ) { |
|
return value < low || value > high; |
|
}; |
|
|
|
toUpper = function( value ) { |
|
// "he-IL" has non-breaking space in weekday names. |
|
return value.split( "\u00A0" ).join( " " ).toUpperCase(); |
|
}; |
|
|
|
toUpperArray = function( arr ) { |
|
var results = []; |
|
for ( var i = 0, l = arr.length; i < l; i++ ) { |
|
results[ i ] = toUpper( arr[i] ); |
|
} |
|
return results; |
|
}; |
|
|
|
parseExact = function( value, format, culture ) { |
|
// try to parse the date string by matching against the format string |
|
// while using the specified culture for date field names. |
|
value = trim( value ); |
|
var cal = culture.calendar, |
|
// convert date formats into regular expressions with groupings. |
|
// use the regexp to determine the input format and extract the date fields. |
|
parseInfo = getParseRegExp( cal, format ), |
|
match = new RegExp( parseInfo.regExp ).exec( value ); |
|
if ( match === null ) { |
|
return null; |
|
} |
|
// found a date format that matches the input. |
|
var groups = parseInfo.groups, |
|
era = null, year = null, month = null, date = null, weekDay = null, |
|
hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null, |
|
pmHour = false; |
|
// iterate the format groups to extract and set the date fields. |
|
for ( var j = 0, jl = groups.length; j < jl; j++ ) { |
|
var matchGroup = match[ j + 1 ]; |
|
if ( matchGroup ) { |
|
var current = groups[ j ], |
|
clength = current.length, |
|
matchInt = parseInt( matchGroup, 10 ); |
|
switch ( current ) { |
|
case "dd": case "d": |
|
// Day of month. |
|
date = matchInt; |
|
// check that date is generally in valid range, also checking overflow below. |
|
if ( outOfRange(date, 1, 31) ) return null; |
|
break; |
|
case "MMM": case "MMMM": |
|
month = getMonthIndex( cal, matchGroup, clength === 3 ); |
|
if ( outOfRange(month, 0, 11) ) return null; |
|
break; |
|
case "M": case "MM": |
|
// Month. |
|
month = matchInt - 1; |
|
if ( outOfRange(month, 0, 11) ) return null; |
|
break; |
|
case "y": case "yy": |
|
case "yyyy": |
|
year = clength < 4 ? expandYear( cal, matchInt ) : matchInt; |
|
if ( outOfRange(year, 0, 9999) ) return null; |
|
break; |
|
case "h": case "hh": |
|
// Hours (12-hour clock). |
|
hour = matchInt; |
|
if ( hour === 12 ) hour = 0; |
|
if ( outOfRange(hour, 0, 11) ) return null; |
|
break; |
|
case "H": case "HH": |
|
// Hours (24-hour clock). |
|
hour = matchInt; |
|
if ( outOfRange(hour, 0, 23) ) return null; |
|
break; |
|
case "m": case "mm": |
|
// Minutes. |
|
min = matchInt; |
|
if ( outOfRange(min, 0, 59) ) return null; |
|
break; |
|
case "s": case "ss": |
|
// Seconds. |
|
sec = matchInt; |
|
if ( outOfRange(sec, 0, 59) ) return null; |
|
break; |
|
case "tt": case "t": |
|
// AM/PM designator. |
|
// see if it is standard, upper, or lower case PM. If not, ensure it is at least one of |
|
// the AM tokens. If not, fail the parse for this format. |
|
pmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] ); |
|
if ( |
|
!pmHour && ( |
|
!cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] ) |
|
) |
|
) return null; |
|
break; |
|
case "f": |
|
// Deciseconds. |
|
case "ff": |
|
// Centiseconds. |
|
case "fff": |
|
// Milliseconds. |
|
msec = matchInt * Math.pow( 10, 3 - clength ); |
|
if ( outOfRange(msec, 0, 999) ) return null; |
|
break; |
|
case "ddd": |
|
// Day of week. |
|
case "dddd": |
|
// Day of week. |
|
weekDay = getDayIndex( cal, matchGroup, clength === 3 ); |
|
if ( outOfRange(weekDay, 0, 6) ) return null; |
|
break; |
|
case "zzz": |
|
// Time zone offset in +/- hours:min. |
|
var offsets = matchGroup.split( /:/ ); |
|
if ( offsets.length !== 2 ) return null; |
|
hourOffset = parseInt( offsets[0], 10 ); |
|
if ( outOfRange(hourOffset, -12, 13) ) return null; |
|
var minOffset = parseInt( offsets[1], 10 ); |
|
if ( outOfRange(minOffset, 0, 59) ) return null; |
|
tzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, "-") ? -minOffset : minOffset ); |
|
break; |
|
case "z": case "zz": |
|
// Time zone offset in +/- hours. |
|
hourOffset = matchInt; |
|
if ( outOfRange(hourOffset, -12, 13) ) return null; |
|
tzMinOffset = hourOffset * 60; |
|
break; |
|
case "g": case "gg": |
|
var eraName = matchGroup; |
|
if ( !eraName || !cal.eras ) return null; |
|
eraName = trim( eraName.toLowerCase() ); |
|
for ( var i = 0, l = cal.eras.length; i < l; i++ ) { |
|
if ( eraName === cal.eras[i].name.toLowerCase() ) { |
|
era = i; |
|
break; |
|
} |
|
} |
|
// could not find an era with that name |
|
if ( era === null ) return null; |
|
break; |
|
} |
|
} |
|
} |
|
var result = new Date(), defaultYear, convert = cal.convert; |
|
defaultYear = convert ? convert.fromGregorian( result )[ 0 ] : result.getFullYear(); |
|
if ( year === null ) { |
|
year = defaultYear; |
|
} |
|
else if ( cal.eras ) { |
|
// year must be shifted to normal gregorian year |
|
// but not if year was not specified, its already normal gregorian |
|
// per the main if clause above. |
|
year += cal.eras[( era || 0 )].offset; |
|
} |
|
// set default day and month to 1 and January, so if unspecified, these are the defaults |
|
// instead of the current day/month. |
|
if ( month === null ) { |
|
month = 0; |
|
} |
|
if ( date === null ) { |
|
date = 1; |
|
} |
|
// now have year, month, and date, but in the culture's calendar. |
|
// convert to gregorian if necessary |
|
if ( convert ) { |
|
result = convert.toGregorian( year, month, date ); |
|
// conversion failed, must be an invalid match |
|
if ( result === null ) return null; |
|
} |
|
else { |
|
// have to set year, month and date together to avoid overflow based on current date. |
|
result.setFullYear( year, month, date ); |
|
// check to see if date overflowed for specified month (only checked 1-31 above). |
|
if ( result.getDate() !== date ) return null; |
|
// invalid day of week. |
|
if ( weekDay !== null && result.getDay() !== weekDay ) { |
|
return null; |
|
} |
|
} |
|
// if pm designator token was found make sure the hours fit the 24-hour clock. |
|
if ( pmHour && hour < 12 ) { |
|
hour += 12; |
|
} |
|
result.setHours( hour, min, sec, msec ); |
|
if ( tzMinOffset !== null ) { |
|
// adjust timezone to utc before applying local offset. |
|
var adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() ); |
|
// Safari limits hours and minutes to the range of -127 to 127. We need to use setHours |
|
// to ensure both these fields will not exceed this range. adjustedMin will range |
|
// somewhere between -1440 and 1500, so we only need to split this into hours. |
|
result.setHours( result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60 ); |
|
} |
|
return result; |
|
}; |
|
}()); |
|
|
|
parseNegativePattern = function( value, nf, negativePattern ) { |
|
var neg = nf[ "-" ], |
|
pos = nf[ "+" ], |
|
ret; |
|
switch ( negativePattern ) { |
|
case "n -": |
|
neg = " " + neg; |
|
pos = " " + pos; |
|
/* falls through */ |
|
case "n-": |
|
if ( endsWith(value, neg) ) { |
|
ret = [ "-", value.substr(0, value.length - neg.length) ]; |
|
} |
|
else if ( endsWith(value, pos) ) { |
|
ret = [ "+", value.substr(0, value.length - pos.length) ]; |
|
} |
|
break; |
|
case "- n": |
|
neg += " "; |
|
pos += " "; |
|
/* falls through */ |
|
case "-n": |
|
if ( startsWith(value, neg) ) { |
|
ret = [ "-", value.substr(neg.length) ]; |
|
} |
|
else if ( startsWith(value, pos) ) { |
|
ret = [ "+", value.substr(pos.length) ]; |
|
} |
|
break; |
|
case "(n)": |
|
if ( startsWith(value, "(") && endsWith(value, ")") ) { |
|
ret = [ "-", value.substr(1, value.length - 2) ]; |
|
} |
|
break; |
|
} |
|
return ret || [ "", value ]; |
|
}; |
|
|
|
// |
|
// public instance functions |
|
// |
|
|
|
Globalize.prototype.findClosestCulture = function( cultureSelector ) { |
|
return Globalize.findClosestCulture.call( this, cultureSelector ); |
|
}; |
|
|
|
Globalize.prototype.format = function( value, format, cultureSelector ) { |
|
return Globalize.format.call( this, value, format, cultureSelector ); |
|
}; |
|
|
|
Globalize.prototype.localize = function( key, cultureSelector ) { |
|
return Globalize.localize.call( this, key, cultureSelector ); |
|
}; |
|
|
|
Globalize.prototype.parseInt = function( value, radix, cultureSelector ) { |
|
return Globalize.parseInt.call( this, value, radix, cultureSelector ); |
|
}; |
|
|
|
Globalize.prototype.parseFloat = function( value, radix, cultureSelector ) { |
|
return Globalize.parseFloat.call( this, value, radix, cultureSelector ); |
|
}; |
|
|
|
Globalize.prototype.culture = function( cultureSelector ) { |
|
return Globalize.culture.call( this, cultureSelector ); |
|
}; |
|
|
|
// |
|
// public singleton functions |
|
// |
|
|
|
Globalize.addCultureInfo = function( cultureName, baseCultureName, info ) { |
|
|
|
var base = {}, |
|
isNew = false; |
|
|
|
if ( typeof cultureName !== "string" ) { |
|
// cultureName argument is optional string. If not specified, assume info is first |
|
// and only argument. Specified info deep-extends current culture. |
|
info = cultureName; |
|
cultureName = this.culture().name; |
|
base = this.cultures[ cultureName ]; |
|
} else if ( typeof baseCultureName !== "string" ) { |
|
// baseCultureName argument is optional string. If not specified, assume info is second |
|
// argument. Specified info deep-extends specified culture. |
|
// If specified culture does not exist, create by deep-extending default |
|
info = baseCultureName; |
|
isNew = ( this.cultures[ cultureName ] == null ); |
|
base = this.cultures[ cultureName ] || this.cultures[ "default" ]; |
|
} else { |
|
// cultureName and baseCultureName specified. Assume a new culture is being created |
|
// by deep-extending an specified base culture |
|
isNew = true; |
|
base = this.cultures[ baseCultureName ]; |
|
} |
|
|
|
this.cultures[ cultureName ] = extend(true, {}, |
|
base, |
|
info |
|
); |
|
// Make the standard calendar the current culture if it's a new culture |
|
if ( isNew ) { |
|
this.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard; |
|
} |
|
}; |
|
|
|
Globalize.findClosestCulture = function( name ) { |
|
var match; |
|
if ( !name ) { |
|
return this.findClosestCulture( this.cultureSelector ) || this.cultures[ "default" ]; |
|
} |
|
if ( typeof name === "string" ) { |
|
name = name.split( "," ); |
|
} |
|
if ( isArray(name) ) { |
|
var lang, |
|
cultures = this.cultures, |
|
list = name, |
|
i, l = list.length, |
|
prioritized = []; |
|
for ( i = 0; i < l; i++ ) { |
|
name = trim( list[i] ); |
|
var pri, parts = name.split( ";" ); |
|
lang = trim( parts[0] ); |
|
if ( parts.length === 1 ) { |
|
pri = 1; |
|
} |
|
else { |
|
name = trim( parts[1] ); |
|
if ( name.indexOf("q=") === 0 ) { |
|
name = name.substr( 2 ); |
|
pri = parseFloat( name ); |
|
pri = isNaN( pri ) ? 0 : pri; |
|
} |
|
else { |
|
pri = 1; |
|
} |
|
} |
|
prioritized.push({ lang: lang, pri: pri }); |
|
} |
|
prioritized.sort(function( a, b ) { |
|
if ( a.pri < b.pri ) { |
|
return 1; |
|
} else if ( a.pri > b.pri ) { |
|
return -1; |
|
} |
|
return 0; |
|
}); |
|
// exact match |
|
for ( i = 0; i < l; i++ ) { |
|
lang = prioritized[ i ].lang; |
|
match = cultures[ lang ]; |
|
if ( match ) { |
|
return match; |
|
} |
|
} |
|
|
|
// neutral language match |
|
for ( i = 0; i < l; i++ ) { |
|
lang = prioritized[ i ].lang; |
|
do { |
|
var index = lang.lastIndexOf( "-" ); |
|
if ( index === -1 ) { |
|
break; |
|
} |
|
// strip off the last part. e.g. en-US => en |
|
lang = lang.substr( 0, index ); |
|
match = cultures[ lang ]; |
|
if ( match ) { |
|
return match; |
|
} |
|
} |
|
while ( 1 ); |
|
} |
|
|
|
// last resort: match first culture using that language |
|
for ( i = 0; i < l; i++ ) { |
|
lang = prioritized[ i ].lang; |
|
for ( var cultureKey in cultures ) { |
|
var culture = cultures[ cultureKey ]; |
|
if ( culture.language == lang ) { |
|
return culture; |
|
} |
|
} |
|
} |
|
} |
|
else if ( typeof name === "object" ) { |
|
return name; |
|
} |
|
return match || null; |
|
}; |
|
|
|
Globalize.format = function( value, format, cultureSelector ) { |
|
var culture = this.findClosestCulture( cultureSelector ); |
|
if ( value instanceof Date ) { |
|
value = formatDate( value, format, culture ); |
|
} |
|
else if ( typeof value === "number" ) { |
|
value = formatNumber( value, format, culture ); |
|
} |
|
return value; |
|
}; |
|
|
|
Globalize.localize = function( key, cultureSelector ) { |
|
return this.findClosestCulture( cultureSelector ).messages[ key ] || |
|
this.cultures[ "default" ].messages[ key ]; |
|
}; |
|
|
|
Globalize.parseDate = function( value, formats, culture ) { |
|
culture = this.findClosestCulture( culture ); |
|
|
|
var date, prop, patterns; |
|
if ( formats ) { |
|
if ( typeof formats === "string" ) { |
|
formats = [ formats ]; |
|
} |
|
if ( formats.length ) { |
|
for ( var i = 0, l = formats.length; i < l; i++ ) { |
|
var format = formats[ i ]; |
|
if ( format ) { |
|
date = parseExact( value, format, culture ); |
|
if ( date ) { |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} else { |
|
patterns = culture.calendar.patterns; |
|
for ( prop in patterns ) { |
|
date = parseExact( value, patterns[prop], culture ); |
|
if ( date ) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
return date || null; |
|
}; |
|
|
|
Globalize.parseInt = function( value, radix, cultureSelector ) { |
|
return truncate( Globalize.parseFloat(value, radix, cultureSelector) ); |
|
}; |
|
|
|
Globalize.parseFloat = function( value, radix, cultureSelector ) { |
|
// radix argument is optional |
|
if ( typeof radix !== "number" ) { |
|
cultureSelector = radix; |
|
radix = 10; |
|
} |
|
|
|
var culture = this.findClosestCulture( cultureSelector ); |
|
var ret = NaN, |
|
nf = culture.numberFormat; |
|
|
|
if ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) { |
|
// remove currency symbol |
|
value = value.replace( culture.numberFormat.currency.symbol, "" ); |
|
// replace decimal seperator |
|
value = value.replace( culture.numberFormat.currency["."], culture.numberFormat["."] ); |
|
} |
|
|
|
// trim leading and trailing whitespace |
|
value = trim( value ); |
|
|
|
// allow infinity or hexidecimal |
|
if ( regexInfinity.test(value) ) { |
|
ret = parseFloat( value ); |
|
} |
|
else if ( !radix && regexHex.test(value) ) { |
|
ret = parseInt( value, 16 ); |
|
} |
|
else { |
|
|
|
// determine sign and number |
|
var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ), |
|
sign = signInfo[ 0 ], |
|
num = signInfo[ 1 ]; |
|
|
|
// #44 - try parsing as "(n)" |
|
if ( sign === "" && nf.pattern[0] !== "(n)" ) { |
|
signInfo = parseNegativePattern( value, nf, "(n)" ); |
|
sign = signInfo[ 0 ]; |
|
num = signInfo[ 1 ]; |
|
} |
|
|
|
// try parsing as "-n" |
|
if ( sign === "" && nf.pattern[0] !== "-n" ) { |
|
signInfo = parseNegativePattern( value, nf, "-n" ); |
|
sign = signInfo[ 0 ]; |
|
num = signInfo[ 1 ]; |
|
} |
|
|
|
sign = sign || "+"; |
|
|
|
// determine exponent and number |
|
var exponent, |
|
intAndFraction, |
|
exponentPos = num.indexOf( "e" ); |
|
if ( exponentPos < 0 ) exponentPos = num.indexOf( "E" ); |
|
if ( exponentPos < 0 ) { |
|
intAndFraction = num; |
|
exponent = null; |
|
} |
|
else { |
|
intAndFraction = num.substr( 0, exponentPos ); |
|
exponent = num.substr( exponentPos + 1 ); |
|
} |
|
// determine decimal position |
|
var integer, |
|
fraction, |
|
decSep = nf[ "." ], |
|
decimalPos = intAndFraction.indexOf( decSep ); |
|
if ( decimalPos < 0 ) { |
|
integer = intAndFraction; |
|
fraction = null; |
|
} |
|
else { |
|
integer = intAndFraction.substr( 0, decimalPos ); |
|
fraction = intAndFraction.substr( decimalPos + decSep.length ); |
|
} |
|
// handle groups (e.g. 1,000,000) |
|
var groupSep = nf[ "," ]; |
|
integer = integer.split( groupSep ).join( "" ); |
|
var altGroupSep = groupSep.replace( /\u00A0/g, " " ); |
|
if ( groupSep !== altGroupSep ) { |
|
integer = integer.split( altGroupSep ).join( "" ); |
|
} |
|
// build a natively parsable number string |
|
var p = sign + integer; |
|
if ( fraction !== null ) { |
|
p += "." + fraction; |
|
} |
|
if ( exponent !== null ) { |
|
// exponent itself may have a number patternd |
|
var expSignInfo = parseNegativePattern( exponent, nf, "-n" ); |
|
p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ]; |
|
} |
|
if ( regexParseFloat.test(p) ) { |
|
ret = parseFloat( p ); |
|
} |
|
} |
|
return ret; |
|
}; |
|
|
|
Globalize.culture = function( cultureSelector ) { |
|
// setter |
|
if ( typeof cultureSelector !== "undefined" ) { |
|
this.cultureSelector = cultureSelector; |
|
} |
|
// getter |
|
return this.findClosestCulture( cultureSelector ) || this.cultures[ "default" ]; |
|
}; |
|
|
|
}( this ));
|
|
|