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