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 GlobalIntl = global.Intl; 24var GlobalIntlDateTimeFormat = GlobalIntl.DateTimeFormat; 25var GlobalIntlNumberFormat = GlobalIntl.NumberFormat; 26var GlobalIntlCollator = GlobalIntl.Collator; 27var GlobalIntlPluralRules = GlobalIntl.PluralRules; 28var GlobalIntlv8BreakIterator = GlobalIntl.v8BreakIterator; 29var GlobalRegExp = global.RegExp; 30var GlobalString = global.String; 31var GlobalArray = global.Array; 32var IntlFallbackSymbol = utils.ImportNow("intl_fallback_symbol"); 33var InternalArray = utils.InternalArray; 34var MathMax = global.Math.max; 35var ObjectHasOwnProperty = global.Object.prototype.hasOwnProperty; 36var ObjectKeys = global.Object.keys; 37var patternSymbol = utils.ImportNow("intl_pattern_symbol"); 38var resolvedSymbol = utils.ImportNow("intl_resolved_symbol"); 39var StringSubstr = GlobalString.prototype.substr; 40var StringSubstring = GlobalString.prototype.substring; 41 42utils.Import(function(from) { 43 ArrayJoin = from.ArrayJoin; 44 ArrayPush = from.ArrayPush; 45}); 46 47// Utilities for definitions 48 49macro NUMBER_IS_NAN(arg) 50(%IS_VAR(arg) !== arg) 51endmacro 52 53// To avoid ES2015 Function name inference. 54 55macro ANONYMOUS_FUNCTION(fn) 56(0, (fn)) 57endmacro 58 59/** 60 * Adds bound method to the prototype of the given object. 61 */ 62function AddBoundMethod(obj, methodName, implementation, length, type, 63 compat) { 64 %CheckIsBootstrapping(); 65 var internalName = %CreatePrivateSymbol(methodName); 66 67 DEFINE_METHOD( 68 obj.prototype, 69 get [methodName]() { 70 if(!IS_RECEIVER(this)) { 71 throw %make_type_error(kIncompatibleMethodReceiver, methodName, this); 72 } 73 var receiver = %IntlUnwrapReceiver(this, type, obj, methodName, compat); 74 if (IS_UNDEFINED(receiver[internalName])) { 75 var boundMethod; 76 if (IS_UNDEFINED(length) || length === 2) { 77 boundMethod = 78 ANONYMOUS_FUNCTION((fst, snd) => implementation(receiver, fst, snd)); 79 } else if (length === 1) { 80 boundMethod = ANONYMOUS_FUNCTION(fst => implementation(receiver, fst)); 81 } else { 82 boundMethod = ANONYMOUS_FUNCTION((...args) => { 83 // DateTimeFormat.format needs to be 0 arg method, but can still 84 // receive an optional dateValue param. If one was provided, pass it 85 // along. 86 if (args.length > 0) { 87 return implementation(receiver, args[0]); 88 } else { 89 return implementation(receiver); 90 } 91 }); 92 } 93 %SetNativeFlag(boundMethod); 94 receiver[internalName] = boundMethod; 95 } 96 return receiver[internalName]; 97 } 98 ); 99} 100 101function IntlConstruct(receiver, constructor, create, newTarget, args, 102 compat) { 103 var locales = args[0]; 104 var options = args[1]; 105 106 var instance = create(locales, options); 107 108 if (compat && IS_UNDEFINED(newTarget) && receiver instanceof constructor) { 109 %object_define_property(receiver, IntlFallbackSymbol, { value: instance }); 110 return receiver; 111 } 112 113 return instance; 114} 115 116 117 118// ------------------------------------------------------------------- 119 120/** 121 * Caches available locales for each service. 122 */ 123var AVAILABLE_LOCALES = { 124 __proto__ : null, 125 'collator': UNDEFINED, 126 'numberformat': UNDEFINED, 127 'dateformat': UNDEFINED, 128 'breakiterator': UNDEFINED, 129 'pluralrules': UNDEFINED, 130 'relativetimeformat': UNDEFINED, 131 'listformat': UNDEFINED, 132}; 133 134/** 135 * Unicode extension regular expression. 136 */ 137var UNICODE_EXTENSION_RE = UNDEFINED; 138 139function GetUnicodeExtensionRE() { 140 if (IS_UNDEFINED(UNDEFINED)) { 141 UNICODE_EXTENSION_RE = new GlobalRegExp('-u(-[a-z0-9]{2,8})+', 'g'); 142 } 143 return UNICODE_EXTENSION_RE; 144} 145 146/** 147 * Matches any Unicode extension. 148 */ 149var ANY_EXTENSION_RE = UNDEFINED; 150 151function GetAnyExtensionRE() { 152 if (IS_UNDEFINED(ANY_EXTENSION_RE)) { 153 ANY_EXTENSION_RE = new GlobalRegExp('-[a-z0-9]{1}-.*', 'g'); 154 } 155 return ANY_EXTENSION_RE; 156} 157 158/** 159 * Replace quoted text (single quote, anything but the quote and quote again). 160 */ 161var QUOTED_STRING_RE = UNDEFINED; 162 163function GetQuotedStringRE() { 164 if (IS_UNDEFINED(QUOTED_STRING_RE)) { 165 QUOTED_STRING_RE = new GlobalRegExp("'[^']+'", 'g'); 166 } 167 return QUOTED_STRING_RE; 168} 169 170/** 171 * Matches valid service name. 172 */ 173var SERVICE_RE = UNDEFINED; 174 175function GetServiceRE() { 176 if (IS_UNDEFINED(SERVICE_RE)) { 177 SERVICE_RE = 178 new GlobalRegExp('^(' + %_Call(ArrayJoin, ObjectKeys(AVAILABLE_LOCALES), '|') + ')$'); 179 } 180 return SERVICE_RE; 181} 182 183/** 184 * Matches valid IANA time zone names. 185 */ 186var TIMEZONE_NAME_CHECK_RE = UNDEFINED; 187var GMT_OFFSET_TIMEZONE_NAME_CHECK_RE = UNDEFINED; 188 189function GetTimezoneNameCheckRE() { 190 if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) { 191 TIMEZONE_NAME_CHECK_RE = new GlobalRegExp( 192 '^([A-Za-z]+)/([A-Za-z_-]+)((?:\/[A-Za-z_-]+)+)*$'); 193 } 194 return TIMEZONE_NAME_CHECK_RE; 195} 196 197function GetGMTOffsetTimezoneNameCheckRE() { 198 if (IS_UNDEFINED(GMT_OFFSET_TIMEZONE_NAME_CHECK_RE)) { 199 GMT_OFFSET_TIMEZONE_NAME_CHECK_RE = new GlobalRegExp( 200 '^(?:ETC/GMT)(?<offset>0|[+-](?:[0-9]|1[0-4]))$'); 201 } 202 return GMT_OFFSET_TIMEZONE_NAME_CHECK_RE; 203} 204 205/** 206 * Matches valid location parts of IANA time zone names. 207 */ 208var TIMEZONE_NAME_LOCATION_PART_RE = UNDEFINED; 209 210function GetTimezoneNameLocationPartRE() { 211 if (IS_UNDEFINED(TIMEZONE_NAME_LOCATION_PART_RE)) { 212 TIMEZONE_NAME_LOCATION_PART_RE = 213 new GlobalRegExp('^([A-Za-z]+)((?:[_-][A-Za-z]+)+)*$'); 214 } 215 return TIMEZONE_NAME_LOCATION_PART_RE; 216} 217 218 219/** 220 * Returns a getOption function that extracts property value for given 221 * options object. If property is missing it returns defaultValue. If value 222 * is out of range for that property it throws RangeError. 223 */ 224function getGetOption(options, caller) { 225 if (IS_UNDEFINED(options)) throw %make_error(kDefaultOptionsMissing, caller); 226 227 // Ecma 402 #sec-getoption 228 var getOption = function (property, type, values, fallback) { 229 // 1. Let value be ? Get(options, property). 230 var value = options[property]; 231 // 2. If value is not undefined, then 232 if (!IS_UNDEFINED(value)) { 233 switch (type) { 234 // If type is "boolean", then let value be ToBoolean(value). 235 case 'boolean': 236 value = TO_BOOLEAN(value); 237 break; 238 // If type is "string", then let value be ToString(value). 239 case 'string': 240 value = TO_STRING(value); 241 break; 242 // Assert: type is "boolean" or "string". 243 default: 244 throw %make_error(kWrongValueType); 245 } 246 247 // d. If values is not undefined, then 248 // If values does not contain an element equal to value, throw a 249 // RangeError exception. 250 if (!IS_UNDEFINED(values) && %ArrayIndexOf(values, value, 0) === -1) { 251 throw %make_range_error(kValueOutOfRange, value, caller, property); 252 } 253 254 return value; 255 } 256 257 return fallback; 258 } 259 260 return getOption; 261} 262 263 264/** 265 * Ecma 402 9.2.5 266 * TODO(jshin): relevantExtensionKeys and localeData need to be taken into 267 * account per spec. 268 * Compares a BCP 47 language priority list requestedLocales against the locales 269 * in availableLocales and determines the best available language to meet the 270 * request. Two algorithms are available to match the locales: the Lookup 271 * algorithm described in RFC 4647 section 3.4, and an implementation dependent 272 * best-fit algorithm. Independent of the locale matching algorithm, options 273 * specified through Unicode locale extension sequences are negotiated 274 * separately, taking the caller's relevant extension keys and locale data as 275 * well as client-provided options into consideration. Returns an object with 276 * a locale property whose value is the language tag of the selected locale, 277 * and properties for each key in relevantExtensionKeys providing the selected 278 * value for that key. 279 */ 280function resolveLocale(service, requestedLocales, options) { 281 requestedLocales = initializeLocaleList(requestedLocales); 282 283 var getOption = getGetOption(options, service); 284 var matcher = getOption('localeMatcher', 'string', 285 ['lookup', 'best fit'], 'best fit'); 286 var resolved; 287 if (matcher === 'lookup') { 288 resolved = lookupMatcher(service, requestedLocales); 289 } else { 290 resolved = bestFitMatcher(service, requestedLocales); 291 } 292 293 return resolved; 294} 295 296%InstallToContext([ 297 "resolve_locale", resolveLocale 298]); 299 300/** 301 * Look up the longest non-empty prefix of |locale| that is an element of 302 * |availableLocales|. Returns undefined when the |locale| is completely 303 * unsupported by |availableLocales|. 304 */ 305function bestAvailableLocale(availableLocales, locale) { 306 do { 307 if (!IS_UNDEFINED(availableLocales[locale])) { 308 return locale; 309 } 310 // Truncate locale if possible. 311 var pos = %StringLastIndexOf(locale, '-'); 312 if (pos === -1) { 313 break; 314 } 315 locale = %_Call(StringSubstring, locale, 0, pos); 316 } while (true); 317 318 return UNDEFINED; 319} 320 321 322/** 323 * Try to match any mutation of |requestedLocale| against |availableLocales|. 324 */ 325function attemptSingleLookup(availableLocales, requestedLocale) { 326 // Remove all extensions. 327 var noExtensionsLocale = %RegExpInternalReplace( 328 GetAnyExtensionRE(), requestedLocale, ''); 329 var availableLocale = bestAvailableLocale( 330 availableLocales, requestedLocale); 331 if (!IS_UNDEFINED(availableLocale)) { 332 // Return the resolved locale and extension. 333 var extensionMatch = %regexp_internal_match( 334 GetUnicodeExtensionRE(), requestedLocale); 335 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0]; 336 return { 337 __proto__: null, 338 locale: availableLocale, 339 extension: extension, 340 localeWithExtension: availableLocale + extension, 341 }; 342 } 343 return UNDEFINED; 344} 345 346 347/** 348 * Returns best matched supported locale and extension info using basic 349 * lookup algorithm. 350 */ 351function lookupMatcher(service, requestedLocales) { 352 if (IS_NULL(%regexp_internal_match(GetServiceRE(), service))) { 353 throw %make_error(kWrongServiceType, service); 354 } 355 356 var availableLocales = getAvailableLocalesOf(service); 357 358 for (var i = 0; i < requestedLocales.length; ++i) { 359 var result = attemptSingleLookup(availableLocales, requestedLocales[i]); 360 if (!IS_UNDEFINED(result)) { 361 return result; 362 } 363 } 364 365 var defLocale = %GetDefaultICULocale(); 366 367 // While ECMA-402 returns defLocale directly, we have to check if it is 368 // supported, as such support is not guaranteed. 369 var result = attemptSingleLookup(availableLocales, defLocale); 370 if (!IS_UNDEFINED(result)) { 371 return result; 372 } 373 374 // Didn't find a match, return default. 375 return { 376 __proto__: null, 377 locale: 'und', 378 extension: '', 379 localeWithExtension: 'und', 380 }; 381} 382 383 384/** 385 * Returns best matched supported locale and extension info using 386 * implementation dependend algorithm. 387 */ 388function bestFitMatcher(service, requestedLocales) { 389 // TODO(cira): implement better best fit algorithm. 390 return lookupMatcher(service, requestedLocales); 391} 392 393/** 394 * Populates internalOptions object with boolean key-value pairs 395 * from extensionMap and options. 396 * Returns filtered extension (number and date format constructors use 397 * Unicode extensions for passing parameters to ICU). 398 * It's used for extension-option pairs only, e.g. kn-normalization, but not 399 * for 'sensitivity' since it doesn't have extension equivalent. 400 * Extensions like nu and ca don't have options equivalent, so we place 401 * undefined in the map.property to denote that. 402 */ 403function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) { 404 var extension = ''; 405 406 var updateExtension = function updateExtension(key, value) { 407 return '-' + key + '-' + TO_STRING(value); 408 } 409 410 var updateProperty = function updateProperty(property, type, value) { 411 if (type === 'boolean' && (typeof value === 'string')) { 412 value = (value === 'true') ? true : false; 413 } 414 415 if (!IS_UNDEFINED(property)) { 416 %DefineWEProperty(outOptions, property, value); 417 } 418 } 419 420 for (var key in keyValues) { 421 if (HAS_OWN_PROPERTY(keyValues, key)) { 422 var value = UNDEFINED; 423 var map = keyValues[key]; 424 if (!IS_UNDEFINED(map.property)) { 425 // This may return true if user specifies numeric: 'false', since 426 // Boolean('nonempty') === true. 427 value = getOption(map.property, map.type, map.values); 428 } 429 if (!IS_UNDEFINED(value)) { 430 updateProperty(map.property, map.type, value); 431 extension += updateExtension(key, value); 432 continue; 433 } 434 // User options didn't have it, check Unicode extension. 435 // Here we want to convert strings 'true', 'false' into proper Boolean 436 // values (not a user error). 437 if (HAS_OWN_PROPERTY(extensionMap, key)) { 438 value = extensionMap[key]; 439 if (!IS_UNDEFINED(value)) { 440 updateProperty(map.property, map.type, value); 441 extension += updateExtension(key, value); 442 } else if (map.type === 'boolean') { 443 // Boolean keys are allowed not to have values in Unicode extension. 444 // Those default to true. 445 updateProperty(map.property, map.type, true); 446 extension += updateExtension(key, true); 447 } 448 } 449 } 450 } 451 452 return extension === ''? '' : '-u' + extension; 453} 454 455 456/** 457 * Given an array-like, outputs an Array with the numbered 458 * properties copied over and defined 459 * configurable: false, writable: false, enumerable: true. 460 * When |expandable| is true, the result array can be expanded. 461 */ 462function freezeArray(input) { 463 var array = []; 464 var l = input.length; 465 for (var i = 0; i < l; i++) { 466 if (i in input) { 467 %object_define_property(array, i, {value: input[i], 468 configurable: false, 469 writable: false, 470 enumerable: true}); 471 } 472 } 473 474 %object_define_property(array, 'length', {value: l, writable: false}); 475 return array; 476} 477 478/* Make JS array[] out of InternalArray */ 479function makeArray(input) { 480 var array = []; 481 %MoveArrayContents(input, array); 482 return array; 483} 484 485/** 486 * Returns an Object that contains all of supported locales for a given 487 * service. 488 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ 489 * that is supported. This is required by the spec. 490 */ 491function getAvailableLocalesOf(service) { 492 // Cache these, they don't ever change per service. 493 if (!IS_UNDEFINED(AVAILABLE_LOCALES[service])) { 494 return AVAILABLE_LOCALES[service]; 495 } 496 497 var available = %AvailableLocalesOf(service); 498 499 for (var i in available) { 500 if (HAS_OWN_PROPERTY(available, i)) { 501 var parts = %regexp_internal_match( 502 /^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/, i); 503 if (!IS_NULL(parts)) { 504 // Build xx-ZZ. We don't care about the actual value, 505 // as long it's not undefined. 506 available[parts[1] + '-' + parts[3]] = null; 507 } 508 } 509 } 510 511 AVAILABLE_LOCALES[service] = available; 512 513 return available; 514} 515 516 517/** 518 * Defines a property and sets writable, enumerable and configurable to true. 519 */ 520function defineWECProperty(object, property, value) { 521 %object_define_property(object, property, {value: value, 522 writable: true, 523 enumerable: true, 524 configurable: true}); 525} 526 527 528/** 529 * Adds property to an object if the value is not undefined. 530 * Sets all descriptors to true. 531 */ 532function addWECPropertyIfDefined(object, property, value) { 533 if (!IS_UNDEFINED(value)) { 534 defineWECProperty(object, property, value); 535 } 536} 537 538 539/** 540 * Returns titlecased word, aMeRricA -> America. 541 */ 542function toTitleCaseWord(word) { 543 return %StringToUpperCaseIntl(%_Call(StringSubstr, word, 0, 1)) + 544 %StringToLowerCaseIntl(%_Call(StringSubstr, word, 1)); 545} 546 547/** 548 * Returns titlecased location, bueNos_airES -> Buenos_Aires 549 * or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only 550 * deals with ASCII only characters. 551 * 'of', 'au' and 'es' are special-cased and lowercased. 552 */ 553function toTitleCaseTimezoneLocation(location) { 554 var match = %regexp_internal_match(GetTimezoneNameLocationPartRE(), location) 555 if (IS_NULL(match)) throw %make_range_error(kExpectedLocation, location); 556 557 var result = toTitleCaseWord(match[1]); 558 if (!IS_UNDEFINED(match[2]) && 2 < match.length) { 559 // The first character is a separator, '_' or '-'. 560 // None of IANA zone names has both '_' and '-'. 561 var separator = %_Call(StringSubstring, match[2], 0, 1); 562 var parts = %StringSplit(match[2], separator, kMaxUint32); 563 for (var i = 1; i < parts.length; i++) { 564 var part = parts[i] 565 var lowercasedPart = %StringToLowerCaseIntl(part); 566 result = result + separator + 567 ((lowercasedPart !== 'es' && 568 lowercasedPart !== 'of' && lowercasedPart !== 'au') ? 569 toTitleCaseWord(part) : lowercasedPart); 570 } 571 } 572 return result; 573} 574 575 576/** 577 * Returns an InternalArray where all locales are canonicalized and duplicates 578 * removed. 579 * Throws on locales that are not well formed BCP47 tags. 580 * ECMA 402 8.2.1 steps 1 (ECMA 402 9.2.1) and 2. 581 */ 582function canonicalizeLocaleList(locales) { 583 var seen = new InternalArray(); 584 if (!IS_UNDEFINED(locales)) { 585 // We allow single string localeID. 586 if (typeof locales === 'string') { 587 %_Call(ArrayPush, seen, %CanonicalizeLanguageTag(locales)); 588 return seen; 589 } 590 591 var o = TO_OBJECT(locales); 592 var len = TO_LENGTH(o.length); 593 594 for (var k = 0; k < len; k++) { 595 if (k in o) { 596 var value = o[k]; 597 598 var tag = %CanonicalizeLanguageTag(value); 599 600 if (%ArrayIndexOf(seen, tag, 0) === -1) { 601 %_Call(ArrayPush, seen, tag); 602 } 603 } 604 } 605 } 606 607 return seen; 608} 609 610// TODO(ftang): remove the %InstallToContext once 611// initializeLocaleList is available in C++ 612// https://bugs.chromium.org/p/v8/issues/detail?id=7987 613%InstallToContext([ 614 "canonicalize_locale_list", canonicalizeLocaleList 615]); 616 617 618function initializeLocaleList(locales) { 619 return freezeArray(canonicalizeLocaleList(locales)); 620} 621 622// ECMA 402 section 8.2.1 623DEFINE_METHOD( 624 GlobalIntl, 625 getCanonicalLocales(locales) { 626 return makeArray(canonicalizeLocaleList(locales)); 627 } 628); 629 630/** 631 * Collator resolvedOptions method. 632 */ 633DEFINE_METHOD( 634 GlobalIntlCollator.prototype, 635 resolvedOptions() { 636 return %CollatorResolvedOptions(this); 637 } 638); 639 640 641/** 642 * Returns the subset of the given locale list for which this locale list 643 * has a matching (possibly fallback) locale. Locales appear in the same 644 * order in the returned list as in the input list. 645 * Options are optional parameter. 646 */ 647DEFINE_METHOD( 648 GlobalIntlCollator, 649 supportedLocalesOf(locales) { 650 return %SupportedLocalesOf('collator', locales, arguments[1]); 651 } 652); 653 654 655DEFINE_METHOD( 656 GlobalIntlPluralRules.prototype, 657 resolvedOptions() { 658 return %PluralRulesResolvedOptions(this); 659 } 660); 661 662DEFINE_METHOD( 663 GlobalIntlPluralRules, 664 supportedLocalesOf(locales) { 665 return %SupportedLocalesOf('pluralrules', locales, arguments[1]); 666 } 667); 668 669DEFINE_METHOD( 670 GlobalIntlPluralRules.prototype, 671 select(value) { 672 return %PluralRulesSelect(this, TO_NUMBER(value) + 0); 673 } 674); 675 676// ECMA 402 #sec-setnfdigitoptions 677// SetNumberFormatDigitOptions ( intlObj, options, mnfdDefault, mxfdDefault ) 678function SetNumberFormatDigitOptions(internalOptions, options, 679 mnfdDefault, mxfdDefault) { 680 // Digit ranges. 681 var mnid = %GetNumberOption(options, 'minimumIntegerDigits', 1, 21, 1); 682 %DefineWEProperty(internalOptions, 'minimumIntegerDigits', mnid); 683 684 var mnfd = %GetNumberOption(options, 'minimumFractionDigits', 0, 20, 685 mnfdDefault); 686 %DefineWEProperty(internalOptions, 'minimumFractionDigits', mnfd); 687 688 var mxfdActualDefault = MathMax(mnfd, mxfdDefault); 689 690 var mxfd = %GetNumberOption(options, 'maximumFractionDigits', mnfd, 20, 691 mxfdActualDefault); 692 693 %DefineWEProperty(internalOptions, 'maximumFractionDigits', mxfd); 694 695 var mnsd = options['minimumSignificantDigits']; 696 var mxsd = options['maximumSignificantDigits']; 697 if (!IS_UNDEFINED(mnsd) || !IS_UNDEFINED(mxsd)) { 698 mnsd = %DefaultNumberOption(mnsd, 1, 21, 1, 'minimumSignificantDigits'); 699 %DefineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd); 700 701 mxsd = %DefaultNumberOption(mxsd, mnsd, 21, 21, 'maximumSignificantDigits'); 702 %DefineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd); 703 } 704} 705 706/** 707 * Initializes the given object so it's a valid NumberFormat instance. 708 * Useful for subclassing. 709 */ 710function CreateNumberFormat(locales, options) { 711 if (IS_UNDEFINED(options)) { 712 options = {__proto__: null}; 713 } else { 714 options = TO_OBJECT(options); 715 } 716 717 var getOption = getGetOption(options, 'numberformat'); 718 719 var locale = resolveLocale('numberformat', locales, options); 720 721 var internalOptions = {__proto__: null}; 722 %DefineWEProperty(internalOptions, 'style', getOption( 723 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal')); 724 725 var currency = getOption('currency', 'string'); 726 if (!IS_UNDEFINED(currency) && !%IsWellFormedCurrencyCode(currency)) { 727 throw %make_range_error(kInvalidCurrencyCode, currency); 728 } 729 730 if (internalOptions.style === 'currency' && IS_UNDEFINED(currency)) { 731 throw %make_type_error(kCurrencyCode); 732 } 733 734 var mnfdDefault, mxfdDefault; 735 736 var currencyDisplay = getOption( 737 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol'); 738 if (internalOptions.style === 'currency') { 739 %DefineWEProperty(internalOptions, 'currency', %StringToUpperCaseIntl(currency)); 740 %DefineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay); 741 742 mnfdDefault = mxfdDefault = %CurrencyDigits(internalOptions.currency); 743 } else { 744 mnfdDefault = 0; 745 mxfdDefault = internalOptions.style === 'percent' ? 0 : 3; 746 } 747 748 SetNumberFormatDigitOptions(internalOptions, options, mnfdDefault, 749 mxfdDefault); 750 751 // Grouping. 752 %DefineWEProperty(internalOptions, 'useGrouping', getOption( 753 'useGrouping', 'boolean', UNDEFINED, true)); 754 755 // ICU prefers options to be passed using -u- extension key/values for 756 // number format, so we need to build that. 757 var extensionMap = %ParseExtension(locale.extension); 758 759 /** 760 * Map of Unicode extensions to option properties, and their values and types, 761 * for a number format. 762 */ 763 var NUMBER_FORMAT_KEY_MAP = { 764 __proto__: null, 765 'nu': {__proto__: null, 'property': UNDEFINED, 'type': 'string'} 766 }; 767 768 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP, 769 getOption, internalOptions); 770 771 var requestedLocale = locale.locale + extension; 772 var resolved = %object_define_properties({__proto__: null}, { 773 currency: {writable: true}, 774 currencyDisplay: {writable: true}, 775 locale: {writable: true}, 776 maximumFractionDigits: {writable: true}, 777 minimumFractionDigits: {writable: true}, 778 minimumIntegerDigits: {writable: true}, 779 numberingSystem: {writable: true}, 780 requestedLocale: {value: requestedLocale, writable: true}, 781 style: {value: internalOptions.style, writable: true}, 782 useGrouping: {writable: true} 783 }); 784 if (HAS_OWN_PROPERTY(internalOptions, 'minimumSignificantDigits')) { 785 %DefineWEProperty(resolved, 'minimumSignificantDigits', UNDEFINED); 786 } 787 if (HAS_OWN_PROPERTY(internalOptions, 'maximumSignificantDigits')) { 788 %DefineWEProperty(resolved, 'maximumSignificantDigits', UNDEFINED); 789 } 790 var numberFormat = %CreateNumberFormat(requestedLocale, internalOptions, 791 resolved); 792 793 if (internalOptions.style === 'currency') { 794 %object_define_property(resolved, 'currencyDisplay', 795 {value: currencyDisplay, writable: true}); 796 } 797 798 %MarkAsInitializedIntlObjectOfType(numberFormat, NUMBER_FORMAT_TYPE); 799 numberFormat[resolvedSymbol] = resolved; 800 801 return numberFormat; 802} 803 804 805/** 806 * Constructs Intl.NumberFormat object given optional locales and options 807 * parameters. 808 * 809 * @constructor 810 */ 811function NumberFormatConstructor() { 812 return IntlConstruct(this, GlobalIntlNumberFormat, CreateNumberFormat, 813 new.target, arguments, true); 814} 815%SetCode(GlobalIntlNumberFormat, NumberFormatConstructor); 816 817 818/** 819 * NumberFormat resolvedOptions method. 820 */ 821DEFINE_METHOD( 822 GlobalIntlNumberFormat.prototype, 823 resolvedOptions() { 824 var methodName = 'resolvedOptions'; 825 if(!IS_RECEIVER(this)) { 826 throw %make_type_error(kIncompatibleMethodReceiver, methodName, this); 827 } 828 var format = %IntlUnwrapReceiver(this, NUMBER_FORMAT_TYPE, 829 GlobalIntlNumberFormat, 830 methodName, true); 831 var result = { 832 locale: format[resolvedSymbol].locale, 833 numberingSystem: format[resolvedSymbol].numberingSystem, 834 style: format[resolvedSymbol].style, 835 useGrouping: format[resolvedSymbol].useGrouping, 836 minimumIntegerDigits: format[resolvedSymbol].minimumIntegerDigits, 837 minimumFractionDigits: format[resolvedSymbol].minimumFractionDigits, 838 maximumFractionDigits: format[resolvedSymbol].maximumFractionDigits, 839 }; 840 841 if (result.style === 'currency') { 842 defineWECProperty(result, 'currency', format[resolvedSymbol].currency); 843 defineWECProperty(result, 'currencyDisplay', 844 format[resolvedSymbol].currencyDisplay); 845 } 846 847 if (HAS_OWN_PROPERTY(format[resolvedSymbol], 'minimumSignificantDigits')) { 848 defineWECProperty(result, 'minimumSignificantDigits', 849 format[resolvedSymbol].minimumSignificantDigits); 850 } 851 852 if (HAS_OWN_PROPERTY(format[resolvedSymbol], 'maximumSignificantDigits')) { 853 defineWECProperty(result, 'maximumSignificantDigits', 854 format[resolvedSymbol].maximumSignificantDigits); 855 } 856 857 return result; 858 } 859); 860 861 862/** 863 * Returns the subset of the given locale list for which this locale list 864 * has a matching (possibly fallback) locale. Locales appear in the same 865 * order in the returned list as in the input list. 866 * Options are optional parameter. 867 */ 868DEFINE_METHOD( 869 GlobalIntlNumberFormat, 870 supportedLocalesOf(locales) { 871 return %SupportedLocalesOf('numberformat', locales, arguments[1]); 872 } 873); 874 875/** 876 * Returns a string that matches LDML representation of the options object. 877 */ 878function toLDMLString(options) { 879 var getOption = getGetOption(options, 'dateformat'); 880 881 var ldmlString = ''; 882 883 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']); 884 ldmlString += appendToLDMLString( 885 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'}); 886 887 option = getOption('era', 'string', ['narrow', 'short', 'long']); 888 ldmlString += appendToLDMLString( 889 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'}); 890 891 option = getOption('year', 'string', ['2-digit', 'numeric']); 892 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'}); 893 894 option = getOption('month', 'string', 895 ['2-digit', 'numeric', 'narrow', 'short', 'long']); 896 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M', 897 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'}); 898 899 option = getOption('day', 'string', ['2-digit', 'numeric']); 900 ldmlString += appendToLDMLString( 901 option, {'2-digit': 'dd', 'numeric': 'd'}); 902 903 var hr12 = getOption('hour12', 'boolean'); 904 option = getOption('hour', 'string', ['2-digit', 'numeric']); 905 if (IS_UNDEFINED(hr12)) { 906 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'}); 907 } else if (hr12 === true) { 908 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'}); 909 } else { 910 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'}); 911 } 912 913 option = getOption('minute', 'string', ['2-digit', 'numeric']); 914 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'}); 915 916 option = getOption('second', 'string', ['2-digit', 'numeric']); 917 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'}); 918 919 option = getOption('timeZoneName', 'string', ['short', 'long']); 920 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'}); 921 922 return ldmlString; 923} 924 925 926/** 927 * Returns either LDML equivalent of the current option or empty string. 928 */ 929function appendToLDMLString(option, pairs) { 930 if (!IS_UNDEFINED(option)) { 931 return pairs[option]; 932 } else { 933 return ''; 934 } 935} 936 937 938/** 939 * Returns object that matches LDML representation of the date. 940 */ 941function fromLDMLString(ldmlString) { 942 // First remove '' quoted text, so we lose 'Uhr' strings. 943 ldmlString = %RegExpInternalReplace(GetQuotedStringRE(), ldmlString, ''); 944 945 var options = {__proto__: null}; 946 var match = %regexp_internal_match(/E{3,5}/, ldmlString); 947 options = appendToDateTimeObject( 948 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'}); 949 950 match = %regexp_internal_match(/G{3,5}/, ldmlString); 951 options = appendToDateTimeObject( 952 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'}); 953 954 match = %regexp_internal_match(/y{1,2}/, ldmlString); 955 options = appendToDateTimeObject( 956 options, 'year', match, {y: 'numeric', yy: '2-digit'}); 957 958 match = %regexp_internal_match(/M{1,5}/, ldmlString); 959 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit', 960 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'}); 961 962 // Sometimes we get L instead of M for month - standalone name. 963 match = %regexp_internal_match(/L{1,5}/, ldmlString); 964 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit', 965 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'}); 966 967 match = %regexp_internal_match(/d{1,2}/, ldmlString); 968 options = appendToDateTimeObject( 969 options, 'day', match, {d: 'numeric', dd: '2-digit'}); 970 971 match = %regexp_internal_match(/h{1,2}/, ldmlString); 972 if (match !== null) { 973 options['hour12'] = true; 974 } 975 options = appendToDateTimeObject( 976 options, 'hour', match, {h: 'numeric', hh: '2-digit'}); 977 978 match = %regexp_internal_match(/H{1,2}/, ldmlString); 979 if (match !== null) { 980 options['hour12'] = false; 981 } 982 options = appendToDateTimeObject( 983 options, 'hour', match, {H: 'numeric', HH: '2-digit'}); 984 985 match = %regexp_internal_match(/m{1,2}/, ldmlString); 986 options = appendToDateTimeObject( 987 options, 'minute', match, {m: 'numeric', mm: '2-digit'}); 988 989 match = %regexp_internal_match(/s{1,2}/, ldmlString); 990 options = appendToDateTimeObject( 991 options, 'second', match, {s: 'numeric', ss: '2-digit'}); 992 993 match = %regexp_internal_match(/z|zzzz/, ldmlString); 994 options = appendToDateTimeObject( 995 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'}); 996 997 return options; 998} 999 1000 1001function appendToDateTimeObject(options, option, match, pairs) { 1002 if (IS_NULL(match)) { 1003 if (!HAS_OWN_PROPERTY(options, option)) { 1004 %DefineWEProperty(options, option, UNDEFINED); 1005 } 1006 return options; 1007 } 1008 1009 var property = match[0]; 1010 %DefineWEProperty(options, option, pairs[property]); 1011 1012 return options; 1013} 1014 1015 1016/** 1017 * Returns options with at least default values in it. 1018 */ 1019function toDateTimeOptions(options, required, defaults) { 1020 if (IS_UNDEFINED(options)) { 1021 options = {__proto__: null}; 1022 } else { 1023 options = TO_OBJECT(options); 1024 } 1025 1026 options = %object_create(options); 1027 1028 var needsDefault = true; 1029 if ((required === 'date' || required === 'any') && 1030 (!IS_UNDEFINED(options.weekday) || !IS_UNDEFINED(options.year) || 1031 !IS_UNDEFINED(options.month) || !IS_UNDEFINED(options.day))) { 1032 needsDefault = false; 1033 } 1034 1035 if ((required === 'time' || required === 'any') && 1036 (!IS_UNDEFINED(options.hour) || !IS_UNDEFINED(options.minute) || 1037 !IS_UNDEFINED(options.second))) { 1038 needsDefault = false; 1039 } 1040 1041 if (needsDefault && (defaults === 'date' || defaults === 'all')) { 1042 %object_define_property(options, 'year', {value: 'numeric', 1043 writable: true, 1044 enumerable: true, 1045 configurable: true}); 1046 %object_define_property(options, 'month', {value: 'numeric', 1047 writable: true, 1048 enumerable: true, 1049 configurable: true}); 1050 %object_define_property(options, 'day', {value: 'numeric', 1051 writable: true, 1052 enumerable: true, 1053 configurable: true}); 1054 } 1055 1056 if (needsDefault && (defaults === 'time' || defaults === 'all')) { 1057 %object_define_property(options, 'hour', {value: 'numeric', 1058 writable: true, 1059 enumerable: true, 1060 configurable: true}); 1061 %object_define_property(options, 'minute', {value: 'numeric', 1062 writable: true, 1063 enumerable: true, 1064 configurable: true}); 1065 %object_define_property(options, 'second', {value: 'numeric', 1066 writable: true, 1067 enumerable: true, 1068 configurable: true}); 1069 } 1070 1071 return options; 1072} 1073 1074 1075/** 1076 * Initializes the given object so it's a valid DateTimeFormat instance. 1077 * Useful for subclassing. 1078 */ 1079function CreateDateTimeFormat(locales, options) { 1080 if (IS_UNDEFINED(options)) { 1081 options = {__proto__: null}; 1082 } 1083 1084 var locale = resolveLocale('dateformat', locales, options); 1085 1086 options = %ToDateTimeOptions(options, 'any', 'date'); 1087 1088 var getOption = getGetOption(options, 'dateformat'); 1089 1090 // We implement only best fit algorithm, but still need to check 1091 // if the formatMatcher values are in range. 1092 var matcher = getOption('formatMatcher', 'string', 1093 ['basic', 'best fit'], 'best fit'); 1094 1095 // Build LDML string for the skeleton that we pass to the formatter. 1096 var ldmlString = toLDMLString(options); 1097 1098 // Filter out supported extension keys so we know what to put in resolved 1099 // section later on. 1100 // We need to pass calendar and number system to the method. 1101 var tz = canonicalizeTimeZoneID(options.timeZone); 1102 1103 // ICU prefers options to be passed using -u- extension key/values, so 1104 // we need to build that. 1105 var internalOptions = {__proto__: null}; 1106 var extensionMap = %ParseExtension(locale.extension); 1107 1108 /** 1109 * Map of Unicode extensions to option properties, and their values and types, 1110 * for a date/time format. 1111 */ 1112 var DATETIME_FORMAT_KEY_MAP = { 1113 __proto__: null, 1114 'ca': {__proto__: null, 'property': UNDEFINED, 'type': 'string'}, 1115 'nu': {__proto__: null, 'property': UNDEFINED, 'type': 'string'} 1116 }; 1117 1118 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP, 1119 getOption, internalOptions); 1120 1121 var requestedLocale = locale.locale + extension; 1122 var resolved = %object_define_properties({__proto__: null}, { 1123 calendar: {writable: true}, 1124 day: {writable: true}, 1125 era: {writable: true}, 1126 hour12: {writable: true}, 1127 hour: {writable: true}, 1128 locale: {writable: true}, 1129 minute: {writable: true}, 1130 month: {writable: true}, 1131 numberingSystem: {writable: true}, 1132 [patternSymbol]: {writable: true}, 1133 requestedLocale: {value: requestedLocale, writable: true}, 1134 second: {writable: true}, 1135 timeZone: {writable: true}, 1136 timeZoneName: {writable: true}, 1137 tz: {value: tz, writable: true}, 1138 weekday: {writable: true}, 1139 year: {writable: true} 1140 }); 1141 1142 var dateFormat = %CreateDateTimeFormat( 1143 requestedLocale, 1144 {__proto__: null, skeleton: ldmlString, timeZone: tz}, resolved); 1145 1146 if (resolved.timeZone === "Etc/Unknown") { 1147 throw %make_range_error(kInvalidTimeZone, tz); 1148 } 1149 1150 %MarkAsInitializedIntlObjectOfType(dateFormat, DATE_TIME_FORMAT_TYPE); 1151 dateFormat[resolvedSymbol] = resolved; 1152 1153 return dateFormat; 1154} 1155 1156 1157/** 1158 * Constructs Intl.DateTimeFormat object given optional locales and options 1159 * parameters. 1160 * 1161 * @constructor 1162 */ 1163function DateTimeFormatConstructor() { 1164 return IntlConstruct(this, GlobalIntlDateTimeFormat, CreateDateTimeFormat, 1165 new.target, arguments, true); 1166} 1167%SetCode(GlobalIntlDateTimeFormat, DateTimeFormatConstructor); 1168 1169 1170/** 1171 * DateTimeFormat resolvedOptions method. 1172 */ 1173DEFINE_METHOD( 1174 GlobalIntlDateTimeFormat.prototype, 1175 resolvedOptions() { 1176 var methodName = 'resolvedOptions'; 1177 if(!IS_RECEIVER(this)) { 1178 throw %make_type_error(kIncompatibleMethodReceiver, methodName, this); 1179 } 1180 var format = %IntlUnwrapReceiver(this, DATE_TIME_FORMAT_TYPE, 1181 GlobalIntlDateTimeFormat, 1182 methodName, true); 1183 1184 /** 1185 * Maps ICU calendar names to LDML/BCP47 types for key 'ca'. 1186 * See typeMap section in third_party/icu/source/data/misc/keyTypeData.txt 1187 * and 1188 * http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml 1189 */ 1190 var ICU_CALENDAR_MAP = { 1191 __proto__: null, 1192 'gregorian': 'gregory', 1193 'ethiopic-amete-alem': 'ethioaa' 1194 }; 1195 1196 var fromPattern = fromLDMLString(format[resolvedSymbol][patternSymbol]); 1197 var userCalendar = ICU_CALENDAR_MAP[format[resolvedSymbol].calendar]; 1198 if (IS_UNDEFINED(userCalendar)) { 1199 // No match means that ICU's legacy name is identical to LDML/BCP type. 1200 userCalendar = format[resolvedSymbol].calendar; 1201 } 1202 1203 var result = { 1204 locale: format[resolvedSymbol].locale, 1205 numberingSystem: format[resolvedSymbol].numberingSystem, 1206 calendar: userCalendar, 1207 timeZone: format[resolvedSymbol].timeZone 1208 }; 1209 1210 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName); 1211 addWECPropertyIfDefined(result, 'era', fromPattern.era); 1212 addWECPropertyIfDefined(result, 'year', fromPattern.year); 1213 addWECPropertyIfDefined(result, 'month', fromPattern.month); 1214 addWECPropertyIfDefined(result, 'day', fromPattern.day); 1215 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday); 1216 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12); 1217 addWECPropertyIfDefined(result, 'hour', fromPattern.hour); 1218 addWECPropertyIfDefined(result, 'minute', fromPattern.minute); 1219 addWECPropertyIfDefined(result, 'second', fromPattern.second); 1220 1221 return result; 1222 } 1223); 1224 1225 1226/** 1227 * Returns the subset of the given locale list for which this locale list 1228 * has a matching (possibly fallback) locale. Locales appear in the same 1229 * order in the returned list as in the input list. 1230 * Options are optional parameter. 1231 */ 1232DEFINE_METHOD( 1233 GlobalIntlDateTimeFormat, 1234 supportedLocalesOf(locales) { 1235 return %SupportedLocalesOf('dateformat', locales, arguments[1]); 1236 } 1237); 1238 1239 1240/** 1241 * Returns canonical Area/Location(/Location) name, or throws an exception 1242 * if the zone name is invalid IANA name. 1243 */ 1244function canonicalizeTimeZoneID(tzID) { 1245 // Skip undefined zones. 1246 if (IS_UNDEFINED(tzID)) { 1247 return tzID; 1248 } 1249 1250 // Convert zone name to string. 1251 tzID = TO_STRING(tzID); 1252 1253 // Special case handling (UTC, GMT). 1254 var upperID = %StringToUpperCaseIntl(tzID); 1255 if (upperID === 'UTC' || upperID === 'GMT' || 1256 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') { 1257 return 'UTC'; 1258 } 1259 1260 // We expect only _, '-' and / beside ASCII letters. 1261 // All inputs should conform to Area/Location(/Location)*, or Etc/GMT* . 1262 // TODO(jshin): 1. Support 'GB-Eire", 'EST5EDT", "ROK', 'US/*', 'NZ' and many 1263 // other aliases/linked names when moving timezone validation code to C++. 1264 // See crbug.com/364374 and crbug.com/v8/8007 . 1265 // 2. Resolve the difference betwee CLDR/ICU and IANA time zone db. 1266 // See http://unicode.org/cldr/trac/ticket/9892 and crbug.com/645807 . 1267 let match = %regexp_internal_match(GetTimezoneNameCheckRE(), tzID); 1268 if (IS_NULL(match)) { 1269 let match = 1270 %regexp_internal_match(GetGMTOffsetTimezoneNameCheckRE(), upperID); 1271 if (!IS_NULL(match) && match.length == 2) 1272 return "Etc/GMT" + match.groups.offset; 1273 else 1274 throw %make_range_error(kInvalidTimeZone, tzID); 1275 } 1276 1277 let result = toTitleCaseTimezoneLocation(match[1]) + '/' + 1278 toTitleCaseTimezoneLocation(match[2]); 1279 1280 if (!IS_UNDEFINED(match[3]) && 3 < match.length) { 1281 let locations = %StringSplit(match[3], '/', kMaxUint32); 1282 // The 1st element is empty. Starts with i=1. 1283 for (var i = 1; i < locations.length; i++) { 1284 result = result + '/' + toTitleCaseTimezoneLocation(locations[i]); 1285 } 1286 } 1287 1288 return result; 1289} 1290 1291/** 1292 * Initializes the given object so it's a valid BreakIterator instance. 1293 * Useful for subclassing. 1294 */ 1295function CreateBreakIterator(locales, options) { 1296 if (IS_UNDEFINED(options)) { 1297 options = {__proto__: null}; 1298 } 1299 1300 var getOption = getGetOption(options, 'breakiterator'); 1301 1302 var internalOptions = {__proto__: null}; 1303 1304 %DefineWEProperty(internalOptions, 'type', getOption( 1305 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word')); 1306 1307 var locale = resolveLocale('breakiterator', locales, options); 1308 var resolved = %object_define_properties({__proto__: null}, { 1309 requestedLocale: {value: locale.locale, writable: true}, 1310 type: {value: internalOptions.type, writable: true}, 1311 locale: {writable: true} 1312 }); 1313 1314 var iterator = %CreateBreakIterator(locale.locale, internalOptions, resolved); 1315 1316 %MarkAsInitializedIntlObjectOfType(iterator, BREAK_ITERATOR_TYPE); 1317 iterator[resolvedSymbol] = resolved; 1318 1319 return iterator; 1320} 1321 1322 1323/** 1324 * Constructs Intl.v8BreakIterator object given optional locales and options 1325 * parameters. 1326 * 1327 * @constructor 1328 */ 1329function v8BreakIteratorConstructor() { 1330 return IntlConstruct(this, GlobalIntlv8BreakIterator, CreateBreakIterator, 1331 new.target, arguments); 1332} 1333%SetCode(GlobalIntlv8BreakIterator, v8BreakIteratorConstructor); 1334 1335 1336/** 1337 * BreakIterator resolvedOptions method. 1338 */ 1339DEFINE_METHOD( 1340 GlobalIntlv8BreakIterator.prototype, 1341 resolvedOptions() { 1342 if (!IS_UNDEFINED(new.target)) { 1343 throw %make_type_error(kOrdinaryFunctionCalledAsConstructor); 1344 } 1345 1346 var methodName = 'resolvedOptions'; 1347 if(!IS_RECEIVER(this)) { 1348 throw %make_type_error(kIncompatibleMethodReceiver, methodName, this); 1349 } 1350 var segmenter = %IntlUnwrapReceiver(this, BREAK_ITERATOR_TYPE, 1351 GlobalIntlv8BreakIterator, methodName, 1352 false); 1353 1354 return { 1355 locale: segmenter[resolvedSymbol].locale, 1356 type: segmenter[resolvedSymbol].type 1357 }; 1358 } 1359); 1360 1361 1362/** 1363 * Returns the subset of the given locale list for which this locale list 1364 * has a matching (possibly fallback) locale. Locales appear in the same 1365 * order in the returned list as in the input list. 1366 * Options are optional parameter. 1367 */ 1368DEFINE_METHOD( 1369 GlobalIntlv8BreakIterator, 1370 supportedLocalesOf(locales) { 1371 if (!IS_UNDEFINED(new.target)) { 1372 throw %make_type_error(kOrdinaryFunctionCalledAsConstructor); 1373 } 1374 1375 return %SupportedLocalesOf('breakiterator', locales, arguments[1]); 1376 } 1377); 1378 1379 1380/** 1381 * Returns index of the first break in the string and moves current pointer. 1382 */ 1383function first(iterator) { 1384 return %BreakIteratorFirst(iterator); 1385} 1386 1387 1388/** 1389 * Returns the index of the next break and moves the pointer. 1390 */ 1391function next(iterator) { 1392 return %BreakIteratorNext(iterator); 1393} 1394 1395 1396/** 1397 * Returns index of the current break. 1398 */ 1399function current(iterator) { 1400 return %BreakIteratorCurrent(iterator); 1401} 1402 1403 1404/** 1405 * Returns type of the current break. 1406 */ 1407function breakType(iterator) { 1408 return %BreakIteratorBreakType(iterator); 1409} 1410 1411 1412AddBoundMethod(GlobalIntlv8BreakIterator, 'first', first, 0, 1413 BREAK_ITERATOR_TYPE, false); 1414AddBoundMethod(GlobalIntlv8BreakIterator, 'next', next, 0, 1415 BREAK_ITERATOR_TYPE, false); 1416AddBoundMethod(GlobalIntlv8BreakIterator, 'current', current, 0, 1417 BREAK_ITERATOR_TYPE, false); 1418AddBoundMethod(GlobalIntlv8BreakIterator, 'breakType', breakType, 0, 1419 BREAK_ITERATOR_TYPE, false); 1420 1421// Save references to Intl objects and methods we use, for added security. 1422var savedObjects = { 1423 __proto__: null, 1424 'collator': GlobalIntlCollator, 1425 'numberformat': GlobalIntlNumberFormat, 1426 'dateformatall': GlobalIntlDateTimeFormat, 1427 'dateformatdate': GlobalIntlDateTimeFormat, 1428 'dateformattime': GlobalIntlDateTimeFormat 1429}; 1430 1431 1432// Default (created with undefined locales and options parameters) collator, 1433// number and date format instances. They'll be created as needed. 1434var defaultObjects = { 1435 __proto__: null, 1436 'collator': UNDEFINED, 1437 'numberformat': UNDEFINED, 1438 'dateformatall': UNDEFINED, 1439 'dateformatdate': UNDEFINED, 1440 'dateformattime': UNDEFINED, 1441}; 1442 1443function clearDefaultObjects() { 1444 defaultObjects['dateformatall'] = UNDEFINED; 1445 defaultObjects['dateformatdate'] = UNDEFINED; 1446 defaultObjects['dateformattime'] = UNDEFINED; 1447} 1448 1449var date_cache_version = 0; 1450 1451function checkDateCacheCurrent() { 1452 var new_date_cache_version = %DateCacheVersion(); 1453 if (new_date_cache_version == date_cache_version) { 1454 return; 1455 } 1456 date_cache_version = new_date_cache_version; 1457 1458 clearDefaultObjects(); 1459} 1460 1461/** 1462 * Returns cached or newly created instance of a given service. 1463 * We cache only default instances (where no locales or options are provided). 1464 */ 1465function cachedOrNewService(service, locales, options, defaults) { 1466 var useOptions = (IS_UNDEFINED(defaults)) ? options : defaults; 1467 if (IS_UNDEFINED(locales) && IS_UNDEFINED(options)) { 1468 checkDateCacheCurrent(); 1469 if (IS_UNDEFINED(defaultObjects[service])) { 1470 defaultObjects[service] = new savedObjects[service](locales, useOptions); 1471 } 1472 return defaultObjects[service]; 1473 } 1474 return new savedObjects[service](locales, useOptions); 1475} 1476 1477// TODO(ftang) remove the %InstallToContext once 1478// cachedOrNewService is available in C++ 1479%InstallToContext([ 1480 "cached_or_new_service", cachedOrNewService 1481]); 1482 1483/** 1484 * Formats a Date object (this) using locale and options values. 1485 * If locale or options are omitted, defaults are used - both date and time are 1486 * present in the output. 1487 */ 1488DEFINE_METHOD( 1489 GlobalDate.prototype, 1490 toLocaleString() { 1491 var locales = arguments[0]; 1492 var options = arguments[1]; 1493 return %ToLocaleDateTime( 1494 this, locales, options, 'any', 'all', 'dateformatall'); 1495 } 1496); 1497 1498 1499/** 1500 * Formats a Date object (this) using locale and options values. 1501 * If locale or options are omitted, defaults are used - only date is present 1502 * in the output. 1503 */ 1504DEFINE_METHOD( 1505 GlobalDate.prototype, 1506 toLocaleDateString() { 1507 var locales = arguments[0]; 1508 var options = arguments[1]; 1509 return %ToLocaleDateTime( 1510 this, locales, options, 'date', 'date', 'dateformatdate'); 1511 } 1512); 1513 1514 1515/** 1516 * Formats a Date object (this) using locale and options values. 1517 * If locale or options are omitted, defaults are used - only time is present 1518 * in the output. 1519 */ 1520DEFINE_METHOD( 1521 GlobalDate.prototype, 1522 toLocaleTimeString() { 1523 var locales = arguments[0]; 1524 var options = arguments[1]; 1525 return %ToLocaleDateTime( 1526 this, locales, options, 'time', 'time', 'dateformattime'); 1527 } 1528); 1529 1530}) 1531