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 13var 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) 42Globalize = function( cultureSelector ) { 43 return new Globalize.prototype.init( cultureSelector ); 44}; 45 46if ( 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 56Globalize.cultures = {}; 57 58Globalize.prototype = { 59 constructor: Globalize, 60 init: function( cultureSelector ) { 61 this.cultures = Globalize.cultures; 62 this.cultureSelector = cultureSelector; 63 64 return this; 65 } 66}; 67Globalize.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". 74Globalize.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", "
190November", "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 263Globalize.cultures[ "default" ].calendar = Globalize.cultures[ "default" ].calendars.standard; 264 265Globalize.cultures.en = Globalize.cultures[ "default" ]; 266 267Globalize.cultureSelector = "en"; 268 269// 270// private variables 271// 272 273regexHex = /^0x[a-f0-9]+$/i; 274regexInfinity = /^[+\-]?infinity$/i; 275regexParseFloat = /^[+\-]?\d*\.?\d*(e[+\-]?\d+)?$/; 276regexTrim = /^\s+|\s+$/g; 277 278// 279// private JavaScript utility functions 280// 281 282arrayIndexOf = 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 294endsWith = function( value, pattern ) { 295 return value.substr( value.length - pattern.length ) === pattern; 296}; 297 298extend = 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 356isArray = Array.isArray || function( obj ) { 357 return Object.prototype.toString.call( obj ) === "[object Array]"; 358}; 359 360isFunction = function( obj ) { 361 return Object.prototype.toString.call( obj ) === "[object Function]"; 362}; 363 364isObject = function( obj ) { 365 return Object.prototype.toString.call( obj ) === "[object Object]"; 366}; 367 368startsWith = function( value, pattern ) { 369 return value.indexOf( pattern ) === 0; 370}; 371 372trim = function( value ) { 373 return ( value + "" ).replace( regexTrim, "" ); 374}; 375 376truncate = function( value ) { 377 if ( isNaN( value ) ) { 378 return NaN; 379 } 380 return Math[ value < 0 ? "ceil" : "floor" ]( value ); 381}; 382 383zeroPad = 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 395appendPreOrPostMatch = 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 428expandFormat = 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 448formatDate = 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 841getTokenRegExp = function() { 842 // regular expression for matching date and time tokens in format str
842ings. 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 846getEra = 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 858getEraYear = 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 str
1051ing 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 1228parseNegativePattern = 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 1270Globalize.prototype.findClosestCulture = function( cultureSelector ) { 1271 return Globalize.findClosestCulture.call( this, cultureSelector ); 1272}; 1273 1274Globalize.prototype.format = function( value, format, cultureSelector ) { 1275 return Globalize.format.call( this, value, format, cultureSelector ); 1276}; 1277 1278Globalize.prototype.localize = function( key, cultureSelector ) { 1279 return Globalize.localize.call( this, key, cultureSelector ); 1280}; 1281 1282Globalize.prototype.parseInt = function( value, radix, cultureSelector ) { 1283 return Globalize.parseInt.call( this, value, radix, cultureSelector ); 1284}; 1285 1286Globalize.prototype.parseFloat = function( value, radix, cultureSelector ) { 1287 return Globalize.parseFloat.call( this, value, radix, cultureSelector ); 1288}; 1289 1290Globalize.prototype.culture = function( cultureSelector ) { 1291 return Globalize.culture.call( this, cultureSelector ); 1292}; 1293 1294// 1295// public singleton functions 1296// 1297 1298Globalize.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 1333Globalize.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 1419Globalize.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 1430Globalize.localize = function( key, cultureSelector ) { 1431 return this.findClosestCulture( cultureSelector ).messages[ key ] || 1432 this.cultures[ "default" ].messages[ key ]; 1433}; 1434 1435Globalize.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 1467Globalize.parseInt = function( value, radix, cultureSelector ) { 1468 return truncate( Globalize.parseFloat(value, radix, cultureSelector) ); 1469}; 1470 1471Globalize.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 1577Globalize.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 ));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.