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