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