jquery-ui/external/glob.js

1334 lines
49 KiB
JavaScript
Raw Normal View History

/*
* Globalization
* http://github.com/nje/jquery-glob
*/
(function() {
var Globalization = {},
localized = { en: {} };
localized["default"] = localized.en;
Globalization.extend = function( deep ) {
var target = arguments[ 1 ] || {};
for ( var i = 2, l = arguments.length; i < l; i++ ) {
var source = arguments[ i ];
if ( source ) {
for ( var field in source ) {
var sourceVal = source[ field ];
if ( typeof sourceVal !== "undefined" ) {
if ( deep && (isObject( sourceVal ) || isArray( sourceVal )) ) {
var targetVal = target[ field ];
// extend onto the existing value, or create a new one
targetVal = targetVal && (isObject( targetVal ) || isArray( targetVal ))
? targetVal
: (isArray( sourceVal ) ? [] : {});
target[ field ] = this.extend( true, targetVal, sourceVal );
}
else {
target[ field ] = sourceVal;
}
}
}
}
}
return target;
}
Globalization.findClosestCulture = function(name) {
var match;
if ( !name ) {
return this.culture || this.cultures["default"];
}
if ( isString( name ) ) {
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, 10 );
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;
});
for ( i = 0; i < l; i++ ) {
lang = prioritized[ i ].lang;
match = cultures[ lang ];
// exact match?
if ( match ) {
return match;
}
}
for ( i = 0; i < l; i++ ) {
lang = prioritized[ i ].lang;
// for each entry try its neutral language
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 );
}
}
else if ( typeof name === 'object' ) {
return name;
}
return match || null;
}
Globalization.preferCulture = function(name) {
this.culture = this.findClosestCulture( name ) || this.cultures["default"];
}
Globalization.localize = function(key, culture, value) {
if (typeof culture === 'string') {
culture = culture || "default";
culture = this.cultures[ culture ] || { name: culture };
}
var local = localized[ culture.name ];
if ( arguments.length === 3 ) {
if ( !local) {
local = localized[ culture.name ] = {};
}
local[ key ] = value;
}
else {
if ( local ) {
value = local[ key ];
}
if ( typeof value === 'undefined' ) {
var language = localized[ culture.language ];
if ( language ) {
value = language[ key ];
}
if ( typeof value === 'undefined' ) {
value = localized["default"][ key ];
}
}
}
return typeof value === "undefined" ? null : value;
}
Globalization.format = function(value, format, culture) {
culture = this.findClosestCulture( culture );
if ( typeof value === "number" ) {
value = formatNumber( value, format, culture );
}
else if ( value instanceof Date ) {
value = formatDate( value, format, culture );
}
return value;
}
Globalization.parseInt = function(value, radix, culture) {
return Math.floor( this.parseFloat( value, radix, culture ) );
}
Globalization.parseFloat = function(value, radix, culture) {
culture = this.findClosestCulture( culture );
var ret = NaN,
nf = culture.numberFormat;
// trim leading and trailing whitespace
value = trim( value );
// allow infinity or hexidecimal
if (regexInfinity.test(value)) {
ret = parseFloat(value, radix);
}
else if (!radix && regexHex.test(value)) {
ret = parseInt(value, 16);
}
else {
var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ),
sign = signInfo[0],
num = signInfo[1];
// determine sign and number
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;
}
Globalization.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;
}
// 1. When defining a culture, all fields are required except the ones stated as optional.
// 2. You can use Globalization.extend to copy an existing culture and provide only the differing values,
// a good practice since most cultures do not differ too much from the 'default' culture.
// DO use the 'default' culture if you do this, as it is the only one that definitely
// exists.
// 3. Other plugins may add to the culture information provided by extending it. However,
// that plugin may extend it prior to the culture being defined, or after. Therefore,
// do not overwrite values that already exist when defining the baseline for a culture,
// by extending your culture object with the existing one.
// 4. 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.
// 5. 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".
// To define a culture, use the following pattern, which handles defining the culture based
// on the 'default culture, extending it with the existing culture if it exists, and defining
// it if it does not exist.
// Globalization.cultures.foo = Globalization.extend(true, Globalization.extend(true, {}, Globalization.cultures['default'], fooCulture), Globalization.cultures.foo)
var cultures = Globalization.cultures = Globalization.cultures || {};
var en = cultures["default"] = cultures.en = Globalization.extend(true, {
// 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
'-': "-",
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.
*/
}
}
}, cultures.en);
en.calendar = en.calendar || en.calendars.standard;
var regexTrim = /^\s+|\s+$/g,
regexInfinity = /^[+-]?infinity$/i,
regexHex = /^0x[a-f0-9]+$/i,
regexParseFloat = /^[+-]?\d*\.?\d*(e[+-]?\d+)?$/,
toString = Object.prototype.toString;
function startsWith(value, pattern) {
return value.indexOf( pattern ) === 0;
}
function endsWith(value, pattern) {
return value.substr( value.length - pattern.length ) === pattern;
}
function trim(value) {
return (value+"").replace( regexTrim, "" );
}
function zeroPad(str, count, left) {
for (var l=str.length; l < count; l++) {
str = (left ? ('0' + str) : (str + '0'));
}
return str;
}
function isArray(obj) {
return toString.call(obj) === "[object Array]";
}
function isString(obj) {
return toString.call(obj) === "[object String]";
}
function isObject(obj) {
return toString.call(obj) === "[object Object]";
}
function arrayIndexOf( 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;
}
// *************************************** Numbers ***************************************
function expandNumber(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;
}
function parseNegativePattern(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 ];
}
function formatNumber(value, format, culture) {
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';
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;
}
// *************************************** Dates ***************************************
function outOfRange(value, low, high) {
return value < low || value > high;
}
function expandYear(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;
}
function getEra(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;
}
function toUpper(value) {
// 'he-IL' has non-breaking space in weekday names.
return value.split( "\u00A0" ).join(' ').toUpperCase();
}
function toUpperArray(arr) {
var results = [];
for ( var i = 0, l = arr.length; i < l; i++ ) {
results[i] = toUpper(arr[i]);
}
return results;
}
function getEraYear(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;
}
function getDayIndex(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;
}
function getMonthIndex(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;
}
function appendPreOrPostMatch(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;
}
function expandFormat(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;
}
function getParseRegExp(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;
}
function getTokenRegExp() {
// 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;
}
function parseExact(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;
}
function formatDate(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
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( '' );
}
// EXPORTS
window.Globalization = Globalization;
//jQuery.findClosestCulture = Globalization.findClosestCulture;
//jQuery.culture = Globalization.culture;
//jQuery.cultures = Globalization.cultures
//jQuery.preferCulture = Globalization.preferCulture
//jQuery.localize = Globalization.localize
//jQuery.format = Globalization.format
//jQuery.parseInt = Globalization.parseInt
//jQuery.parseFloat = Globalization.parseFloat
//jQuery.parseDate = Globalization.parseDate
})();