root / drupal7 / sites / all / modules / jquery_update / replace / ui / external / globalize.js @ 503b3f7b
1 | 503b3f7b | Assos Assos | /*!
|
---|---|---|---|
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 )); |