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