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