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