Mon, 11 Aug 2014 11:09:23 +0200
Do not use 100% cpu on a system without devices
117 | 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 | var l; | |
715 | if ( exponent > 0 ) { | |
716 | right = zeroPad( right, exponent, false ); | |
717 | numberString += right.slice( 0, exponent ); | |
718 | right = right.substr( exponent ); | |
719 | } | |
720 | else if ( exponent < 0 ) { | |
721 | exponent = -exponent; | |
722 | numberString = zeroPad( numberString, exponent + 1, true ); | |
723 | right = numberString.slice( -exponent, numberString.length ) + right; | |
724 | numberString = numberString.slice( 0, -exponent ); | |
725 | } | |
726 | ||
727 | if ( precision > 0 ) { | |
728 | right = formatInfo[ "." ] + | |
729 | ( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) ); | |
730 | } | |
731 | else { | |
732 | right = ""; | |
733 | } | |
734 | ||
735 | var stringIndex = numberString.length - 1, | |
736 | sep = formatInfo[ "," ], | |
737 | ret = ""; | |
738 | ||
739 | while ( stringIndex >= 0 ) { | |
740 | if ( curSize === 0 || curSize > stringIndex ) { | |
741 | return numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right ); | |
742 | } | |
743 | ret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : "" ); | |
744 | ||
745 | stringIndex -= curSize; | |
746 | ||
747 | if ( curGroupIndex < groupSizes.length ) { | |
748 | curSize = groupSizes[ curGroupIndex ]; | |
749 | curGroupIndex++; | |
750 | } | |
751 | } | |
752 | ||
753 | return numberString.slice( 0, stringIndex + 1 ) + sep + ret + right; | |
754 | }; | |
755 | ||
756 | formatNumber = function( value, format, culture ) { | |
757 | if ( !isFinite(value) ) { | |
758 | if ( value === Infinity ) { | |
759 | return culture.numberFormat.positiveInfinity; | |
760 | } | |
761 | if ( value === -Infinity ) { | |
762 | return culture.numberFormat.negativeInfinity; | |
763 | } | |
764 | return culture.numberFormat.NaN; | |
765 | } | |
766 | if ( !format || format === "i" ) { | |
767 | return culture.name.length ? value.toLocaleString() : value.toString(); | |
768 | } | |
769 | format = format || "D"; | |
770 | ||
771 | var nf = culture.numberFormat, | |
772 | number = Math.abs( value ), | |
773 | precision = -1, | |
774 | pattern; | |
775 | if ( format.length > 1 ) precision = parseInt( format.slice(1), 10 ); | |
776 | ||
777 | var current = format.charAt( 0 ).toUpperCase(), | |
778 | formatInfo; | |
779 | ||
780 | switch ( current ) { | |
781 | case "D": | |
782 | pattern = "n"; | |
783 | number = truncate( number ); | |
784 | if ( precision !== -1 ) { | |
785 | number = zeroPad( "" + number, precision, true ); | |
786 | } | |
787 | if ( value < 0 ) number = "-" + number; | |
788 | break; | |
789 | case "N": | |
790 | formatInfo = nf; | |
791 | /* falls through */ | |
792 | case "C": | |
793 | formatInfo = formatInfo || nf.currency; | |
794 | /* falls through */ | |
795 | case "P": | |
796 | formatInfo = formatInfo || nf.percent; | |
797 | pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" ); | |
798 | if ( precision === -1 ) precision = formatInfo.decimals; | |
799 | number = expandNumber( number * (current === "P" ? 100 : 1), precision, formatInfo ); | |
800 | break; | |
801 | default: | |
802 | throw "Bad number format specifier: " + current; | |
803 | } | |
804 | ||
805 | var patternParts = /n|\$|-|%/g, | |
806 | ret = ""; | |
807 | for ( ; ; ) { | |
808 | var index = patternParts.lastIndex, | |
809 | ar = patternParts.exec( pattern ); | |
810 | ||
811 | ret += pattern.slice( index, ar ? ar.index : pattern.length ); | |
812 | ||
813 | if ( !ar ) { | |
814 | break; | |
815 | } | |
816 | ||
817 | switch ( ar[0] ) { | |
818 | case "n": | |
819 | ret += number; | |
820 | break; | |
821 | case "$": | |
822 | ret += nf.currency.symbol; | |
823 | break; | |
824 | case "-": | |
825 | // don't make 0 negative | |
826 | if ( /[1-9]/.test(number) ) { | |
827 | ret += nf[ "-" ]; | |
828 | } | |
829 | break; | |
830 | case "%": | |
831 | ret += nf.percent.symbol; | |
832 | break; | |
833 | } | |
834 | } | |
835 | ||
836 | return ret; | |
837 | }; | |
838 | ||
839 | }()); | |
840 | ||
841 | getTokenRegExp = function() { | |
842 | // regular expression for matching date and time tokens in format strings. | |
843 | 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); | |
844 | }; | |
845 | ||
846 | getEra = function( date, eras ) { | |
847 | if ( !eras ) return 0; | |
848 | var start, ticks = date.getTime(); | |
849 | for ( var i = 0, l = eras.length; i < l; i++ ) { | |
850 | start = eras[ i ].start; | |
851 | if ( start === null || ticks >= start ) { | |
852 | return i; | |
853 | } | |
854 | } | |
855 | return 0; | |
856 | }; | |
857 | ||
858 | getEraYear = function( date, cal, era, sortable ) { | |
859 | var year = date.getFullYear(); | |
860 | if ( !sortable && cal.eras ) { | |
861 | // convert normal gregorian year to era-shifted gregorian | |
862 | // year by subtracting the era offset | |
863 | year -= cal.eras[ era ].offset; | |
864 | } | |
865 | return year; | |
866 | }; | |
867 | ||
868 | // parseExact | |
869 | (function() { | |
870 | var expandYear, | |
871 | getDayIndex, | |
872 | getMonthIndex, | |
873 | getParseRegExp, | |
874 | outOfRange, | |
875 | toUpper, | |
876 | toUpperArray; | |
877 | ||
878 | expandYear = function( cal, year ) { | |
879 | // expands 2-digit year into 4 digits. | |
880 | if ( year < 100 ) { | |
881 | var now = new Date(), | |
882 | era = getEra( now ), | |
883 | curr = getEraYear( now, cal, era ), | |
884 | twoDigitYearMax = cal.twoDigitYearMax; | |
885 | twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax; | |
886 | year += curr - ( curr % 100 ); | |
887 | if ( year > twoDigitYearMax ) { | |
888 | year -= 100; | |
889 | } | |
890 | } | |
891 | return year; | |
892 | }; | |
893 | ||
894 | getDayIndex = function ( cal, value, abbr ) { | |
895 | var ret, | |
896 | days = cal.days, | |
897 | upperDays = cal._upperDays; | |
898 | if ( !upperDays ) { | |
899 | cal._upperDays = upperDays = [ | |
900 | toUpperArray( days.names ), | |
901 | toUpperArray( days.namesAbbr ), | |
902 | toUpperArray( days.namesShort ) | |
903 | ]; | |
904 | } | |
905 | value = toUpper( value ); | |
906 | if ( abbr ) { | |
907 | ret = arrayIndexOf( upperDays[1], value ); | |
908 | if ( ret === -1 ) { | |
909 | ret = arrayIndexOf( upperDays[2], value ); | |
910 | } | |
911 | } | |
912 | else { | |
913 | ret = arrayIndexOf( upperDays[0], value ); | |
914 | } | |
915 | return ret; | |
916 | }; | |
917 | ||
918 | getMonthIndex = function( cal, value, abbr ) { | |
919 | var months = cal.months, | |
920 | monthsGen = cal.monthsGenitive || cal.months, | |
921 | upperMonths = cal._upperMonths, | |
922 | upperMonthsGen = cal._upperMonthsGen; | |
923 | if ( !upperMonths ) { | |
924 | cal._upperMonths = upperMonths = [ | |
925 | toUpperArray( months.names ), | |
926 | toUpperArray( months.namesAbbr ) | |
927 | ]; | |
928 | cal._upperMonthsGen = upperMonthsGen = [ | |
929 | toUpperArray( monthsGen.names ), | |
930 | toUpperArray( monthsGen.namesAbbr ) | |
931 | ]; | |
932 | } | |
933 | value = toUpper( value ); | |
934 | var i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value ); | |
935 | if ( i < 0 ) { | |
936 | i = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value ); | |
937 | } | |
938 | return i; | |
939 | }; | |
940 | ||
941 | getParseRegExp = function( cal, format ) { | |
942 | // converts a format string into a regular expression with groups that | |
943 | // can be used to extract date fields from a date string. | |
944 | // check for a cached parse regex. | |
945 | var re = cal._parseRegExp; | |
946 | if ( !re ) { | |
947 | cal._parseRegExp = re = {}; | |
948 | } | |
949 | else { | |
950 | var reFormat = re[ format ]; | |
951 | if ( reFormat ) { | |
952 | return reFormat; | |
953 | } | |
954 | } | |
955 | ||
956 | // expand single digit formats, then escape regular expression characters. | |
957 | var expFormat = expandFormat( cal, format ).replace( /([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1" ), | |
958 | regexp = [ "^" ], | |
959 | groups = [], | |
960 | index = 0, | |
961 | quoteCount = 0, | |
962 | tokenRegExp = getTokenRegExp(), | |
963 | match; | |
964 | ||
965 | // iterate through each date token found. | |
966 | while ( (match = tokenRegExp.exec(expFormat)) !== null ) { | |
967 | var preMatch = expFormat.slice( index, match.index ); | |
968 | index = tokenRegExp.lastIndex; | |
969 | ||
970 | // don't replace any matches that occur inside a string literal. | |
971 | quoteCount += appendPreOrPostMatch( preMatch, regexp ); | |
972 | if ( quoteCount % 2 ) { | |
973 | regexp.push( match[0] ); | |
974 | continue; | |
975 | } | |
976 | ||
977 | // add a regex group for the token. | |
978 | var m = match[ 0 ], | |
979 | len = m.length, | |
980 | add; | |
981 | switch ( m ) { | |
982 | case "dddd": case "ddd": | |
983 | case "MMMM": case "MMM": | |
984 | case "gg": case "g": | |
985 | add = "(\\D+)"; | |
986 | break; | |
987 | case "tt": case "t": | |
988 | add = "(\\D*)"; | |
989 | break; | |
990 | case "yyyy": | |
991 | case "fff": | |
992 | case "ff": | |
993 | case "f": | |
994 | add = "(\\d{" + len + "})"; | |
995 | break; | |
996 | case "dd": case "d": | |
997 | case "MM": case "M": | |
998 | case "yy": case "y": | |
999 | case "HH": case "H": | |
1000 | case "hh": case "h": | |
1001 | case "mm": case "m": | |
1002 | case "ss": case "s": | |
1003 | add = "(\\d\\d?)"; | |
1004 | break; | |
1005 | case "zzz": | |
1006 | add = "([+-]?\\d\\d?:\\d{2})"; | |
1007 | break; | |
1008 | case "zz": case "z": | |
1009 | add = "([+-]?\\d\\d?)"; | |
1010 | break; | |
1011 | case "/": | |
1012 | add = "(\\/)"; | |
1013 | break; | |
1014 | default: | |
1015 | throw "Invalid date format pattern \'" + m + "\'."; | |
1016 | } | |
1017 | if ( add ) { | |
1018 | regexp.push( add ); | |
1019 | } | |
1020 | groups.push( match[0] ); | |
1021 | } | |
1022 | appendPreOrPostMatch( expFormat.slice(index), regexp ); | |
1023 | regexp.push( "$" ); | |
1024 | ||
1025 | // allow whitespace to differ when matching formats. | |
1026 | var regexpStr = regexp.join( "" ).replace( /\s+/g, "\\s+" ), | |
1027 | parseRegExp = { "regExp": regexpStr, "groups": groups }; | |
1028 | ||
1029 | // cache the regex for this format. | |
1030 | return re[ format ] = parseRegExp; | |
1031 | }; | |
1032 | ||
1033 | outOfRange = function( value, low, high ) { | |
1034 | return value < low || value > high; | |
1035 | }; | |
1036 | ||
1037 | toUpper = function( value ) { | |
1038 | // "he-IL" has non-breaking space in weekday names. | |
1039 | return value.split( "\u00A0" ).join( " " ).toUpperCase(); | |
1040 | }; | |
1041 | ||
1042 | toUpperArray = function( arr ) { | |
1043 | var results = []; | |
1044 | for ( var i = 0, l = arr.length; i < l; i++ ) { | |
1045 | results[ i ] = toUpper( arr[i] ); | |
1046 | } | |
1047 | return results; | |
1048 | }; | |
1049 | ||
1050 | parseExact = function( value, format, culture ) { | |
1051 | // try to parse the date string by matching against the format string | |
1052 | // while using the specified culture for date field names. | |
1053 | value = trim( value ); | |
1054 | var cal = culture.calendar, | |
1055 | // convert date formats into regular expressions with groupings. | |
1056 | // use the regexp to determine the input format and extract the date fields. | |
1057 | parseInfo = getParseRegExp( cal, format ), | |
1058 | match = new RegExp( parseInfo.regExp ).exec( value ); | |
1059 | if ( match === null ) { | |
1060 | return null; | |
1061 | } | |
1062 | // found a date format that matches the input. | |
1063 | var groups = parseInfo.groups, | |
1064 | era = null, year = null, month = null, date = null, weekDay = null, | |
1065 | hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null, | |
1066 | pmHour = false; | |
1067 | // iterate the format groups to extract and set the date fields. | |
1068 | for ( var j = 0, jl = groups.length; j < jl; j++ ) { | |
1069 | var matchGroup = match[ j + 1 ]; | |
1070 | if ( matchGroup ) { | |
1071 | var current = groups[ j ], | |
1072 | clength = current.length, | |
1073 | matchInt = parseInt( matchGroup, 10 ); | |
1074 | switch ( current ) { | |
1075 | case "dd": case "d": | |
1076 | // Day of month. | |
1077 | date = matchInt; | |
1078 | // check that date is generally in valid range, also checking overflow below. | |
1079 | if ( outOfRange(date, 1, 31) ) return null; | |
1080 | break; | |
1081 | case "MMM": case "MMMM": | |
1082 | month = getMonthIndex( cal, matchGroup, clength === 3 ); | |
1083 | if ( outOfRange(month, 0, 11) ) return null; | |
1084 | break; | |
1085 | case "M": case "MM": | |
1086 | // Month. | |
1087 | month = matchInt - 1; | |
1088 | if ( outOfRange(month, 0, 11) ) return null; | |
1089 | break; | |
1090 | case "y": case "yy": | |
1091 | case "yyyy": | |
1092 | year = clength < 4 ? expandYear( cal, matchInt ) : matchInt; | |
1093 | if ( outOfRange(year, 0, 9999) ) return null; | |
1094 | break; | |
1095 | case "h": case "hh": | |
1096 | // Hours (12-hour clock). | |
1097 | hour = matchInt; | |
1098 | if ( hour === 12 ) hour = 0; | |
1099 | if ( outOfRange(hour, 0, 11) ) return null; | |
1100 | break; | |
1101 | case "H": case "HH": | |
1102 | // Hours (24-hour clock). | |
1103 | hour = matchInt; | |
1104 | if ( outOfRange(hour, 0, 23) ) return null; | |
1105 | break; | |
1106 | case "m": case "mm": | |
1107 | // Minutes. | |
1108 | min = matchInt; | |
1109 | if ( outOfRange(min, 0, 59) ) return null; | |
1110 | break; | |
1111 | case "s": case "ss": | |
1112 | // Seconds. | |
1113 | sec = matchInt; | |
1114 | if ( outOfRange(sec, 0, 59) ) return null; | |
1115 | break; | |
1116 | case "tt": case "t": | |
1117 | // AM/PM designator. | |
1118 | // see if it is standard, upper, or lower case PM. If not, ensure it is at least one of | |
1119 | // the AM tokens. If not, fail the parse for this format. | |
1120 | pmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] ); | |
1121 | if ( | |
1122 | !pmHour && ( | |
1123 | !cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] ) | |
1124 | ) | |
1125 | ) return null; | |
1126 | break; | |
1127 | case "f": | |
1128 | // Deciseconds. | |
1129 | case "ff": | |
1130 | // Centiseconds. | |
1131 | case "fff": | |
1132 | // Milliseconds. | |
1133 | msec = matchInt * Math.pow( 10, 3 - clength ); | |
1134 | if ( outOfRange(msec, 0, 999) ) return null; | |
1135 | break; | |
1136 | case "ddd": | |
1137 | // Day of week. | |
1138 | case "dddd": | |
1139 | // Day of week. | |
1140 | weekDay = getDayIndex( cal, matchGroup, clength === 3 ); | |
1141 | if ( outOfRange(weekDay, 0, 6) ) return null; | |
1142 | break; | |
1143 | case "zzz": | |
1144 | // Time zone offset in +/- hours:min. | |
1145 | var offsets = matchGroup.split( /:/ ); | |
1146 | if ( offsets.length !== 2 ) return null; | |
1147 | hourOffset = parseInt( offsets[0], 10 ); | |
1148 | if ( outOfRange(hourOffset, -12, 13) ) return null; | |
1149 | var minOffset = parseInt( offsets[1], 10 ); | |
1150 | if ( outOfRange(minOffset, 0, 59) ) return null; | |
1151 | tzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, "-") ? -minOffset : minOffset ); | |
1152 | break; | |
1153 | case "z": case "zz": | |
1154 | // Time zone offset in +/- hours. | |
1155 | hourOffset = matchInt; | |
1156 | if ( outOfRange(hourOffset, -12, 13) ) return null; | |
1157 | tzMinOffset = hourOffset * 60; | |
1158 | break; | |
1159 | case "g": case "gg": | |
1160 | var eraName = matchGroup; | |
1161 | if ( !eraName || !cal.eras ) return null; | |
1162 | eraName = trim( eraName.toLowerCase() ); | |
1163 | for ( var i = 0, l = cal.eras.length; i < l; i++ ) { | |
1164 | if ( eraName === cal.eras[i].name.toLowerCase() ) { | |
1165 | era = i; | |
1166 | break; | |
1167 | } | |
1168 | } | |
1169 | // could not find an era with that name | |
1170 | if ( era === null ) return null; | |
1171 | break; | |
1172 | } | |
1173 | } | |
1174 | } | |
1175 | var result = new Date(), defaultYear, convert = cal.convert; | |
1176 | defaultYear = convert ? convert.fromGregorian( result )[ 0 ] : result.getFullYear(); | |
1177 | if ( year === null ) { | |
1178 | year = defaultYear; | |
1179 | } | |
1180 | else if ( cal.eras ) { | |
1181 | // year must be shifted to normal gregorian year | |
1182 | // but not if year was not specified, its already normal gregorian | |
1183 | // per the main if clause above. | |
1184 | year += cal.eras[( era || 0 )].offset; | |
1185 | } | |
1186 | // set default day and month to 1 and January, so if unspecified, these are the defaults | |
1187 | // instead of the current day/month. | |
1188 | if ( month === null ) { | |
1189 | month = 0; | |
1190 | } | |
1191 | if ( date === null ) { | |
1192 | date = 1; | |
1193 | } | |
1194 | // now have year, month, and date, but in the culture's calendar. | |
1195 | // convert to gregorian if necessary | |
1196 | if ( convert ) { | |
1197 | result = convert.toGregorian( year, month, date ); | |
1198 | // conversion failed, must be an invalid match | |
1199 | if ( result === null ) return null; | |
1200 | } | |
1201 | else { | |
1202 | // have to set year, month and date together to avoid overflow based on current date. | |
1203 | result.setFullYear( year, month, date ); | |
1204 | // check to see if date overflowed for specified month (only checked 1-31 above). | |
1205 | if ( result.getDate() !== date ) return null; | |
1206 | // invalid day of week. | |
1207 | if ( weekDay !== null && result.getDay() !== weekDay ) { | |
1208 | return null; | |
1209 | } | |
1210 | } | |
1211 | // if pm designator token was found make sure the hours fit the 24-hour clock. | |
1212 | if ( pmHour && hour < 12 ) { | |
1213 | hour += 12; | |
1214 | } | |
1215 | result.setHours( hour, min, sec, msec ); | |
1216 | if ( tzMinOffset !== null ) { | |
1217 | // adjust timezone to utc before applying local offset. | |
1218 | var adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() ); | |
1219 | // Safari limits hours and minutes to the range of -127 to 127. We need to use setHours | |
1220 | // to ensure both these fields will not exceed this range. adjustedMin will range | |
1221 | // somewhere between -1440 and 1500, so we only need to split this into hours. | |
1222 | result.setHours( result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60 ); | |
1223 | } | |
1224 | return result; | |
1225 | }; | |
1226 | }()); | |
1227 | ||
1228 | parseNegativePattern = function( value, nf, negativePattern ) { | |
1229 | var neg = nf[ "-" ], | |
1230 | pos = nf[ "+" ], | |
1231 | ret; | |
1232 | switch ( negativePattern ) { | |
1233 | case "n -": | |
1234 | neg = " " + neg; | |
1235 | pos = " " + pos; | |
1236 | /* falls through */ | |
1237 | case "n-": | |
1238 | if ( endsWith(value, neg) ) { | |
1239 | ret = [ "-", value.substr(0, value.length - neg.length) ]; | |
1240 | } | |
1241 | else if ( endsWith(value, pos) ) { | |
1242 | ret = [ "+", value.substr(0, value.length - pos.length) ]; | |
1243 | } | |
1244 | break; | |
1245 | case "- n": | |
1246 | neg += " "; | |
1247 | pos += " "; | |
1248 | /* falls through */ | |
1249 | case "-n": | |
1250 | if ( startsWith(value, neg) ) { | |
1251 | ret = [ "-", value.substr(neg.length) ]; | |
1252 | } | |
1253 | else if ( startsWith(value, pos) ) { | |
1254 | ret = [ "+", value.substr(pos.length) ]; | |
1255 | } | |
1256 | break; | |
1257 | case "(n)": | |
1258 | if ( startsWith(value, "(") && endsWith(value, ")") ) { | |
1259 | ret = [ "-", value.substr(1, value.length - 2) ]; | |
1260 | } | |
1261 | break; | |
1262 | } | |
1263 | return ret || [ "", value ]; | |
1264 | }; | |
1265 | ||
1266 | // | |
1267 | // public instance functions | |
1268 | // | |
1269 | ||
1270 | Globalize.prototype.findClosestCulture = function( cultureSelector ) { | |
1271 | return Globalize.findClosestCulture.call( this, cultureSelector ); | |
1272 | }; | |
1273 | ||
1274 | Globalize.prototype.format = function( value, format, cultureSelector ) { | |
1275 | return Globalize.format.call( this, value, format, cultureSelector ); | |
1276 | }; | |
1277 | ||
1278 | Globalize.prototype.localize = function( key, cultureSelector ) { | |
1279 | return Globalize.localize.call( this, key, cultureSelector ); | |
1280 | }; | |
1281 | ||
1282 | Globalize.prototype.parseInt = function( value, radix, cultureSelector ) { | |
1283 | return Globalize.parseInt.call( this, value, radix, cultureSelector ); | |
1284 | }; | |
1285 | ||
1286 | Globalize.prototype.parseFloat = function( value, radix, cultureSelector ) { | |
1287 | return Globalize.parseFloat.call( this, value, radix, cultureSelector ); | |
1288 | }; | |
1289 | ||
1290 | Globalize.prototype.culture = function( cultureSelector ) { | |
1291 | return Globalize.culture.call( this, cultureSelector ); | |
1292 | }; | |
1293 | ||
1294 | // | |
1295 | // public singleton functions | |
1296 | // | |
1297 | ||
1298 | Globalize.addCultureInfo = function( cultureName, baseCultureName, info ) { | |
1299 | ||
1300 | var base = {}, | |
1301 | isNew = false; | |
1302 | ||
1303 | if ( typeof cultureName !== "string" ) { | |
1304 | // cultureName argument is optional string. If not specified, assume info is first | |
1305 | // and only argument. Specified info deep-extends current culture. | |
1306 | info = cultureName; | |
1307 | cultureName = this.culture().name; | |
1308 | base = this.cultures[ cultureName ]; | |
1309 | } else if ( typeof baseCultureName !== "string" ) { | |
1310 | // baseCultureName argument is optional string. If not specified, assume info is second | |
1311 | // argument. Specified info deep-extends specified culture. | |
1312 | // If specified culture does not exist, create by deep-extending default | |
1313 | info = baseCultureName; | |
1314 | isNew = ( this.cultures[ cultureName ] == null ); | |
1315 | base = this.cultures[ cultureName ] || this.cultures[ "default" ]; | |
1316 | } else { | |
1317 | // cultureName and baseCultureName specified. Assume a new culture is being created | |
1318 | // by deep-extending an specified base culture | |
1319 | isNew = true; | |
1320 | base = this.cultures[ baseCultureName ]; | |
1321 | } | |
1322 | ||
1323 | this.cultures[ cultureName ] = extend(true, {}, | |
1324 | base, | |
1325 | info | |
1326 | ); | |
1327 | // Make the standard calendar the current culture if it's a new culture | |
1328 | if ( isNew ) { | |
1329 | this.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard; | |
1330 | } | |
1331 | }; | |
1332 | ||
1333 | Globalize.findClosestCulture = function( name ) { | |
1334 | var match; | |
1335 | if ( !name ) { | |
1336 | return this.findClosestCulture( this.cultureSelector ) || this.cultures[ "default" ]; | |
1337 | } | |
1338 | if ( typeof name === "string" ) { | |
1339 | name = name.split( "," ); | |
1340 | } | |
1341 | if ( isArray(name) ) { | |
1342 | var lang, | |
1343 | cultures = this.cultures, | |
1344 | list = name, | |
1345 | i, l = list.length, | |
1346 | prioritized = []; | |
1347 | for ( i = 0; i < l; i++ ) { | |
1348 | name = trim( list[i] ); | |
1349 | var pri, parts = name.split( ";" ); | |
1350 | lang = trim( parts[0] ); | |
1351 | if ( parts.length === 1 ) { | |
1352 | pri = 1; | |
1353 | } | |
1354 | else { | |
1355 | name = trim( parts[1] ); | |
1356 | if ( name.indexOf("q=") === 0 ) { | |
1357 | name = name.substr( 2 ); | |
1358 | pri = parseFloat( name ); | |
1359 | pri = isNaN( pri ) ? 0 : pri; | |
1360 | } | |
1361 | else { | |
1362 | pri = 1; | |
1363 | } | |
1364 | } | |
1365 | prioritized.push({ lang: lang, pri: pri }); | |
1366 | } | |
1367 | prioritized.sort(function( a, b ) { | |
1368 | if ( a.pri < b.pri ) { | |
1369 | return 1; | |
1370 | } else if ( a.pri > b.pri ) { | |
1371 | return -1; | |
1372 | } | |
1373 | return 0; | |
1374 | }); | |
1375 | // exact match | |
1376 | for ( i = 0; i < l; i++ ) { | |
1377 | lang = prioritized[ i ].lang; | |
1378 | match = cultures[ lang ]; | |
1379 | if ( match ) { | |
1380 | return match; | |
1381 | } | |
1382 | } | |
1383 | ||
1384 | // neutral language match | |
1385 | for ( i = 0; i < l; i++ ) { | |
1386 | lang = prioritized[ i ].lang; | |
1387 | do { | |
1388 | var index = lang.lastIndexOf( "-" ); | |
1389 | if ( index === -1 ) { | |
1390 | break; | |
1391 | } | |
1392 | // strip off the last part. e.g. en-US => en | |
1393 | lang = lang.substr( 0, index ); | |
1394 | match = cultures[ lang ]; | |
1395 | if ( match ) { | |
1396 | return match; | |
1397 | } | |
1398 | } | |
1399 | while ( 1 ); | |
1400 | } | |
1401 | ||
1402 | // last resort: match first culture using that language | |
1403 | for ( i = 0; i < l; i++ ) { | |
1404 | lang = prioritized[ i ].lang; | |
1405 | for ( var cultureKey in cultures ) { | |
1406 | var culture = cultures[ cultureKey ]; | |
1407 | if ( culture.language == lang ) { | |
1408 | return culture; | |
1409 | } | |
1410 | } | |
1411 | } | |
1412 | } | |
1413 | else if ( typeof name === "object" ) { | |
1414 | return name; | |
1415 | } | |
1416 | return match || null; | |
1417 | }; | |
1418 | ||
1419 | Globalize.format = function( value, format, cultureSelector ) { | |
1420 | var culture = this.findClosestCulture( cultureSelector ); | |
1421 | if ( value instanceof Date ) { | |
1422 | value = formatDate( value, format, culture ); | |
1423 | } | |
1424 | else if ( typeof value === "number" ) { | |
1425 | value = formatNumber( value, format, culture ); | |
1426 | } | |
1427 | return value; | |
1428 | }; | |
1429 | ||
1430 | Globalize.localize = function( key, cultureSelector ) { | |
1431 | return this.findClosestCulture( cultureSelector ).messages[ key ] || | |
1432 | this.cultures[ "default" ].messages[ key ]; | |
1433 | }; | |
1434 | ||
1435 | Globalize.parseDate = function( value, formats, culture ) { | |
1436 | culture = this.findClosestCulture( culture ); | |
1437 | ||
1438 | var date, prop, patterns; | |
1439 | if ( formats ) { | |
1440 | if ( typeof formats === "string" ) { | |
1441 | formats = [ formats ]; | |
1442 | } | |
1443 | if ( formats.length ) { | |
1444 | for ( var i = 0, l = formats.length; i < l; i++ ) { | |
1445 | var format = formats[ i ]; | |
1446 | if ( format ) { | |
1447 | date = parseExact( value, format, culture ); | |
1448 | if ( date ) { | |
1449 | break; | |
1450 | } | |
1451 | } | |
1452 | } | |
1453 | } | |
1454 | } else { | |
1455 | patterns = culture.calendar.patterns; | |
1456 | for ( prop in patterns ) { | |
1457 | date = parseExact( value, patterns[prop], culture ); | |
1458 | if ( date ) { | |
1459 | break; | |
1460 | } | |
1461 | } | |
1462 | } | |
1463 | ||
1464 | return date || null; | |
1465 | }; | |
1466 | ||
1467 | Globalize.parseInt = function( value, radix, cultureSelector ) { | |
1468 | return truncate( Globalize.parseFloat(value, radix, cultureSelector) ); | |
1469 | }; | |
1470 | ||
1471 | Globalize.parseFloat = function( value, radix, cultureSelector ) { | |
1472 | // radix argument is optional | |
1473 | if ( typeof radix !== "number" ) { | |
1474 | cultureSelector = radix; | |
1475 | radix = 10; | |
1476 | } | |
1477 | ||
1478 | var culture = this.findClosestCulture( cultureSelector ); | |
1479 | var ret = NaN, | |
1480 | nf = culture.numberFormat; | |
1481 | ||
1482 | if ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) { | |
1483 | // remove currency symbol | |
1484 | value = value.replace( culture.numberFormat.currency.symbol, "" ); | |
1485 | // replace decimal seperator | |
1486 | value = value.replace( culture.numberFormat.currency["."], culture.numberFormat["."] ); | |
1487 | } | |
1488 | ||
1489 | //Remove percentage character from number string before parsing | |
1490 | if ( value.indexOf(culture.numberFormat.percent.symbol) > -1){ | |
1491 | value = value.replace( culture.numberFormat.percent.symbol, "" ); | |
1492 | } | |
1493 | ||
1494 | // remove spaces: leading, trailing and between - and number. Used for negative currency pt-BR | |
1495 | value = value.replace( / /g, "" ); | |
1496 | ||
1497 | // allow infinity or hexidecimal | |
1498 | if ( regexInfinity.test(value) ) { | |
1499 | ret = parseFloat( value ); | |
1500 | } | |
1501 | else if ( !radix && regexHex.test(value) ) { | |
1502 | ret = parseInt( value, 16 ); | |
1503 | } | |
1504 | else { | |
1505 | ||
1506 | // determine sign and number | |
1507 | var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ), | |
1508 | sign = signInfo[ 0 ], | |
1509 | num = signInfo[ 1 ]; | |
1510 | ||
1511 | // #44 - try parsing as "(n)" | |
1512 | if ( sign === "" && nf.pattern[0] !== "(n)" ) { | |
1513 | signInfo = parseNegativePattern( value, nf, "(n)" ); | |
1514 | sign = signInfo[ 0 ]; | |
1515 | num = signInfo[ 1 ]; | |
1516 | } | |
1517 | ||
1518 | // try parsing as "-n" | |
1519 | if ( sign === "" && nf.pattern[0] !== "-n" ) { | |
1520 | signInfo = parseNegativePattern( value, nf, "-n" ); | |
1521 | sign = signInfo[ 0 ]; | |
1522 | num = signInfo[ 1 ]; | |
1523 | } | |
1524 | ||
1525 | sign = sign || "+"; | |
1526 | ||
1527 | // determine exponent and number | |
1528 | var exponent, | |
1529 | intAndFraction, | |
1530 | exponentPos = num.indexOf( "e" ); | |
1531 | if ( exponentPos < 0 ) exponentPos = num.indexOf( "E" ); | |
1532 | if ( exponentPos < 0 ) { | |
1533 | intAndFraction = num; | |
1534 | exponent = null; | |
1535 | } | |
1536 | else { | |
1537 | intAndFraction = num.substr( 0, exponentPos ); | |
1538 | exponent = num.substr( exponentPos + 1 ); | |
1539 | } | |
1540 | // determine decimal position | |
1541 | var integer, | |
1542 | fraction, | |
1543 | decSep = nf[ "." ], | |
1544 | decimalPos = intAndFraction.indexOf( decSep ); | |
1545 | if ( decimalPos < 0 ) { | |
1546 | integer = intAndFraction; | |
1547 | fraction = null; | |
1548 | } | |
1549 | else { | |
1550 | integer = intAndFraction.substr( 0, decimalPos ); | |
1551 | fraction = intAndFraction.substr( decimalPos + decSep.length ); | |
1552 | } | |
1553 | // handle groups (e.g. 1,000,000) | |
1554 | var groupSep = nf[ "," ]; | |
1555 | integer = integer.split( groupSep ).join( "" ); | |
1556 | var altGroupSep = groupSep.replace( /\u00A0/g, " " ); | |
1557 | if ( groupSep !== altGroupSep ) { | |
1558 | integer = integer.split( altGroupSep ).join( "" ); | |
1559 | } | |
1560 | // build a natively parsable number string | |
1561 | var p = sign + integer; | |
1562 | if ( fraction !== null ) { | |
1563 | p += "." + fraction; | |
1564 | } | |
1565 | if ( exponent !== null ) { | |
1566 | // exponent itself may have a number patternd | |
1567 | var expSignInfo = parseNegativePattern( exponent, nf, "-n" ); | |
1568 | p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ]; | |
1569 | } | |
1570 | if ( regexParseFloat.test(p) ) { | |
1571 | ret = parseFloat( p ); | |
1572 | } | |
1573 | } | |
1574 | return ret; | |
1575 | }; | |
1576 | ||
1577 | Globalize.culture = function( cultureSelector ) { | |
1578 | // setter | |
1579 | if ( typeof cultureSelector !== "undefined" ) { | |
1580 | this.cultureSelector = cultureSelector; | |
1581 | } | |
1582 | // getter | |
1583 | return this.findClosestCulture( cultureSelector ) || this.cultures[ "default" ]; | |
1584 | }; | |
1585 | ||
1586 | }( this )); |