1574 lines
44 KiB
JavaScript
Executable File
1574 lines
44 KiB
JavaScript
Executable File
/*!
|
|
* 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( deep ) {
|
|
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 ) {
|
|
return value | 0;
|
|
};
|
|
|
|
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;
|
|
|
|
if ( !format || !format.length || format === "i" ) {
|
|
var ret;
|
|
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();
|
|
}
|
|
}
|
|
|
|
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 + "\'.";
|
|
break;
|
|
}
|
|
}
|
|
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 );
|
|
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;
|
|
// fall through
|
|
case "C":
|
|
formatInfo = formatInfo || nf.currency;
|
|
// fall 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.
|
|
var now = new Date(),
|
|
era = getEra( now );
|
|
if ( year < 100 ) {
|
|
var twoDigitYearMax = cal.twoDigitYearMax;
|
|
twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax;
|
|
var curr = getEraYear( now, cal, era );
|
|
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 = "(\\" + cal[ "/" ] + ")";
|
|
break;
|
|
default:
|
|
throw "Invalid date format pattern \'" + m + "\'.";
|
|
break;
|
|
}
|
|
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;
|
|
// fall 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 += " ";
|
|
// fall 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.cultures[ 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 ) {
|
|
return a.pri < b.pri ? 1 : -1;
|
|
});
|
|
|
|
// 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 ) {
|
|
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.culture[ "default" ];
|
|
};
|
|
|
|
}( this ));
|