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