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