1 // Copyright 2013 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 // limitations under the License. 28 29 // ECMAScript 402 API implementation. 30 31 /** 32 * Intl object is a single object that has some named properties, 33 * all of which are constructors. 34 */ 35 $Object.defineProperty(global, "Intl", { enumerable: false, value: (function() { 36 37 'use strict'; 38 39 var Intl = {}; 40 41 var undefined = global.undefined; 42 43 var AVAILABLE_SERVICES = ['collator', 44 'numberformat', 45 'dateformat', 46 'breakiterator']; 47 48 /** 49 * Caches available locales for each service. 50 */ 51 var AVAILABLE_LOCALES = { 52 'collator': undefined, 53 'numberformat': undefined, 54 'dateformat': undefined, 55 'breakiterator': undefined 56 }; 57 58 /** 59 * Caches default ICU locale. 60 */ 61 var DEFAULT_ICU_LOCALE = undefined; 62 63 /** 64 * Unicode extension regular expression. 65 */ 66 var UNICODE_EXTENSION_RE = undefined; 67 68 function GetUnicodeExtensionRE() { 69 if (UNICODE_EXTENSION_RE === undefined) { 70 UNICODE_EXTENSION_RE = new $RegExp('-u(-[a-z0-9]{2,8})+', 'g'); 71 } 72 return UNICODE_EXTENSION_RE; 73 } 74 75 /** 76 * Matches any Unicode extension. 77 */ 78 var ANY_EXTENSION_RE = undefined; 79 80 function GetAnyExtensionRE() { 81 if (ANY_EXTENSION_RE === undefined) { 82 ANY_EXTENSION_RE = new $RegExp('-[a-z0-9]{1}-.*', 'g'); 83 } 84 return ANY_EXTENSION_RE; 85 } 86 87 /** 88 * Replace quoted text (single quote, anything but the quote and quote again). 89 */ 90 var QUOTED_STRING_RE = undefined; 91 92 function GetQuotedStringRE() { 93 if (QUOTED_STRING_RE === undefined) { 94 QUOTED_STRING_RE = new $RegExp("'[^']+'", 'g'); 95 } 96 return QUOTED_STRING_RE; 97 } 98 99 /** 100 * Matches valid service name. 101 */ 102 var SERVICE_RE = undefined; 103 104 function GetServiceRE() { 105 if (SERVICE_RE === undefined) { 106 SERVICE_RE = 107 new $RegExp('^(collator|numberformat|dateformat|breakiterator)$'); 108 } 109 return SERVICE_RE; 110 } 111 112 /** 113 * Validates a language tag against bcp47 spec. 114 * Actual value is assigned on first run. 115 */ 116 var LANGUAGE_TAG_RE = undefined; 117 118 function GetLanguageTagRE() { 119 if (LANGUAGE_TAG_RE === undefined) { 120 BuildLanguageTagREs(); 121 } 122 return LANGUAGE_TAG_RE; 123 } 124 125 /** 126 * Helps find duplicate variants in the language tag. 127 */ 128 var LANGUAGE_VARIANT_RE = undefined; 129 130 function GetLanguageVariantRE() { 131 if (LANGUAGE_VARIANT_RE === undefined) { 132 BuildLanguageTagREs(); 133 } 134 return LANGUAGE_VARIANT_RE; 135 } 136 137 /** 138 * Helps find duplicate singletons in the language tag. 139 */ 140 var LANGUAGE_SINGLETON_RE = undefined; 141 142 function GetLanguageSingletonRE() { 143 if (LANGUAGE_SINGLETON_RE === undefined) { 144 BuildLanguageTagREs(); 145 } 146 return LANGUAGE_SINGLETON_RE; 147 } 148 149 /** 150 * Matches valid IANA time zone names. 151 */ 152 var TIMEZONE_NAME_CHECK_RE = undefined; 153 154 function GetTimezoneNameCheckRE() { 155 if (TIMEZONE_NAME_CHECK_RE === undefined) { 156 TIMEZONE_NAME_CHECK_RE = 157 new $RegExp('^([A-Za-z]+)/([A-Za-z]+)(?:_([A-Za-z]+))*$'); 158 } 159 return TIMEZONE_NAME_CHECK_RE; 160 } 161 162 /** 163 * Maps ICU calendar names into LDML type. 164 */ 165 var ICU_CALENDAR_MAP = { 166 'gregorian': 'gregory', 167 'japanese': 'japanese', 168 'buddhist': 'buddhist', 169 'roc': 'roc', 170 'persian': 'persian', 171 'islamic-civil': 'islamicc', 172 'islamic': 'islamic', 173 'hebrew': 'hebrew', 174 'chinese': 'chinese', 175 'indian': 'indian', 176 'coptic': 'coptic', 177 'ethiopic': 'ethiopic', 178 'ethiopic-amete-alem': 'ethioaa' 179 }; 180 181 /** 182 * Map of Unicode extensions to option properties, and their values and types, 183 * for a collator. 184 */ 185 var COLLATOR_KEY_MAP = { 186 'kn': {'property': 'numeric', 'type': 'boolean'}, 187 'kf': {'property': 'caseFirst', 'type': 'string', 188 'values': ['false', 'lower', 'upper']} 189 }; 190 191 /** 192 * Map of Unicode extensions to option properties, and their values and types, 193 * for a number format. 194 */ 195 var NUMBER_FORMAT_KEY_MAP = { 196 'nu': {'property': undefined, 'type': 'string'} 197 }; 198 199 /** 200 * Map of Unicode extensions to option properties, and their values and types, 201 * for a date/time format. 202 */ 203 var DATETIME_FORMAT_KEY_MAP = { 204 'ca': {'property': undefined, 'type': 'string'}, 205 'nu': {'property': undefined, 'type': 'string'} 206 }; 207 208 /** 209 * Allowed -u-co- values. List taken from: 210 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml 211 */ 212 var ALLOWED_CO_VALUES = [ 213 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic', 214 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin' 215 ]; 216 217 /** 218 * Error message for when function object is created with new and it's not 219 * a constructor. 220 */ 221 var ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR = 222 'Function object that\'s not a constructor was created with new'; 223 224 225 /** 226 * Adds bound method to the prototype of the given object. 227 */ 228 function addBoundMethod(obj, methodName, implementation, length) { 229 function getter() { 230 if (!this || typeof this !== 'object' || 231 this.__initializedIntlObject === undefined) { 232 throw new $TypeError('Method ' + methodName + ' called on a ' + 233 'non-object or on a wrong type of object.'); 234 } 235 var internalName = '__bound' + methodName + '__'; 236 if (this[internalName] === undefined) { 237 var that = this; 238 var boundMethod; 239 if (length === undefined || length === 2) { 240 boundMethod = function(x, y) { 241 if (%_IsConstructCall()) { 242 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 243 } 244 return implementation(that, x, y); 245 } 246 } else if (length === 1) { 247 boundMethod = function(x) { 248 if (%_IsConstructCall()) { 249 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 250 } 251 return implementation(that, x); 252 } 253 } else { 254 boundMethod = function() { 255 if (%_IsConstructCall()) { 256 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 257 } 258 // DateTimeFormat.format needs to be 0 arg method, but can stil 259 // receive optional dateValue param. If one was provided, pass it 260 // along. 261 if (%_ArgumentsLength() > 0) { 262 return implementation(that, %_Arguments(0)); 263 } else { 264 return implementation(that); 265 } 266 } 267 } 268 %FunctionSetName(boundMethod, internalName); 269 %FunctionRemovePrototype(boundMethod); 270 %SetNativeFlag(boundMethod); 271 this[internalName] = boundMethod; 272 } 273 return this[internalName]; 274 } 275 276 %FunctionSetName(getter, methodName); 277 %FunctionRemovePrototype(getter); 278 %SetNativeFlag(getter); 279 280 $Object.defineProperty(obj.prototype, methodName, { 281 get: getter, 282 enumerable: false, 283 configurable: true 284 }); 285 } 286 287 288 /** 289 * Returns an intersection of locales and service supported locales. 290 * Parameter locales is treated as a priority list. 291 */ 292 function supportedLocalesOf(service, locales, options) { 293 if (IS_NULL(service.match(GetServiceRE()))) { 294 throw new $Error('Internal error, wrong service type: ' + service); 295 } 296 297 // Provide defaults if matcher was not specified. 298 if (options === undefined) { 299 options = {}; 300 } else { 301 options = toObject(options); 302 } 303 304 var matcher = options.localeMatcher; 305 if (matcher !== undefined) { 306 matcher = $String(matcher); 307 if (matcher !== 'lookup' && matcher !== 'best fit') { 308 throw new $RangeError('Illegal value for localeMatcher:' + matcher); 309 } 310 } else { 311 matcher = 'best fit'; 312 } 313 314 var requestedLocales = initializeLocaleList(locales); 315 316 // Cache these, they don't ever change per service. 317 if (AVAILABLE_LOCALES[service] === undefined) { 318 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); 319 } 320 321 // Use either best fit or lookup algorithm to match locales. 322 if (matcher === 'best fit') { 323 return initializeLocaleList(bestFitSupportedLocalesOf( 324 requestedLocales, AVAILABLE_LOCALES[service])); 325 } 326 327 return initializeLocaleList(lookupSupportedLocalesOf( 328 requestedLocales, AVAILABLE_LOCALES[service])); 329 } 330 331 332 /** 333 * Returns the subset of the provided BCP 47 language priority list for which 334 * this service has a matching locale when using the BCP 47 Lookup algorithm. 335 * Locales appear in the same order in the returned list as in the input list. 336 */ 337 function lookupSupportedLocalesOf(requestedLocales, availableLocales) { 338 var matchedLocales = []; 339 for (var i = 0; i < requestedLocales.length; ++i) { 340 // Remove -u- extension. 341 var locale = requestedLocales[i].replace(GetUnicodeExtensionRE(), ''); 342 do { 343 if (availableLocales[locale] !== undefined) { 344 // Push requested locale not the resolved one. 345 matchedLocales.push(requestedLocales[i]); 346 break; 347 } 348 // Truncate locale if possible, if not break. 349 var pos = locale.lastIndexOf('-'); 350 if (pos === -1) { 351 break; 352 } 353 locale = locale.substring(0, pos); 354 } while (true); 355 } 356 357 return matchedLocales; 358 } 359 360 361 /** 362 * Returns the subset of the provided BCP 47 language priority list for which 363 * this service has a matching locale when using the implementation 364 * dependent algorithm. 365 * Locales appear in the same order in the returned list as in the input list. 366 */ 367 function bestFitSupportedLocalesOf(requestedLocales, availableLocales) { 368 // TODO(cira): implement better best fit algorithm. 369 return lookupSupportedLocalesOf(requestedLocales, availableLocales); 370 } 371 372 373 /** 374 * Returns a getOption function that extracts property value for given 375 * options object. If property is missing it returns defaultValue. If value 376 * is out of range for that property it throws RangeError. 377 */ 378 function getGetOption(options, caller) { 379 if (options === undefined) { 380 throw new $Error('Internal ' + caller + ' error. ' + 381 'Default options are missing.'); 382 } 383 384 var getOption = function getOption(property, type, values, defaultValue) { 385 if (options[property] !== undefined) { 386 var value = options[property]; 387 switch (type) { 388 case 'boolean': 389 value = $Boolean(value); 390 break; 391 case 'string': 392 value = $String(value); 393 break; 394 case 'number': 395 value = $Number(value); 396 break; 397 default: 398 throw new $Error('Internal error. Wrong value type.'); 399 } 400 if (values !== undefined && values.indexOf(value) === -1) { 401 throw new $RangeError('Value ' + value + ' out of range for ' + caller + 402 ' options property ' + property); 403 } 404 405 return value; 406 } 407 408 return defaultValue; 409 } 410 411 return getOption; 412 } 413 414 415 /** 416 * Compares a BCP 47 language priority list requestedLocales against the locales 417 * in availableLocales and determines the best available language to meet the 418 * request. Two algorithms are available to match the locales: the Lookup 419 * algorithm described in RFC 4647 section 3.4, and an implementation dependent 420 * best-fit algorithm. Independent of the locale matching algorithm, options 421 * specified through Unicode locale extension sequences are negotiated 422 * separately, taking the caller's relevant extension keys and locale data as 423 * well as client-provided options into consideration. Returns an object with 424 * a locale property whose value is the language tag of the selected locale, 425 * and properties for each key in relevantExtensionKeys providing the selected 426 * value for that key. 427 */ 428 function resolveLocale(service, requestedLocales, options) { 429 requestedLocales = initializeLocaleList(requestedLocales); 430 431 var getOption = getGetOption(options, service); 432 var matcher = getOption('localeMatcher', 'string', 433 ['lookup', 'best fit'], 'best fit'); 434 var resolved; 435 if (matcher === 'lookup') { 436 resolved = lookupMatcher(service, requestedLocales); 437 } else { 438 resolved = bestFitMatcher(service, requestedLocales); 439 } 440 441 return resolved; 442 } 443 444 445 /** 446 * Returns best matched supported locale and extension info using basic 447 * lookup algorithm. 448 */ 449 function lookupMatcher(service, requestedLocales) { 450 if (IS_NULL(service.match(GetServiceRE()))) { 451 throw new $Error('Internal error, wrong service type: ' + service); 452 } 453 454 // Cache these, they don't ever change per service. 455 if (AVAILABLE_LOCALES[service] === undefined) { 456 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service); 457 } 458 459 for (var i = 0; i < requestedLocales.length; ++i) { 460 // Remove all extensions. 461 var locale = requestedLocales[i].replace(GetAnyExtensionRE(), ''); 462 do { 463 if (AVAILABLE_LOCALES[service][locale] !== undefined) { 464 // Return the resolved locale and extension. 465 var extensionMatch = requestedLocales[i].match(GetUnicodeExtensionRE()); 466 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0]; 467 return {'locale': locale, 'extension': extension, 'position': i}; 468 } 469 // Truncate locale if possible. 470 var pos = locale.lastIndexOf('-'); 471 if (pos === -1) { 472 break; 473 } 474 locale = locale.substring(0, pos); 475 } while (true); 476 } 477 478 // Didn't find a match, return default. 479 if (DEFAULT_ICU_LOCALE === undefined) { 480 DEFAULT_ICU_LOCALE = %GetDefaultICULocale(); 481 } 482 483 return {'locale': DEFAULT_ICU_LOCALE, 'extension': '', 'position': -1}; 484 } 485 486 487 /** 488 * Returns best matched supported locale and extension info using 489 * implementation dependend algorithm. 490 */ 491 function bestFitMatcher(service, requestedLocales) { 492 // TODO(cira): implement better best fit algorithm. 493 return lookupMatcher(service, requestedLocales); 494 } 495 496 497 /** 498 * Parses Unicode extension into key - value map. 499 * Returns empty object if the extension string is invalid. 500 * We are not concerned with the validity of the values at this point. 501 */ 502 function parseExtension(extension) { 503 var extensionSplit = extension.split('-'); 504 505 // Assume ['', 'u', ...] input, but don't throw. 506 if (extensionSplit.length <= 2 || 507 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) { 508 return {}; 509 } 510 511 // Key is {2}alphanum, value is {3,8}alphanum. 512 // Some keys may not have explicit values (booleans). 513 var extensionMap = {}; 514 var previousKey = undefined; 515 for (var i = 2; i < extensionSplit.length; ++i) { 516 var length = extensionSplit[i].length; 517 var element = extensionSplit[i]; 518 if (length === 2) { 519 extensionMap[element] = undefined; 520 previousKey = element; 521 } else if (length >= 3 && length <=8 && previousKey !== undefined) { 522 extensionMap[previousKey] = element; 523 previousKey = undefined; 524 } else { 525 // There is a value that's too long, or that doesn't have a key. 526 return {}; 527 } 528 } 529 530 return extensionMap; 531 } 532 533 534 /** 535 * Converts parameter to an Object if possible. 536 */ 537 function toObject(value) { 538 if (IS_NULL_OR_UNDEFINED(value)) { 539 throw new $TypeError('Value cannot be converted to an Object.'); 540 } 541 542 return $Object(value); 543 } 544 545 546 /** 547 * Populates internalOptions object with boolean key-value pairs 548 * from extensionMap and options. 549 * Returns filtered extension (number and date format constructors use 550 * Unicode extensions for passing parameters to ICU). 551 * It's used for extension-option pairs only, e.g. kn-normalization, but not 552 * for 'sensitivity' since it doesn't have extension equivalent. 553 * Extensions like nu and ca don't have options equivalent, so we place 554 * undefined in the map.property to denote that. 555 */ 556 function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) { 557 var extension = ''; 558 559 var updateExtension = function updateExtension(key, value) { 560 return '-' + key + '-' + $String(value); 561 } 562 563 var updateProperty = function updateProperty(property, type, value) { 564 if (type === 'boolean' && (typeof value === 'string')) { 565 value = (value === 'true') ? true : false; 566 } 567 568 if (property !== undefined) { 569 defineWEProperty(outOptions, property, value); 570 } 571 } 572 573 for (var key in keyValues) { 574 if (keyValues.hasOwnProperty(key)) { 575 var value = undefined; 576 var map = keyValues[key]; 577 if (map.property !== undefined) { 578 // This may return true if user specifies numeric: 'false', since 579 // Boolean('nonempty') === true. 580 value = getOption(map.property, map.type, map.values); 581 } 582 if (value !== undefined) { 583 updateProperty(map.property, map.type, value); 584 extension += updateExtension(key, value); 585 continue; 586 } 587 // User options didn't have it, check Unicode extension. 588 // Here we want to convert strings 'true', 'false' into proper Boolean 589 // values (not a user error). 590 if (extensionMap.hasOwnProperty(key)) { 591 value = extensionMap[key]; 592 if (value !== undefined) { 593 updateProperty(map.property, map.type, value); 594 extension += updateExtension(key, value); 595 } else if (map.type === 'boolean') { 596 // Boolean keys are allowed not to have values in Unicode extension. 597 // Those default to true. 598 updateProperty(map.property, map.type, true); 599 extension += updateExtension(key, true); 600 } 601 } 602 } 603 } 604 605 return extension === ''? '' : '-u' + extension; 606 } 607 608 609 /** 610 * Converts all OwnProperties into 611 * configurable: false, writable: false, enumerable: true. 612 */ 613 function freezeArray(array) { 614 array.forEach(function(element, index) { 615 $Object.defineProperty(array, index, {value: element, 616 configurable: false, 617 writable: false, 618 enumerable: true}); 619 }); 620 621 $Object.defineProperty(array, 'length', {value: array.length, 622 writable: false}); 623 624 return array; 625 } 626 627 628 /** 629 * It's sometimes desireable to leave user requested locale instead of ICU 630 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter 631 * one, if that was what user requested). 632 * This function returns user specified tag if its maximized form matches ICU 633 * resolved locale. If not we return ICU result. 634 */ 635 function getOptimalLanguageTag(original, resolved) { 636 // Returns Array<Object>, where each object has maximized and base properties. 637 // Maximized: zh -> zh-Hans-CN 638 // Base: zh-CN-u-ca-gregory -> zh-CN 639 // Take care of grandfathered or simple cases. 640 if (original === resolved) { 641 return original; 642 } 643 644 var locales = %GetLanguageTagVariants([original, resolved]); 645 if (locales[0].maximized !== locales[1].maximized) { 646 return resolved; 647 } 648 649 // Preserve extensions of resolved locale, but swap base tags with original. 650 var resolvedBase = new $RegExp('^' + locales[1].base); 651 return resolved.replace(resolvedBase, locales[0].base); 652 } 653 654 655 /** 656 * Returns an Object that contains all of supported locales for a given 657 * service. 658 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ 659 * that is supported. This is required by the spec. 660 */ 661 function getAvailableLocalesOf(service) { 662 var available = %AvailableLocalesOf(service); 663 664 for (var i in available) { 665 if (available.hasOwnProperty(i)) { 666 var parts = i.match(/^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/); 667 if (parts !== null) { 668 // Build xx-ZZ. We don't care about the actual value, 669 // as long it's not undefined. 670 available[parts[1] + '-' + parts[3]] = null; 671 } 672 } 673 } 674 675 return available; 676 } 677 678 679 /** 680 * Defines a property and sets writable and enumerable to true. 681 * Configurable is false by default. 682 */ 683 function defineWEProperty(object, property, value) { 684 $Object.defineProperty(object, property, 685 {value: value, writable: true, enumerable: true}); 686 } 687 688 689 /** 690 * Adds property to an object if the value is not undefined. 691 * Sets configurable descriptor to false. 692 */ 693 function addWEPropertyIfDefined(object, property, value) { 694 if (value !== undefined) { 695 defineWEProperty(object, property, value); 696 } 697 } 698 699 700 /** 701 * Defines a property and sets writable, enumerable and configurable to true. 702 */ 703 function defineWECProperty(object, property, value) { 704 $Object.defineProperty(object, property, 705 {value: value, 706 writable: true, 707 enumerable: true, 708 configurable: true}); 709 } 710 711 712 /** 713 * Adds property to an object if the value is not undefined. 714 * Sets all descriptors to true. 715 */ 716 function addWECPropertyIfDefined(object, property, value) { 717 if (value !== undefined) { 718 defineWECProperty(object, property, value); 719 } 720 } 721 722 723 /** 724 * Returns titlecased word, aMeRricA -> America. 725 */ 726 function toTitleCaseWord(word) { 727 return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase(); 728 } 729 730 /** 731 * Canonicalizes the language tag, or throws in case the tag is invalid. 732 */ 733 function canonicalizeLanguageTag(localeID) { 734 // null is typeof 'object' so we have to do extra check. 735 if (typeof localeID !== 'string' && typeof localeID !== 'object' || 736 IS_NULL(localeID)) { 737 throw new $TypeError('Language ID should be string or object.'); 738 } 739 740 var localeString = $String(localeID); 741 742 if (isValidLanguageTag(localeString) === false) { 743 throw new $RangeError('Invalid language tag: ' + localeString); 744 } 745 746 // This call will strip -kn but not -kn-true extensions. 747 // ICU bug filled - http://bugs.icu-project.org/trac/ticket/9265. 748 // TODO(cira): check if -u-kn-true-kc-true-kh-true still throws after 749 // upgrade to ICU 4.9. 750 var tag = %CanonicalizeLanguageTag(localeString); 751 if (tag === 'invalid-tag') { 752 throw new $RangeError('Invalid language tag: ' + localeString); 753 } 754 755 return tag; 756 } 757 758 759 /** 760 * Returns an array where all locales are canonicalized and duplicates removed. 761 * Throws on locales that are not well formed BCP47 tags. 762 */ 763 function initializeLocaleList(locales) { 764 var seen = []; 765 if (locales === undefined) { 766 // Constructor is called without arguments. 767 seen = []; 768 } else { 769 // We allow single string localeID. 770 if (typeof locales === 'string') { 771 seen.push(canonicalizeLanguageTag(locales)); 772 return freezeArray(seen); 773 } 774 775 var o = toObject(locales); 776 // Converts it to UInt32 (>>> is shr on 32bit integers). 777 var len = o.length >>> 0; 778 779 for (var k = 0; k < len; k++) { 780 if (k in o) { 781 var value = o[k]; 782 783 var tag = canonicalizeLanguageTag(value); 784 785 if (seen.indexOf(tag) === -1) { 786 seen.push(tag); 787 } 788 } 789 } 790 } 791 792 return freezeArray(seen); 793 } 794 795 796 /** 797 * Validates the language tag. Section 2.2.9 of the bcp47 spec 798 * defines a valid tag. 799 * 800 * ICU is too permissible and lets invalid tags, like 801 * hant-cmn-cn, through. 802 * 803 * Returns false if the language tag is invalid. 804 */ 805 function isValidLanguageTag(locale) { 806 // Check if it's well-formed, including grandfadered tags. 807 if (GetLanguageTagRE().test(locale) === false) { 808 return false; 809 } 810 811 // Just return if it's a x- form. It's all private. 812 if (locale.indexOf('x-') === 0) { 813 return true; 814 } 815 816 // Check if there are any duplicate variants or singletons (extensions). 817 818 // Remove private use section. 819 locale = locale.split(/-x-/)[0]; 820 821 // Skip language since it can match variant regex, so we start from 1. 822 // We are matching i-klingon here, but that's ok, since i-klingon-klingon 823 // is not valid and would fail LANGUAGE_TAG_RE test. 824 var variants = []; 825 var extensions = []; 826 var parts = locale.split(/-/); 827 for (var i = 1; i < parts.length; i++) { 828 var value = parts[i]; 829 if (GetLanguageVariantRE().test(value) === true && extensions.length === 0) { 830 if (variants.indexOf(value) === -1) { 831 variants.push(value); 832 } else { 833 return false; 834 } 835 } 836 837 if (GetLanguageSingletonRE().test(value) === true) { 838 if (extensions.indexOf(value) === -1) { 839 extensions.push(value); 840 } else { 841 return false; 842 } 843 } 844 } 845 846 return true; 847 } 848 849 850 /** 851 * Builds a regular expresion that validates the language tag 852 * against bcp47 spec. 853 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF. 854 * Runs on load and initializes the global REs. 855 */ 856 function BuildLanguageTagREs() { 857 var alpha = '[a-zA-Z]'; 858 var digit = '[0-9]'; 859 var alphanum = '(' + alpha + '|' + digit + ')'; 860 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' + 861 'zh-min|zh-min-nan|zh-xiang)'; 862 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' + 863 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' + 864 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)'; 865 var grandfathered = '(' + irregular + '|' + regular + ')'; 866 var privateUse = '(x(-' + alphanum + '{1,8})+)'; 867 868 var singleton = '(' + digit + '|[A-WY-Za-wy-z])'; 869 LANGUAGE_SINGLETON_RE = new $RegExp('^' + singleton + '$', 'i'); 870 871 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)'; 872 873 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))'; 874 LANGUAGE_VARIANT_RE = new $RegExp('^' + variant + '$', 'i'); 875 876 var region = '(' + alpha + '{2}|' + digit + '{3})'; 877 var script = '(' + alpha + '{4})'; 878 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})'; 879 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' + 880 alpha + '{5,8})'; 881 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' + 882 variant + ')*(-' + extension + ')*(-' + privateUse + ')?'; 883 884 var languageTag = 885 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$'; 886 LANGUAGE_TAG_RE = new $RegExp(languageTag, 'i'); 887 } 888 889 /** 890 * Initializes the given object so it's a valid Collator instance. 891 * Useful for subclassing. 892 */ 893 function initializeCollator(collator, locales, options) { 894 if (collator.hasOwnProperty('__initializedIntlObject')) { 895 throw new $TypeError('Trying to re-initialize Collator object.'); 896 } 897 898 if (options === undefined) { 899 options = {}; 900 } 901 902 var getOption = getGetOption(options, 'collator'); 903 904 var internalOptions = {}; 905 906 defineWEProperty(internalOptions, 'usage', getOption( 907 'usage', 'string', ['sort', 'search'], 'sort')); 908 909 var sensitivity = getOption('sensitivity', 'string', 910 ['base', 'accent', 'case', 'variant']); 911 if (sensitivity === undefined && internalOptions.usage === 'sort') { 912 sensitivity = 'variant'; 913 } 914 defineWEProperty(internalOptions, 'sensitivity', sensitivity); 915 916 defineWEProperty(internalOptions, 'ignorePunctuation', getOption( 917 'ignorePunctuation', 'boolean', undefined, false)); 918 919 var locale = resolveLocale('collator', locales, options); 920 921 // ICU can't take kb, kc... parameters through localeID, so we need to pass 922 // them as options. 923 // One exception is -co- which has to be part of the extension, but only for 924 // usage: sort, and its value can't be 'standard' or 'search'. 925 var extensionMap = parseExtension(locale.extension); 926 setOptions( 927 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions); 928 929 var collation = 'default'; 930 var extension = ''; 931 if (extensionMap.hasOwnProperty('co') && internalOptions.usage === 'sort') { 932 if (ALLOWED_CO_VALUES.indexOf(extensionMap.co) !== -1) { 933 extension = '-u-co-' + extensionMap.co; 934 // ICU can't tell us what the collation is, so save user's input. 935 collation = extensionMap.co; 936 } 937 } else if (internalOptions.usage === 'search') { 938 extension = '-u-co-search'; 939 } 940 defineWEProperty(internalOptions, 'collation', collation); 941 942 var requestedLocale = locale.locale + extension; 943 944 // We define all properties C++ code may produce, to prevent security 945 // problems. If malicious user decides to redefine Object.prototype.locale 946 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us"). 947 // Object.defineProperties will either succeed defining or throw an error. 948 var resolved = $Object.defineProperties({}, { 949 caseFirst: {writable: true}, 950 collation: {value: internalOptions.collation, writable: true}, 951 ignorePunctuation: {writable: true}, 952 locale: {writable: true}, 953 numeric: {writable: true}, 954 requestedLocale: {value: requestedLocale, writable: true}, 955 sensitivity: {writable: true}, 956 strength: {writable: true}, 957 usage: {value: internalOptions.usage, writable: true} 958 }); 959 960 var internalCollator = %CreateCollator(requestedLocale, 961 internalOptions, 962 resolved); 963 964 // Writable, configurable and enumerable are set to false by default. 965 $Object.defineProperty(collator, 'collator', {value: internalCollator}); 966 $Object.defineProperty(collator, '__initializedIntlObject', 967 {value: 'collator'}); 968 $Object.defineProperty(collator, 'resolved', {value: resolved}); 969 970 return collator; 971 } 972 973 974 /** 975 * Constructs Intl.Collator object given optional locales and options 976 * parameters. 977 * 978 * @constructor 979 */ 980 %SetProperty(Intl, 'Collator', function() { 981 var locales = %_Arguments(0); 982 var options = %_Arguments(1); 983 984 if (!this || this === Intl) { 985 // Constructor is called as a function. 986 return new Intl.Collator(locales, options); 987 } 988 989 return initializeCollator(toObject(this), locales, options); 990 }, 991 DONT_ENUM 992 ); 993 994 995 /** 996 * Collator resolvedOptions method. 997 */ 998 %SetProperty(Intl.Collator.prototype, 'resolvedOptions', function() { 999 if (%_IsConstructCall()) { 1000 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1001 } 1002 1003 if (!this || typeof this !== 'object' || 1004 this.__initializedIntlObject !== 'collator') { 1005 throw new $TypeError('resolvedOptions method called on a non-object ' + 1006 'or on a object that is not Intl.Collator.'); 1007 } 1008 1009 var coll = this; 1010 var locale = getOptimalLanguageTag(coll.resolved.requestedLocale, 1011 coll.resolved.locale); 1012 1013 return { 1014 locale: locale, 1015 usage: coll.resolved.usage, 1016 sensitivity: coll.resolved.sensitivity, 1017 ignorePunctuation: coll.resolved.ignorePunctuation, 1018 numeric: coll.resolved.numeric, 1019 caseFirst: coll.resolved.caseFirst, 1020 collation: coll.resolved.collation 1021 }; 1022 }, 1023 DONT_ENUM 1024 ); 1025 %FunctionSetName(Intl.Collator.prototype.resolvedOptions, 'resolvedOptions'); 1026 %FunctionRemovePrototype(Intl.Collator.prototype.resolvedOptions); 1027 %SetNativeFlag(Intl.Collator.prototype.resolvedOptions); 1028 1029 1030 /** 1031 * Returns the subset of the given locale list for which this locale list 1032 * has a matching (possibly fallback) locale. Locales appear in the same 1033 * order in the returned list as in the input list. 1034 * Options are optional parameter. 1035 */ 1036 %SetProperty(Intl.Collator, 'supportedLocalesOf', function(locales) { 1037 if (%_IsConstructCall()) { 1038 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1039 } 1040 1041 return supportedLocalesOf('collator', locales, %_Arguments(1)); 1042 }, 1043 DONT_ENUM 1044 ); 1045 %FunctionSetName(Intl.Collator.supportedLocalesOf, 'supportedLocalesOf'); 1046 %FunctionRemovePrototype(Intl.Collator.supportedLocalesOf); 1047 %SetNativeFlag(Intl.Collator.supportedLocalesOf); 1048 1049 1050 /** 1051 * When the compare method is called with two arguments x and y, it returns a 1052 * Number other than NaN that represents the result of a locale-sensitive 1053 * String comparison of x with y. 1054 * The result is intended to order String values in the sort order specified 1055 * by the effective locale and collation options computed during construction 1056 * of this Collator object, and will be negative, zero, or positive, depending 1057 * on whether x comes before y in the sort order, the Strings are equal under 1058 * the sort order, or x comes after y in the sort order, respectively. 1059 */ 1060 function compare(collator, x, y) { 1061 return %InternalCompare(collator.collator, $String(x), $String(y)); 1062 }; 1063 1064 1065 addBoundMethod(Intl.Collator, 'compare', compare, 2); 1066 1067 /** 1068 * Verifies that the input is a well-formed ISO 4217 currency code. 1069 * Don't uppercase to test. It could convert invalid code into a valid one. 1070 * For example \u00DFP (Eszett+P) becomes SSP. 1071 */ 1072 function isWellFormedCurrencyCode(currency) { 1073 return typeof currency == "string" && 1074 currency.length == 3 && 1075 currency.match(/[^A-Za-z]/) == null; 1076 } 1077 1078 1079 /** 1080 * Returns the valid digit count for a property, or throws RangeError on 1081 * a value out of the range. 1082 */ 1083 function getNumberOption(options, property, min, max, fallback) { 1084 var value = options[property]; 1085 if (value !== undefined) { 1086 value = $Number(value); 1087 if ($isNaN(value) || value < min || value > max) { 1088 throw new $RangeError(property + ' value is out of range.'); 1089 } 1090 return $floor(value); 1091 } 1092 1093 return fallback; 1094 } 1095 1096 1097 /** 1098 * Initializes the given object so it's a valid NumberFormat instance. 1099 * Useful for subclassing. 1100 */ 1101 function initializeNumberFormat(numberFormat, locales, options) { 1102 if (numberFormat.hasOwnProperty('__initializedIntlObject')) { 1103 throw new $TypeError('Trying to re-initialize NumberFormat object.'); 1104 } 1105 1106 if (options === undefined) { 1107 options = {}; 1108 } 1109 1110 var getOption = getGetOption(options, 'numberformat'); 1111 1112 var locale = resolveLocale('numberformat', locales, options); 1113 1114 var internalOptions = {}; 1115 defineWEProperty(internalOptions, 'style', getOption( 1116 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal')); 1117 1118 var currency = getOption('currency', 'string'); 1119 if (currency !== undefined && !isWellFormedCurrencyCode(currency)) { 1120 throw new $RangeError('Invalid currency code: ' + currency); 1121 } 1122 1123 if (internalOptions.style === 'currency' && currency === undefined) { 1124 throw new $TypeError('Currency code is required with currency style.'); 1125 } 1126 1127 var currencyDisplay = getOption( 1128 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol'); 1129 if (internalOptions.style === 'currency') { 1130 defineWEProperty(internalOptions, 'currency', currency.toUpperCase()); 1131 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay); 1132 } 1133 1134 // Digit ranges. 1135 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1); 1136 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid); 1137 1138 var mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0); 1139 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd); 1140 1141 var mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, 3); 1142 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd); 1143 1144 var mnsd = options['minimumSignificantDigits']; 1145 var mxsd = options['maximumSignificantDigits']; 1146 if (mnsd !== undefined || mxsd !== undefined) { 1147 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0); 1148 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd); 1149 1150 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21); 1151 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd); 1152 } 1153 1154 // Grouping. 1155 defineWEProperty(internalOptions, 'useGrouping', getOption( 1156 'useGrouping', 'boolean', undefined, true)); 1157 1158 // ICU prefers options to be passed using -u- extension key/values for 1159 // number format, so we need to build that. 1160 var extensionMap = parseExtension(locale.extension); 1161 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP, 1162 getOption, internalOptions); 1163 1164 var requestedLocale = locale.locale + extension; 1165 var resolved = $Object.defineProperties({}, { 1166 currency: {writable: true}, 1167 currencyDisplay: {writable: true}, 1168 locale: {writable: true}, 1169 maximumFractionDigits: {writable: true}, 1170 minimumFractionDigits: {writable: true}, 1171 minimumIntegerDigits: {writable: true}, 1172 numberingSystem: {writable: true}, 1173 requestedLocale: {value: requestedLocale, writable: true}, 1174 style: {value: internalOptions.style, writable: true}, 1175 useGrouping: {writable: true} 1176 }); 1177 if (internalOptions.hasOwnProperty('minimumSignificantDigits')) { 1178 defineWEProperty(resolved, 'minimumSignificantDigits', undefined); 1179 } 1180 if (internalOptions.hasOwnProperty('maximumSignificantDigits')) { 1181 defineWEProperty(resolved, 'maximumSignificantDigits', undefined); 1182 } 1183 var formatter = %CreateNumberFormat(requestedLocale, 1184 internalOptions, 1185 resolved); 1186 1187 // We can't get information about number or currency style from ICU, so we 1188 // assume user request was fulfilled. 1189 if (internalOptions.style === 'currency') { 1190 $Object.defineProperty(resolved, 'currencyDisplay', {value: currencyDisplay, 1191 writable: true}); 1192 } 1193 1194 $Object.defineProperty(numberFormat, 'formatter', {value: formatter}); 1195 $Object.defineProperty(numberFormat, 'resolved', {value: resolved}); 1196 $Object.defineProperty(numberFormat, '__initializedIntlObject', 1197 {value: 'numberformat'}); 1198 1199 return numberFormat; 1200 } 1201 1202 1203 /** 1204 * Constructs Intl.NumberFormat object given optional locales and options 1205 * parameters. 1206 * 1207 * @constructor 1208 */ 1209 %SetProperty(Intl, 'NumberFormat', function() { 1210 var locales = %_Arguments(0); 1211 var options = %_Arguments(1); 1212 1213 if (!this || this === Intl) { 1214 // Constructor is called as a function. 1215 return new Intl.NumberFormat(locales, options); 1216 } 1217 1218 return initializeNumberFormat(toObject(this), locales, options); 1219 }, 1220 DONT_ENUM 1221 ); 1222 1223 1224 /** 1225 * NumberFormat resolvedOptions method. 1226 */ 1227 %SetProperty(Intl.NumberFormat.prototype, 'resolvedOptions', function() { 1228 if (%_IsConstructCall()) { 1229 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1230 } 1231 1232 if (!this || typeof this !== 'object' || 1233 this.__initializedIntlObject !== 'numberformat') { 1234 throw new $TypeError('resolvedOptions method called on a non-object' + 1235 ' or on a object that is not Intl.NumberFormat.'); 1236 } 1237 1238 var format = this; 1239 var locale = getOptimalLanguageTag(format.resolved.requestedLocale, 1240 format.resolved.locale); 1241 1242 var result = { 1243 locale: locale, 1244 numberingSystem: format.resolved.numberingSystem, 1245 style: format.resolved.style, 1246 useGrouping: format.resolved.useGrouping, 1247 minimumIntegerDigits: format.resolved.minimumIntegerDigits, 1248 minimumFractionDigits: format.resolved.minimumFractionDigits, 1249 maximumFractionDigits: format.resolved.maximumFractionDigits, 1250 }; 1251 1252 if (result.style === 'currency') { 1253 defineWECProperty(result, 'currency', format.resolved.currency); 1254 defineWECProperty(result, 'currencyDisplay', 1255 format.resolved.currencyDisplay); 1256 } 1257 1258 if (format.resolved.hasOwnProperty('minimumSignificantDigits')) { 1259 defineWECProperty(result, 'minimumSignificantDigits', 1260 format.resolved.minimumSignificantDigits); 1261 } 1262 1263 if (format.resolved.hasOwnProperty('maximumSignificantDigits')) { 1264 defineWECProperty(result, 'maximumSignificantDigits', 1265 format.resolved.maximumSignificantDigits); 1266 } 1267 1268 return result; 1269 }, 1270 DONT_ENUM 1271 ); 1272 %FunctionSetName(Intl.NumberFormat.prototype.resolvedOptions, 1273 'resolvedOptions'); 1274 %FunctionRemovePrototype(Intl.NumberFormat.prototype.resolvedOptions); 1275 %SetNativeFlag(Intl.NumberFormat.prototype.resolvedOptions); 1276 1277 1278 /** 1279 * Returns the subset of the given locale list for which this locale list 1280 * has a matching (possibly fallback) locale. Locales appear in the same 1281 * order in the returned list as in the input list. 1282 * Options are optional parameter. 1283 */ 1284 %SetProperty(Intl.NumberFormat, 'supportedLocalesOf', function(locales) { 1285 if (%_IsConstructCall()) { 1286 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1287 } 1288 1289 return supportedLocalesOf('numberformat', locales, %_Arguments(1)); 1290 }, 1291 DONT_ENUM 1292 ); 1293 %FunctionSetName(Intl.NumberFormat.supportedLocalesOf, 'supportedLocalesOf'); 1294 %FunctionRemovePrototype(Intl.NumberFormat.supportedLocalesOf); 1295 %SetNativeFlag(Intl.NumberFormat.supportedLocalesOf); 1296 1297 1298 /** 1299 * Returns a String value representing the result of calling ToNumber(value) 1300 * according to the effective locale and the formatting options of this 1301 * NumberFormat. 1302 */ 1303 function formatNumber(formatter, value) { 1304 // Spec treats -0 and +0 as 0. 1305 var number = $Number(value) + 0; 1306 1307 return %InternalNumberFormat(formatter.formatter, number); 1308 } 1309 1310 1311 /** 1312 * Returns a Number that represents string value that was passed in. 1313 */ 1314 function parseNumber(formatter, value) { 1315 return %InternalNumberParse(formatter.formatter, $String(value)); 1316 } 1317 1318 1319 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1); 1320 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1); 1321 1322 /** 1323 * Returns a string that matches LDML representation of the options object. 1324 */ 1325 function toLDMLString(options) { 1326 var getOption = getGetOption(options, 'dateformat'); 1327 1328 var ldmlString = ''; 1329 1330 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']); 1331 ldmlString += appendToLDMLString( 1332 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'}); 1333 1334 option = getOption('era', 'string', ['narrow', 'short', 'long']); 1335 ldmlString += appendToLDMLString( 1336 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'}); 1337 1338 option = getOption('year', 'string', ['2-digit', 'numeric']); 1339 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'}); 1340 1341 option = getOption('month', 'string', 1342 ['2-digit', 'numeric', 'narrow', 'short', 'long']); 1343 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M', 1344 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'}); 1345 1346 option = getOption('day', 'string', ['2-digit', 'numeric']); 1347 ldmlString += appendToLDMLString( 1348 option, {'2-digit': 'dd', 'numeric': 'd'}); 1349 1350 var hr12 = getOption('hour12', 'boolean'); 1351 option = getOption('hour', 'string', ['2-digit', 'numeric']); 1352 if (hr12 === undefined) { 1353 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'}); 1354 } else if (hr12 === true) { 1355 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'}); 1356 } else { 1357 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'}); 1358 } 1359 1360 option = getOption('minute', 'string', ['2-digit', 'numeric']); 1361 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'}); 1362 1363 option = getOption('second', 'string', ['2-digit', 'numeric']); 1364 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'}); 1365 1366 option = getOption('timeZoneName', 'string', ['short', 'long']); 1367 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'}); 1368 1369 return ldmlString; 1370 } 1371 1372 1373 /** 1374 * Returns either LDML equivalent of the current option or empty string. 1375 */ 1376 function appendToLDMLString(option, pairs) { 1377 if (option !== undefined) { 1378 return pairs[option]; 1379 } else { 1380 return ''; 1381 } 1382 } 1383 1384 1385 /** 1386 * Returns object that matches LDML representation of the date. 1387 */ 1388 function fromLDMLString(ldmlString) { 1389 // First remove '' quoted text, so we lose 'Uhr' strings. 1390 ldmlString = ldmlString.replace(GetQuotedStringRE(), ''); 1391 1392 var options = {}; 1393 var match = ldmlString.match(/E{3,5}/g); 1394 options = appendToDateTimeObject( 1395 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'}); 1396 1397 match = ldmlString.match(/G{3,5}/g); 1398 options = appendToDateTimeObject( 1399 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'}); 1400 1401 match = ldmlString.match(/y{1,2}/g); 1402 options = appendToDateTimeObject( 1403 options, 'year', match, {y: 'numeric', yy: '2-digit'}); 1404 1405 match = ldmlString.match(/M{1,5}/g); 1406 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit', 1407 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'}); 1408 1409 // Sometimes we get L instead of M for month - standalone name. 1410 match = ldmlString.match(/L{1,5}/g); 1411 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit', 1412 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'}); 1413 1414 match = ldmlString.match(/d{1,2}/g); 1415 options = appendToDateTimeObject( 1416 options, 'day', match, {d: 'numeric', dd: '2-digit'}); 1417 1418 match = ldmlString.match(/h{1,2}/g); 1419 if (match !== null) { 1420 options['hour12'] = true; 1421 } 1422 options = appendToDateTimeObject( 1423 options, 'hour', match, {h: 'numeric', hh: '2-digit'}); 1424 1425 match = ldmlString.match(/H{1,2}/g); 1426 if (match !== null) { 1427 options['hour12'] = false; 1428 } 1429 options = appendToDateTimeObject( 1430 options, 'hour', match, {H: 'numeric', HH: '2-digit'}); 1431 1432 match = ldmlString.match(/m{1,2}/g); 1433 options = appendToDateTimeObject( 1434 options, 'minute', match, {m: 'numeric', mm: '2-digit'}); 1435 1436 match = ldmlString.match(/s{1,2}/g); 1437 options = appendToDateTimeObject( 1438 options, 'second', match, {s: 'numeric', ss: '2-digit'}); 1439 1440 match = ldmlString.match(/z|zzzz/g); 1441 options = appendToDateTimeObject( 1442 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'}); 1443 1444 return options; 1445 } 1446 1447 1448 function appendToDateTimeObject(options, option, match, pairs) { 1449 if (IS_NULL(match)) { 1450 if (!options.hasOwnProperty(option)) { 1451 defineWEProperty(options, option, undefined); 1452 } 1453 return options; 1454 } 1455 1456 var property = match[0]; 1457 defineWEProperty(options, option, pairs[property]); 1458 1459 return options; 1460 } 1461 1462 1463 /** 1464 * Returns options with at least default values in it. 1465 */ 1466 function toDateTimeOptions(options, required, defaults) { 1467 if (options === undefined) { 1468 options = null; 1469 } else { 1470 options = toObject(options); 1471 } 1472 1473 options = $Object.apply(this, [options]); 1474 1475 var needsDefault = true; 1476 if ((required === 'date' || required === 'any') && 1477 (options.weekday !== undefined || options.year !== undefined || 1478 options.month !== undefined || options.day !== undefined)) { 1479 needsDefault = false; 1480 } 1481 1482 if ((required === 'time' || required === 'any') && 1483 (options.hour !== undefined || options.minute !== undefined || 1484 options.second !== undefined)) { 1485 needsDefault = false; 1486 } 1487 1488 if (needsDefault && (defaults === 'date' || defaults === 'all')) { 1489 $Object.defineProperty(options, 'year', {value: 'numeric', 1490 writable: true, 1491 enumerable: true, 1492 configurable: true}); 1493 $Object.defineProperty(options, 'month', {value: 'numeric', 1494 writable: true, 1495 enumerable: true, 1496 configurable: true}); 1497 $Object.defineProperty(options, 'day', {value: 'numeric', 1498 writable: true, 1499 enumerable: true, 1500 configurable: true}); 1501 } 1502 1503 if (needsDefault && (defaults === 'time' || defaults === 'all')) { 1504 $Object.defineProperty(options, 'hour', {value: 'numeric', 1505 writable: true, 1506 enumerable: true, 1507 configurable: true}); 1508 $Object.defineProperty(options, 'minute', {value: 'numeric', 1509 writable: true, 1510 enumerable: true, 1511 configurable: true}); 1512 $Object.defineProperty(options, 'second', {value: 'numeric', 1513 writable: true, 1514 enumerable: true, 1515 configurable: true}); 1516 } 1517 1518 return options; 1519 } 1520 1521 1522 /** 1523 * Initializes the given object so it's a valid DateTimeFormat instance. 1524 * Useful for subclassing. 1525 */ 1526 function initializeDateTimeFormat(dateFormat, locales, options) { 1527 1528 if (dateFormat.hasOwnProperty('__initializedIntlObject')) { 1529 throw new $TypeError('Trying to re-initialize DateTimeFormat object.'); 1530 } 1531 1532 if (options === undefined) { 1533 options = {}; 1534 } 1535 1536 var locale = resolveLocale('dateformat', locales, options); 1537 1538 options = toDateTimeOptions(options, 'any', 'date'); 1539 1540 var getOption = getGetOption(options, 'dateformat'); 1541 1542 // We implement only best fit algorithm, but still need to check 1543 // if the formatMatcher values are in range. 1544 var matcher = getOption('formatMatcher', 'string', 1545 ['basic', 'best fit'], 'best fit'); 1546 1547 // Build LDML string for the skeleton that we pass to the formatter. 1548 var ldmlString = toLDMLString(options); 1549 1550 // Filter out supported extension keys so we know what to put in resolved 1551 // section later on. 1552 // We need to pass calendar and number system to the method. 1553 var tz = canonicalizeTimeZoneID(options.timeZone); 1554 1555 // ICU prefers options to be passed using -u- extension key/values, so 1556 // we need to build that. 1557 var internalOptions = {}; 1558 var extensionMap = parseExtension(locale.extension); 1559 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP, 1560 getOption, internalOptions); 1561 1562 var requestedLocale = locale.locale + extension; 1563 var resolved = $Object.defineProperties({}, { 1564 calendar: {writable: true}, 1565 day: {writable: true}, 1566 era: {writable: true}, 1567 hour12: {writable: true}, 1568 hour: {writable: true}, 1569 locale: {writable: true}, 1570 minute: {writable: true}, 1571 month: {writable: true}, 1572 numberingSystem: {writable: true}, 1573 pattern: {writable: true}, 1574 requestedLocale: {value: requestedLocale, writable: true}, 1575 second: {writable: true}, 1576 timeZone: {writable: true}, 1577 timeZoneName: {writable: true}, 1578 tz: {value: tz, writable: true}, 1579 weekday: {writable: true}, 1580 year: {writable: true} 1581 }); 1582 1583 var formatter = %CreateDateTimeFormat( 1584 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved); 1585 1586 if (tz !== undefined && tz !== resolved.timeZone) { 1587 throw new $RangeError('Unsupported time zone specified ' + tz); 1588 } 1589 1590 $Object.defineProperty(dateFormat, 'formatter', {value: formatter}); 1591 $Object.defineProperty(dateFormat, 'resolved', {value: resolved}); 1592 $Object.defineProperty(dateFormat, '__initializedIntlObject', 1593 {value: 'dateformat'}); 1594 1595 return dateFormat; 1596 } 1597 1598 1599 /** 1600 * Constructs Intl.DateTimeFormat object given optional locales and options 1601 * parameters. 1602 * 1603 * @constructor 1604 */ 1605 %SetProperty(Intl, 'DateTimeFormat', function() { 1606 var locales = %_Arguments(0); 1607 var options = %_Arguments(1); 1608 1609 if (!this || this === Intl) { 1610 // Constructor is called as a function. 1611 return new Intl.DateTimeFormat(locales, options); 1612 } 1613 1614 return initializeDateTimeFormat(toObject(this), locales, options); 1615 }, 1616 DONT_ENUM 1617 ); 1618 1619 1620 /** 1621 * DateTimeFormat resolvedOptions method. 1622 */ 1623 %SetProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() { 1624 if (%_IsConstructCall()) { 1625 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1626 } 1627 1628 if (!this || typeof this !== 'object' || 1629 this.__initializedIntlObject !== 'dateformat') { 1630 throw new $TypeError('resolvedOptions method called on a non-object or ' + 1631 'on a object that is not Intl.DateTimeFormat.'); 1632 } 1633 1634 var format = this; 1635 var fromPattern = fromLDMLString(format.resolved.pattern); 1636 var userCalendar = ICU_CALENDAR_MAP[format.resolved.calendar]; 1637 if (userCalendar === undefined) { 1638 // Use ICU name if we don't have a match. It shouldn't happen, but 1639 // it would be too strict to throw for this. 1640 userCalendar = format.resolved.calendar; 1641 } 1642 1643 var locale = getOptimalLanguageTag(format.resolved.requestedLocale, 1644 format.resolved.locale); 1645 1646 var result = { 1647 locale: locale, 1648 numberingSystem: format.resolved.numberingSystem, 1649 calendar: userCalendar, 1650 timeZone: format.resolved.timeZone 1651 }; 1652 1653 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName); 1654 addWECPropertyIfDefined(result, 'era', fromPattern.era); 1655 addWECPropertyIfDefined(result, 'year', fromPattern.year); 1656 addWECPropertyIfDefined(result, 'month', fromPattern.month); 1657 addWECPropertyIfDefined(result, 'day', fromPattern.day); 1658 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday); 1659 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12); 1660 addWECPropertyIfDefined(result, 'hour', fromPattern.hour); 1661 addWECPropertyIfDefined(result, 'minute', fromPattern.minute); 1662 addWECPropertyIfDefined(result, 'second', fromPattern.second); 1663 1664 return result; 1665 }, 1666 DONT_ENUM 1667 ); 1668 %FunctionSetName(Intl.DateTimeFormat.prototype.resolvedOptions, 1669 'resolvedOptions'); 1670 %FunctionRemovePrototype(Intl.DateTimeFormat.prototype.resolvedOptions); 1671 %SetNativeFlag(Intl.DateTimeFormat.prototype.resolvedOptions); 1672 1673 1674 /** 1675 * Returns the subset of the given locale list for which this locale list 1676 * has a matching (possibly fallback) locale. Locales appear in the same 1677 * order in the returned list as in the input list. 1678 * Options are optional parameter. 1679 */ 1680 %SetProperty(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) { 1681 if (%_IsConstructCall()) { 1682 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1683 } 1684 1685 return supportedLocalesOf('dateformat', locales, %_Arguments(1)); 1686 }, 1687 DONT_ENUM 1688 ); 1689 %FunctionSetName(Intl.DateTimeFormat.supportedLocalesOf, 'supportedLocalesOf'); 1690 %FunctionRemovePrototype(Intl.DateTimeFormat.supportedLocalesOf); 1691 %SetNativeFlag(Intl.DateTimeFormat.supportedLocalesOf); 1692 1693 1694 /** 1695 * Returns a String value representing the result of calling ToNumber(date) 1696 * according to the effective locale and the formatting options of this 1697 * DateTimeFormat. 1698 */ 1699 function formatDate(formatter, dateValue) { 1700 var dateMs; 1701 if (dateValue === undefined) { 1702 dateMs = $Date.now(); 1703 } else { 1704 dateMs = $Number(dateValue); 1705 } 1706 1707 if (!$isFinite(dateMs)) { 1708 throw new $RangeError('Provided date is not in valid range.'); 1709 } 1710 1711 return %InternalDateFormat(formatter.formatter, new $Date(dateMs)); 1712 } 1713 1714 1715 /** 1716 * Returns a Date object representing the result of calling ToString(value) 1717 * according to the effective locale and the formatting options of this 1718 * DateTimeFormat. 1719 * Returns undefined if date string cannot be parsed. 1720 */ 1721 function parseDate(formatter, value) { 1722 return %InternalDateParse(formatter.formatter, $String(value)); 1723 } 1724 1725 1726 // 0 because date is optional argument. 1727 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0); 1728 addBoundMethod(Intl.DateTimeFormat, 'v8Parse', parseDate, 1); 1729 1730 1731 /** 1732 * Returns canonical Area/Location name, or throws an exception if the zone 1733 * name is invalid IANA name. 1734 */ 1735 function canonicalizeTimeZoneID(tzID) { 1736 // Skip undefined zones. 1737 if (tzID === undefined) { 1738 return tzID; 1739 } 1740 1741 // Special case handling (UTC, GMT). 1742 var upperID = tzID.toUpperCase(); 1743 if (upperID === 'UTC' || upperID === 'GMT' || 1744 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') { 1745 return 'UTC'; 1746 } 1747 1748 // We expect only _ and / beside ASCII letters. 1749 // All inputs should conform to Area/Location from now on. 1750 var match = GetTimezoneNameCheckRE().exec(tzID); 1751 if (IS_NULL(match)) { 1752 throw new $RangeError('Expected Area/Location for time zone, got ' + tzID); 1753 } 1754 1755 var result = toTitleCaseWord(match[1]) + '/' + toTitleCaseWord(match[2]); 1756 var i = 3; 1757 while (match[i] !== undefined && i < match.length) { 1758 result = result + '_' + toTitleCaseWord(match[i]); 1759 i++; 1760 } 1761 1762 return result; 1763 } 1764 1765 /** 1766 * Initializes the given object so it's a valid BreakIterator instance. 1767 * Useful for subclassing. 1768 */ 1769 function initializeBreakIterator(iterator, locales, options) { 1770 if (iterator.hasOwnProperty('__initializedIntlObject')) { 1771 throw new $TypeError('Trying to re-initialize v8BreakIterator object.'); 1772 } 1773 1774 if (options === undefined) { 1775 options = {}; 1776 } 1777 1778 var getOption = getGetOption(options, 'breakiterator'); 1779 1780 var internalOptions = {}; 1781 1782 defineWEProperty(internalOptions, 'type', getOption( 1783 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word')); 1784 1785 var locale = resolveLocale('breakiterator', locales, options); 1786 var resolved = $Object.defineProperties({}, { 1787 requestedLocale: {value: locale.locale, writable: true}, 1788 type: {value: internalOptions.type, writable: true}, 1789 locale: {writable: true} 1790 }); 1791 1792 var internalIterator = %CreateBreakIterator(locale.locale, 1793 internalOptions, 1794 resolved); 1795 1796 $Object.defineProperty(iterator, 'iterator', {value: internalIterator}); 1797 $Object.defineProperty(iterator, 'resolved', {value: resolved}); 1798 $Object.defineProperty(iterator, '__initializedIntlObject', 1799 {value: 'breakiterator'}); 1800 1801 return iterator; 1802 } 1803 1804 1805 /** 1806 * Constructs Intl.v8BreakIterator object given optional locales and options 1807 * parameters. 1808 * 1809 * @constructor 1810 */ 1811 %SetProperty(Intl, 'v8BreakIterator', function() { 1812 var locales = %_Arguments(0); 1813 var options = %_Arguments(1); 1814 1815 if (!this || this === Intl) { 1816 // Constructor is called as a function. 1817 return new Intl.v8BreakIterator(locales, options); 1818 } 1819 1820 return initializeBreakIterator(toObject(this), locales, options); 1821 }, 1822 DONT_ENUM 1823 ); 1824 1825 1826 /** 1827 * BreakIterator resolvedOptions method. 1828 */ 1829 %SetProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', function() { 1830 if (%_IsConstructCall()) { 1831 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1832 } 1833 1834 if (!this || typeof this !== 'object' || 1835 this.__initializedIntlObject !== 'breakiterator') { 1836 throw new $TypeError('resolvedOptions method called on a non-object or ' + 1837 'on a object that is not Intl.v8BreakIterator.'); 1838 } 1839 1840 var segmenter = this; 1841 var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale, 1842 segmenter.resolved.locale); 1843 1844 return { 1845 locale: locale, 1846 type: segmenter.resolved.type 1847 }; 1848 }, 1849 DONT_ENUM 1850 ); 1851 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions, 1852 'resolvedOptions'); 1853 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions); 1854 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions); 1855 1856 1857 /** 1858 * Returns the subset of the given locale list for which this locale list 1859 * has a matching (possibly fallback) locale. Locales appear in the same 1860 * order in the returned list as in the input list. 1861 * Options are optional parameter. 1862 */ 1863 %SetProperty(Intl.v8BreakIterator, 'supportedLocalesOf', function(locales) { 1864 if (%_IsConstructCall()) { 1865 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1866 } 1867 1868 return supportedLocalesOf('breakiterator', locales, %_Arguments(1)); 1869 }, 1870 DONT_ENUM 1871 ); 1872 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf'); 1873 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf); 1874 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf); 1875 1876 1877 /** 1878 * Adopts text to segment using the iterator. Old text, if present, 1879 * gets discarded. 1880 */ 1881 function adoptText(iterator, text) { 1882 %BreakIteratorAdoptText(iterator.iterator, $String(text)); 1883 } 1884 1885 1886 /** 1887 * Returns index of the first break in the string and moves current pointer. 1888 */ 1889 function first(iterator) { 1890 return %BreakIteratorFirst(iterator.iterator); 1891 } 1892 1893 1894 /** 1895 * Returns the index of the next break and moves the pointer. 1896 */ 1897 function next(iterator) { 1898 return %BreakIteratorNext(iterator.iterator); 1899 } 1900 1901 1902 /** 1903 * Returns index of the current break. 1904 */ 1905 function current(iterator) { 1906 return %BreakIteratorCurrent(iterator.iterator); 1907 } 1908 1909 1910 /** 1911 * Returns type of the current break. 1912 */ 1913 function breakType(iterator) { 1914 return %BreakIteratorBreakType(iterator.iterator); 1915 } 1916 1917 1918 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1); 1919 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0); 1920 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0); 1921 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0); 1922 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0); 1923 1924 // Save references to Intl objects and methods we use, for added security. 1925 var savedObjects = { 1926 'collator': Intl.Collator, 1927 'numberformat': Intl.NumberFormat, 1928 'dateformatall': Intl.DateTimeFormat, 1929 'dateformatdate': Intl.DateTimeFormat, 1930 'dateformattime': Intl.DateTimeFormat 1931 }; 1932 1933 1934 // Default (created with undefined locales and options parameters) collator, 1935 // number and date format instances. They'll be created as needed. 1936 var defaultObjects = { 1937 'collator': undefined, 1938 'numberformat': undefined, 1939 'dateformatall': undefined, 1940 'dateformatdate': undefined, 1941 'dateformattime': undefined, 1942 }; 1943 1944 1945 /** 1946 * Returns cached or newly created instance of a given service. 1947 * We cache only default instances (where no locales or options are provided). 1948 */ 1949 function cachedOrNewService(service, locales, options, defaults) { 1950 var useOptions = (defaults === undefined) ? options : defaults; 1951 if (locales === undefined && options === undefined) { 1952 if (defaultObjects[service] === undefined) { 1953 defaultObjects[service] = new savedObjects[service](locales, useOptions); 1954 } 1955 return defaultObjects[service]; 1956 } 1957 return new savedObjects[service](locales, useOptions); 1958 } 1959 1960 1961 /** 1962 * Compares this and that, and returns less than 0, 0 or greater than 0 value. 1963 * Overrides the built-in method. 1964 */ 1965 $Object.defineProperty($String.prototype, 'localeCompare', { 1966 value: function(that) { 1967 if (%_IsConstructCall()) { 1968 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1969 } 1970 1971 if (IS_NULL_OR_UNDEFINED(this)) { 1972 throw new $TypeError('Method invoked on undefined or null value.'); 1973 } 1974 1975 var locales = %_Arguments(1); 1976 var options = %_Arguments(2); 1977 var collator = cachedOrNewService('collator', locales, options); 1978 return compare(collator, this, that); 1979 }, 1980 writable: true, 1981 configurable: true, 1982 enumerable: false 1983 }); 1984 %FunctionSetName($String.prototype.localeCompare, 'localeCompare'); 1985 %FunctionRemovePrototype($String.prototype.localeCompare); 1986 %SetNativeFlag($String.prototype.localeCompare); 1987 1988 1989 /** 1990 * Formats a Number object (this) using locale and options values. 1991 * If locale or options are omitted, defaults are used. 1992 */ 1993 $Object.defineProperty($Number.prototype, 'toLocaleString', { 1994 value: function() { 1995 if (%_IsConstructCall()) { 1996 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1997 } 1998 1999 if (!(this instanceof $Number) && typeof(this) !== 'number') { 2000 throw new $TypeError('Method invoked on an object that is not Number.'); 2001 } 2002 2003 var locales = %_Arguments(0); 2004 var options = %_Arguments(1); 2005 var numberFormat = cachedOrNewService('numberformat', locales, options); 2006 return formatNumber(numberFormat, this); 2007 }, 2008 writable: true, 2009 configurable: true, 2010 enumerable: false 2011 }); 2012 %FunctionSetName($Number.prototype.toLocaleString, 'toLocaleString'); 2013 %FunctionRemovePrototype($Number.prototype.toLocaleString); 2014 %SetNativeFlag($Number.prototype.toLocaleString); 2015 2016 2017 /** 2018 * Returns actual formatted date or fails if date parameter is invalid. 2019 */ 2020 function toLocaleDateTime(date, locales, options, required, defaults, service) { 2021 if (!(date instanceof $Date)) { 2022 throw new $TypeError('Method invoked on an object that is not Date.'); 2023 } 2024 2025 if ($isNaN(date)) { 2026 return 'Invalid Date'; 2027 } 2028 2029 var internalOptions = toDateTimeOptions(options, required, defaults); 2030 2031 var dateFormat = 2032 cachedOrNewService(service, locales, options, internalOptions); 2033 2034 return formatDate(dateFormat, date); 2035 } 2036 2037 2038 /** 2039 * Formats a Date object (this) using locale and options values. 2040 * If locale or options are omitted, defaults are used - both date and time are 2041 * present in the output. 2042 */ 2043 $Object.defineProperty($Date.prototype, 'toLocaleString', { 2044 value: function() { 2045 if (%_IsConstructCall()) { 2046 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 2047 } 2048 2049 var locales = %_Arguments(0); 2050 var options = %_Arguments(1); 2051 return toLocaleDateTime( 2052 this, locales, options, 'any', 'all', 'dateformatall'); 2053 }, 2054 writable: true, 2055 configurable: true, 2056 enumerable: false 2057 }); 2058 %FunctionSetName($Date.prototype.toLocaleString, 'toLocaleString'); 2059 %FunctionRemovePrototype($Date.prototype.toLocaleString); 2060 %SetNativeFlag($Date.prototype.toLocaleString); 2061 2062 2063 /** 2064 * Formats a Date object (this) using locale and options values. 2065 * If locale or options are omitted, defaults are used - only date is present 2066 * in the output. 2067 */ 2068 $Object.defineProperty($Date.prototype, 'toLocaleDateString', { 2069 value: function() { 2070 if (%_IsConstructCall()) { 2071 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 2072 } 2073 2074 var locales = %_Arguments(0); 2075 var options = %_Arguments(1); 2076 return toLocaleDateTime( 2077 this, locales, options, 'date', 'date', 'dateformatdate'); 2078 }, 2079 writable: true, 2080 configurable: true, 2081 enumerable: false 2082 }); 2083 %FunctionSetName($Date.prototype.toLocaleDateString, 'toLocaleDateString'); 2084 %FunctionRemovePrototype($Date.prototype.toLocaleDateString); 2085 %SetNativeFlag($Date.prototype.toLocaleDateString); 2086 2087 2088 /** 2089 * Formats a Date object (this) using locale and options values. 2090 * If locale or options are omitted, defaults are used - only time is present 2091 * in the output. 2092 */ 2093 $Object.defineProperty($Date.prototype, 'toLocaleTimeString', { 2094 value: function() { 2095 if (%_IsConstructCall()) { 2096 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 2097 } 2098 2099 var locales = %_Arguments(0); 2100 var options = %_Arguments(1); 2101 return toLocaleDateTime( 2102 this, locales, options, 'time', 'time', 'dateformattime'); 2103 }, 2104 writable: true, 2105 configurable: true, 2106 enumerable: false 2107 }); 2108 %FunctionSetName($Date.prototype.toLocaleTimeString, 'toLocaleTimeString'); 2109 %FunctionRemovePrototype($Date.prototype.toLocaleTimeString); 2110 %SetNativeFlag($Date.prototype.toLocaleTimeString); 2111 2112 return Intl; 2113 }())}); 2114