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 15 var Intl = {}; 16 17 var undefined = global.undefined; 18 19 var AVAILABLE_SERVICES = ['collator', 20 'numberformat', 21 'dateformat', 22 'breakiterator']; 23 24 var NORMALIZATION_FORMS = ['NFC', 25 'NFD', 26 'NFKC', 27 'NFKD']; 28 29 /** 30 * Caches available locales for each service. 31 */ 32 var AVAILABLE_LOCALES = { 33 'collator': undefined, 34 'numberformat': undefined, 35 'dateformat': undefined, 36 'breakiterator': undefined 37 }; 38 39 /** 40 * Caches default ICU locale. 41 */ 42 var DEFAULT_ICU_LOCALE = undefined; 43 44 /** 45 * Unicode extension regular expression. 46 */ 47 var UNICODE_EXTENSION_RE = undefined; 48 49 function 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 */ 59 var ANY_EXTENSION_RE = undefined; 60 61 function 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 */ 71 var QUOTED_STRING_RE = undefined; 72 73 function 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 */ 83 var SERVICE_RE = undefined; 84 85 function 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 */ 97 var LANGUAGE_TAG_RE = undefined; 98 99 function 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 */ 109 var LANGUAGE_VARIANT_RE = undefined; 110 111 function 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 */ 121 var LANGUAGE_SINGLETON_RE = undefined; 122 123 function 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 */ 133 var TIMEZONE_NAME_CHECK_RE = undefined; 134 135 function 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 */ 146 var 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 */ 166 var 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 */ 176 var 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 */ 184 var 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 */ 193 var 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 */ 202 var 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 */ 209 function 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 */ 272 function 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 */ 317 function 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 */ 347 function 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 */ 358 function 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 */ 408 function 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 */ 429 function 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 */ 471 function 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 */ 482 function 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 */ 524 function 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 */ 581 function 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 */ 602 function 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 */ 628 function 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 */ 650 function 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 */ 660 function 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 */ 670 function 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 */ 683 function 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 */ 693 function 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 */ 700 function 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 */ 730 function 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 */ 772 function 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 */ 823 function 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 */ 860 function 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 %AddNamedProperty(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 %AddNamedProperty(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 %AddNamedProperty(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 */ 1024 function compare(collator, x, y) { 1025 return %InternalCompare(%GetImplFromInitializedIntlObject(collator), 1026 $String(x), $String(y)); 1027 }; 1028 1029 1030 addBoundMethod(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 */ 1037 function 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 */ 1048 function 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 */ 1066 function 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 %AddNamedProperty(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 %AddNamedProperty(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 %AddNamedProperty(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 */ 1265 function 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 */ 1277 function parseNumber(formatter, value) { 1278 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter), 1279 $String(value)); 1280 } 1281 1282 1283 addBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1); 1284 addBoundMethod(Intl.NumberFormat, 'v8Parse', parseNumber, 1); 1285 1286 /** 1287 * Returns a string that matches LDML representation of the options object. 1288 */ 1289 function 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 */ 1340 function 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 */ 1352 function 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 1412 function 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 */ 1430 function 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 */ 1488 function 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 %AddNamedProperty(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 %AddNamedProperty(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 %AddNamedProperty(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 */ 1658 function 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 */ 1681 function parseDate(formatter, value) { 1682 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter), 1683 $String(value)); 1684 } 1685 1686 1687 // 0 because date is optional argument. 1688 addBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0); 1689 addBoundMethod(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 */ 1696 function 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 */ 1730 function 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 %AddNamedProperty(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 %AddNamedProperty(Intl.v8BreakIterator.prototype, 'resolvedOptions', 1790 function() { 1791 if (%_IsConstructCall()) { 1792 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1793 } 1794 1795 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) { 1796 throw new $TypeError('resolvedOptions method called on a non-object or ' + 1797 'on a object that is not Intl.v8BreakIterator.'); 1798 } 1799 1800 var segmenter = this; 1801 var locale = getOptimalLanguageTag(segmenter.resolved.requestedLocale, 1802 segmenter.resolved.locale); 1803 1804 return { 1805 locale: locale, 1806 type: segmenter.resolved.type 1807 }; 1808 }, 1809 DONT_ENUM 1810 ); 1811 %FunctionSetName(Intl.v8BreakIterator.prototype.resolvedOptions, 1812 'resolvedOptions'); 1813 %FunctionRemovePrototype(Intl.v8BreakIterator.prototype.resolvedOptions); 1814 %SetNativeFlag(Intl.v8BreakIterator.prototype.resolvedOptions); 1815 1816 1817 /** 1818 * Returns the subset of the given locale list for which this locale list 1819 * has a matching (possibly fallback) locale. Locales appear in the same 1820 * order in the returned list as in the input list. 1821 * Options are optional parameter. 1822 */ 1823 %AddNamedProperty(Intl.v8BreakIterator, 'supportedLocalesOf', 1824 function(locales) { 1825 if (%_IsConstructCall()) { 1826 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1827 } 1828 1829 return supportedLocalesOf('breakiterator', locales, %_Arguments(1)); 1830 }, 1831 DONT_ENUM 1832 ); 1833 %FunctionSetName(Intl.v8BreakIterator.supportedLocalesOf, 'supportedLocalesOf'); 1834 %FunctionRemovePrototype(Intl.v8BreakIterator.supportedLocalesOf); 1835 %SetNativeFlag(Intl.v8BreakIterator.supportedLocalesOf); 1836 1837 1838 /** 1839 * Adopts text to segment using the iterator. Old text, if present, 1840 * gets discarded. 1841 */ 1842 function adoptText(iterator, text) { 1843 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator), 1844 $String(text)); 1845 } 1846 1847 1848 /** 1849 * Returns index of the first break in the string and moves current pointer. 1850 */ 1851 function first(iterator) { 1852 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator)); 1853 } 1854 1855 1856 /** 1857 * Returns the index of the next break and moves the pointer. 1858 */ 1859 function next(iterator) { 1860 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator)); 1861 } 1862 1863 1864 /** 1865 * Returns index of the current break. 1866 */ 1867 function current(iterator) { 1868 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator)); 1869 } 1870 1871 1872 /** 1873 * Returns type of the current break. 1874 */ 1875 function breakType(iterator) { 1876 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator)); 1877 } 1878 1879 1880 addBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1); 1881 addBoundMethod(Intl.v8BreakIterator, 'first', first, 0); 1882 addBoundMethod(Intl.v8BreakIterator, 'next', next, 0); 1883 addBoundMethod(Intl.v8BreakIterator, 'current', current, 0); 1884 addBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0); 1885 1886 // Save references to Intl objects and methods we use, for added security. 1887 var savedObjects = { 1888 'collator': Intl.Collator, 1889 'numberformat': Intl.NumberFormat, 1890 'dateformatall': Intl.DateTimeFormat, 1891 'dateformatdate': Intl.DateTimeFormat, 1892 'dateformattime': Intl.DateTimeFormat 1893 }; 1894 1895 1896 // Default (created with undefined locales and options parameters) collator, 1897 // number and date format instances. They'll be created as needed. 1898 var defaultObjects = { 1899 'collator': undefined, 1900 'numberformat': undefined, 1901 'dateformatall': undefined, 1902 'dateformatdate': undefined, 1903 'dateformattime': undefined, 1904 }; 1905 1906 1907 /** 1908 * Returns cached or newly created instance of a given service. 1909 * We cache only default instances (where no locales or options are provided). 1910 */ 1911 function cachedOrNewService(service, locales, options, defaults) { 1912 var useOptions = (defaults === undefined) ? options : defaults; 1913 if (locales === undefined && options === undefined) { 1914 if (defaultObjects[service] === undefined) { 1915 defaultObjects[service] = new savedObjects[service](locales, useOptions); 1916 } 1917 return defaultObjects[service]; 1918 } 1919 return new savedObjects[service](locales, useOptions); 1920 } 1921 1922 1923 /** 1924 * Compares this and that, and returns less than 0, 0 or greater than 0 value. 1925 * Overrides the built-in method. 1926 */ 1927 ObjectDefineProperty($String.prototype, 'localeCompare', { 1928 value: function(that) { 1929 if (%_IsConstructCall()) { 1930 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1931 } 1932 1933 if (IS_NULL_OR_UNDEFINED(this)) { 1934 throw new $TypeError('Method invoked on undefined or null value.'); 1935 } 1936 1937 var locales = %_Arguments(1); 1938 var options = %_Arguments(2); 1939 var collator = cachedOrNewService('collator', locales, options); 1940 return compare(collator, this, that); 1941 }, 1942 writable: true, 1943 configurable: true, 1944 enumerable: false 1945 }); 1946 %FunctionSetName($String.prototype.localeCompare, 'localeCompare'); 1947 %FunctionRemovePrototype($String.prototype.localeCompare); 1948 %SetNativeFlag($String.prototype.localeCompare); 1949 1950 1951 /** 1952 * Unicode normalization. This method is called with one argument that 1953 * specifies the normalization form. 1954 * If none is specified, "NFC" is assumed. 1955 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw 1956 * a RangeError Exception. 1957 */ 1958 ObjectDefineProperty($String.prototype, 'normalize', { 1959 value: function(that) { 1960 if (%_IsConstructCall()) { 1961 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1962 } 1963 1964 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize"); 1965 1966 var form = $String(%_Arguments(0) || 'NFC'); 1967 1968 var normalizationForm = NORMALIZATION_FORMS.indexOf(form); 1969 if (normalizationForm === -1) { 1970 throw new $RangeError('The normalization form should be one of ' 1971 + NORMALIZATION_FORMS.join(', ') + '.'); 1972 } 1973 1974 return %StringNormalize(this, normalizationForm); 1975 }, 1976 writable: true, 1977 configurable: true, 1978 enumerable: false 1979 }); 1980 %FunctionSetName($String.prototype.normalize, 'normalize'); 1981 %FunctionRemovePrototype($String.prototype.normalize); 1982 %SetNativeFlag($String.prototype.normalize); 1983 1984 1985 /** 1986 * Formats a Number object (this) using locale and options values. 1987 * If locale or options are omitted, defaults are used. 1988 */ 1989 ObjectDefineProperty($Number.prototype, 'toLocaleString', { 1990 value: function() { 1991 if (%_IsConstructCall()) { 1992 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 1993 } 1994 1995 if (!(this instanceof $Number) && typeof(this) !== 'number') { 1996 throw new $TypeError('Method invoked on an object that is not Number.'); 1997 } 1998 1999 var locales = %_Arguments(0); 2000 var options = %_Arguments(1); 2001 var numberFormat = cachedOrNewService('numberformat', locales, options); 2002 return formatNumber(numberFormat, this); 2003 }, 2004 writable: true, 2005 configurable: true, 2006 enumerable: false 2007 }); 2008 %FunctionSetName($Number.prototype.toLocaleString, 'toLocaleString'); 2009 %FunctionRemovePrototype($Number.prototype.toLocaleString); 2010 %SetNativeFlag($Number.prototype.toLocaleString); 2011 2012 2013 /** 2014 * Returns actual formatted date or fails if date parameter is invalid. 2015 */ 2016 function toLocaleDateTime(date, locales, options, required, defaults, service) { 2017 if (!(date instanceof $Date)) { 2018 throw new $TypeError('Method invoked on an object that is not Date.'); 2019 } 2020 2021 if ($isNaN(date)) { 2022 return 'Invalid Date'; 2023 } 2024 2025 var internalOptions = toDateTimeOptions(options, required, defaults); 2026 2027 var dateFormat = 2028 cachedOrNewService(service, locales, options, internalOptions); 2029 2030 return formatDate(dateFormat, date); 2031 } 2032 2033 2034 /** 2035 * Formats a Date object (this) using locale and options values. 2036 * If locale or options are omitted, defaults are used - both date and time are 2037 * present in the output. 2038 */ 2039 ObjectDefineProperty($Date.prototype, 'toLocaleString', { 2040 value: function() { 2041 if (%_IsConstructCall()) { 2042 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 2043 } 2044 2045 var locales = %_Arguments(0); 2046 var options = %_Arguments(1); 2047 return toLocaleDateTime( 2048 this, locales, options, 'any', 'all', 'dateformatall'); 2049 }, 2050 writable: true, 2051 configurable: true, 2052 enumerable: false 2053 }); 2054 %FunctionSetName($Date.prototype.toLocaleString, 'toLocaleString'); 2055 %FunctionRemovePrototype($Date.prototype.toLocaleString); 2056 %SetNativeFlag($Date.prototype.toLocaleString); 2057 2058 2059 /** 2060 * Formats a Date object (this) using locale and options values. 2061 * If locale or options are omitted, defaults are used - only date is present 2062 * in the output. 2063 */ 2064 ObjectDefineProperty($Date.prototype, 'toLocaleDateString', { 2065 value: function() { 2066 if (%_IsConstructCall()) { 2067 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 2068 } 2069 2070 var locales = %_Arguments(0); 2071 var options = %_Arguments(1); 2072 return toLocaleDateTime( 2073 this, locales, options, 'date', 'date', 'dateformatdate'); 2074 }, 2075 writable: true, 2076 configurable: true, 2077 enumerable: false 2078 }); 2079 %FunctionSetName($Date.prototype.toLocaleDateString, 'toLocaleDateString'); 2080 %FunctionRemovePrototype($Date.prototype.toLocaleDateString); 2081 %SetNativeFlag($Date.prototype.toLocaleDateString); 2082 2083 2084 /** 2085 * Formats a Date object (this) using locale and options values. 2086 * If locale or options are omitted, defaults are used - only time is present 2087 * in the output. 2088 */ 2089 ObjectDefineProperty($Date.prototype, 'toLocaleTimeString', { 2090 value: function() { 2091 if (%_IsConstructCall()) { 2092 throw new $TypeError(ORDINARY_FUNCTION_CALLED_AS_CONSTRUCTOR); 2093 } 2094 2095 var locales = %_Arguments(0); 2096 var options = %_Arguments(1); 2097 return toLocaleDateTime( 2098 this, locales, options, 'time', 'time', 'dateformattime'); 2099 }, 2100 writable: true, 2101 configurable: true, 2102 enumerable: false 2103 }); 2104 %FunctionSetName($Date.prototype.toLocaleTimeString, 'toLocaleTimeString'); 2105 %FunctionRemovePrototype($Date.prototype.toLocaleTimeString); 2106 %SetNativeFlag($Date.prototype.toLocaleTimeString); 2107 2108 return Intl; 2109 }())}); 2110