blob: 6c769a74ab79e2ea0c08737111d0e69163ed5ae0 [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 GlobalDate = global.Date;
25var GlobalNumber = global.Number;
26var GlobalRegExp = global.RegExp;
27var GlobalString = global.String;
Ben Murdochda12d292016-06-02 14:46:10 +010028var InstallFunctions = utils.InstallFunctions;
29var InstallGetter = utils.InstallGetter;
Ben Murdochc5610432016-08-08 18:44:38 +010030var InternalArray = utils.InternalArray;
Ben Murdochda12d292016-06-02 14:46:10 +010031var InternalRegExpMatch;
32var InternalRegExpReplace
Ben Murdochda12d292016-06-02 14:46:10 +010033var IsNaN;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000034var MakeError;
35var MakeRangeError;
36var MakeTypeError;
Ben Murdochda12d292016-06-02 14:46:10 +010037var ObjectHasOwnProperty = utils.ImportNow("ObjectHasOwnProperty");
38var OverrideFunction = utils.OverrideFunction;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000039var patternSymbol = utils.ImportNow("intl_pattern_symbol");
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000040var resolvedSymbol = utils.ImportNow("intl_resolved_symbol");
Ben Murdochda12d292016-06-02 14:46:10 +010041var SetFunctionName = utils.SetFunctionName;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000042var StringIndexOf;
43var StringLastIndexOf;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000044var StringSplit;
45var StringSubstr;
46var StringSubstring;
47
48utils.Import(function(from) {
49 ArrayIndexOf = from.ArrayIndexOf;
50 ArrayJoin = from.ArrayJoin;
51 ArrayPush = from.ArrayPush;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000052 IsNaN = from.IsNaN;
53 MakeError = from.MakeError;
54 MakeRangeError = from.MakeRangeError;
55 MakeTypeError = from.MakeTypeError;
Ben Murdochda12d292016-06-02 14:46:10 +010056 InternalRegExpMatch = from.InternalRegExpMatch;
57 InternalRegExpReplace = from.InternalRegExpReplace;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000058 StringIndexOf = from.StringIndexOf;
59 StringLastIndexOf = from.StringLastIndexOf;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000060 StringSplit = from.StringSplit;
61 StringSubstr = from.StringSubstr;
62 StringSubstring = from.StringSubstring;
63});
64
Ben Murdochc5610432016-08-08 18:44:38 +010065utils.ImportFromExperimental(function(from) {
66 FLAG_intl_extra = from.FLAG_intl_extra;
67});
68
Ben Murdochda12d292016-06-02 14:46:10 +010069// Utilities for definitions
70
71function InstallFunction(object, name, func) {
72 InstallFunctions(object, DONT_ENUM, [name, func]);
73}
74
75
76function InstallConstructor(object, name, func) {
77 %CheckIsBootstrapping();
78 SetFunctionName(func, name);
79 %AddNamedProperty(object, name, func, DONT_ENUM);
80 %SetNativeFlag(func);
81 %ToFastProperties(object);
82}
83
84/**
85 * Adds bound method to the prototype of the given object.
86 */
Ben Murdochc5610432016-08-08 18:44:38 +010087function AddBoundMethod(obj, methodName, implementation, length, type) {
Ben Murdochda12d292016-06-02 14:46:10 +010088 %CheckIsBootstrapping();
89 var internalName = %CreatePrivateSymbol(methodName);
90 var getter = function() {
Ben Murdochc5610432016-08-08 18:44:38 +010091 if (!%IsInitializedIntlObjectOfType(this, type)) {
Ben Murdochda12d292016-06-02 14:46:10 +010092 throw MakeTypeError(kMethodCalledOnWrongObject, methodName);
93 }
94 if (IS_UNDEFINED(this[internalName])) {
95 var boundMethod;
96 if (IS_UNDEFINED(length) || length === 2) {
97 boundMethod = (x, y) => implementation(this, x, y);
98 } else if (length === 1) {
99 boundMethod = x => implementation(this, x);
100 } else {
101 boundMethod = (...args) => {
102 // DateTimeFormat.format needs to be 0 arg method, but can stil
103 // receive optional dateValue param. If one was provided, pass it
104 // along.
105 if (args.length > 0) {
106 return implementation(this, args[0]);
107 } else {
108 return implementation(this);
109 }
110 }
111 }
112 // TODO(littledan): Once function name reform is shipped, remove the
113 // following line and wrap the boundMethod definition in an anonymous
114 // function macro.
115 %FunctionSetName(boundMethod, '__bound' + methodName + '__');
116 %FunctionRemovePrototype(boundMethod);
117 %SetNativeFlag(boundMethod);
118 this[internalName] = boundMethod;
119 }
120 return this[internalName];
121 };
122
123 InstallGetter(obj.prototype, methodName, getter, DONT_ENUM);
124}
125
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000126// -------------------------------------------------------------------
127
128var Intl = {};
129
130%AddNamedProperty(global, "Intl", Intl, DONT_ENUM);
131
132/**
133 * Caches available locales for each service.
134 */
135var AVAILABLE_LOCALES = {
136 'collator': UNDEFINED,
137 'numberformat': UNDEFINED,
138 'dateformat': UNDEFINED,
139 'breakiterator': UNDEFINED
140};
141
142/**
143 * Caches default ICU locale.
144 */
145var DEFAULT_ICU_LOCALE = UNDEFINED;
146
Ben Murdochc5610432016-08-08 18:44:38 +0100147function GetDefaultICULocaleJS() {
148 if (IS_UNDEFINED(DEFAULT_ICU_LOCALE)) {
149 DEFAULT_ICU_LOCALE = %GetDefaultICULocale();
150 }
151 return DEFAULT_ICU_LOCALE;
152}
153
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000154/**
155 * Unicode extension regular expression.
156 */
157var UNICODE_EXTENSION_RE = UNDEFINED;
158
159function GetUnicodeExtensionRE() {
160 if (IS_UNDEFINED(UNDEFINED)) {
161 UNICODE_EXTENSION_RE = new GlobalRegExp('-u(-[a-z0-9]{2,8})+', 'g');
162 }
163 return UNICODE_EXTENSION_RE;
164}
165
166/**
167 * Matches any Unicode extension.
168 */
169var ANY_EXTENSION_RE = UNDEFINED;
170
171function GetAnyExtensionRE() {
172 if (IS_UNDEFINED(ANY_EXTENSION_RE)) {
173 ANY_EXTENSION_RE = new GlobalRegExp('-[a-z0-9]{1}-.*', 'g');
174 }
175 return ANY_EXTENSION_RE;
176}
177
178/**
179 * Replace quoted text (single quote, anything but the quote and quote again).
180 */
181var QUOTED_STRING_RE = UNDEFINED;
182
183function GetQuotedStringRE() {
184 if (IS_UNDEFINED(QUOTED_STRING_RE)) {
185 QUOTED_STRING_RE = new GlobalRegExp("'[^']+'", 'g');
186 }
187 return QUOTED_STRING_RE;
188}
189
190/**
191 * Matches valid service name.
192 */
193var SERVICE_RE = UNDEFINED;
194
195function GetServiceRE() {
196 if (IS_UNDEFINED(SERVICE_RE)) {
197 SERVICE_RE =
198 new GlobalRegExp('^(collator|numberformat|dateformat|breakiterator)$');
199 }
200 return SERVICE_RE;
201}
202
203/**
204 * Validates a language tag against bcp47 spec.
205 * Actual value is assigned on first run.
206 */
207var LANGUAGE_TAG_RE = UNDEFINED;
208
209function GetLanguageTagRE() {
210 if (IS_UNDEFINED(LANGUAGE_TAG_RE)) {
211 BuildLanguageTagREs();
212 }
213 return LANGUAGE_TAG_RE;
214}
215
216/**
217 * Helps find duplicate variants in the language tag.
218 */
219var LANGUAGE_VARIANT_RE = UNDEFINED;
220
221function GetLanguageVariantRE() {
222 if (IS_UNDEFINED(LANGUAGE_VARIANT_RE)) {
223 BuildLanguageTagREs();
224 }
225 return LANGUAGE_VARIANT_RE;
226}
227
228/**
229 * Helps find duplicate singletons in the language tag.
230 */
231var LANGUAGE_SINGLETON_RE = UNDEFINED;
232
233function GetLanguageSingletonRE() {
234 if (IS_UNDEFINED(LANGUAGE_SINGLETON_RE)) {
235 BuildLanguageTagREs();
236 }
237 return LANGUAGE_SINGLETON_RE;
238}
239
240/**
241 * Matches valid IANA time zone names.
242 */
243var TIMEZONE_NAME_CHECK_RE = UNDEFINED;
244
245function GetTimezoneNameCheckRE() {
246 if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) {
247 TIMEZONE_NAME_CHECK_RE = new GlobalRegExp(
248 '^([A-Za-z]+)/([A-Za-z_-]+)((?:\/[A-Za-z_-]+)+)*$');
249 }
250 return TIMEZONE_NAME_CHECK_RE;
251}
252
253/**
254 * Matches valid location parts of IANA time zone names.
255 */
256var TIMEZONE_NAME_LOCATION_PART_RE = UNDEFINED;
257
258function GetTimezoneNameLocationPartRE() {
259 if (IS_UNDEFINED(TIMEZONE_NAME_LOCATION_PART_RE)) {
260 TIMEZONE_NAME_LOCATION_PART_RE =
261 new GlobalRegExp('^([A-Za-z]+)((?:[_-][A-Za-z]+)+)*$');
262 }
263 return TIMEZONE_NAME_LOCATION_PART_RE;
264}
265
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000266
267/**
268 * Returns an intersection of locales and service supported locales.
269 * Parameter locales is treated as a priority list.
270 */
271function supportedLocalesOf(service, locales, options) {
Ben Murdochda12d292016-06-02 14:46:10 +0100272 if (IS_NULL(InternalRegExpMatch(GetServiceRE(), service))) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000273 throw MakeError(kWrongServiceType, service);
274 }
275
276 // Provide defaults if matcher was not specified.
277 if (IS_UNDEFINED(options)) {
278 options = {};
279 } else {
280 options = TO_OBJECT(options);
281 }
282
283 var matcher = options.localeMatcher;
284 if (!IS_UNDEFINED(matcher)) {
Ben Murdoch61f157c2016-09-16 13:49:30 +0100285 matcher = TO_STRING(matcher);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000286 if (matcher !== 'lookup' && matcher !== 'best fit') {
287 throw MakeRangeError(kLocaleMatcher, matcher);
288 }
289 } else {
290 matcher = 'best fit';
291 }
292
293 var requestedLocales = initializeLocaleList(locales);
294
295 // Cache these, they don't ever change per service.
296 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) {
297 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
298 }
299
300 // Use either best fit or lookup algorithm to match locales.
301 if (matcher === 'best fit') {
302 return initializeLocaleList(bestFitSupportedLocalesOf(
303 requestedLocales, AVAILABLE_LOCALES[service]));
304 }
305
306 return initializeLocaleList(lookupSupportedLocalesOf(
307 requestedLocales, AVAILABLE_LOCALES[service]));
308}
309
310
311/**
312 * Returns the subset of the provided BCP 47 language priority list for which
313 * this service has a matching locale when using the BCP 47 Lookup algorithm.
314 * Locales appear in the same order in the returned list as in the input list.
315 */
316function lookupSupportedLocalesOf(requestedLocales, availableLocales) {
Ben Murdochc5610432016-08-08 18:44:38 +0100317 var matchedLocales = new InternalArray();
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000318 for (var i = 0; i < requestedLocales.length; ++i) {
319 // Remove -u- extension.
Ben Murdochda12d292016-06-02 14:46:10 +0100320 var locale = InternalRegExpReplace(
321 GetUnicodeExtensionRE(), requestedLocales[i], '');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000322 do {
323 if (!IS_UNDEFINED(availableLocales[locale])) {
324 // Push requested locale not the resolved one.
325 %_Call(ArrayPush, matchedLocales, requestedLocales[i]);
326 break;
327 }
328 // Truncate locale if possible, if not break.
329 var pos = %_Call(StringLastIndexOf, locale, '-');
330 if (pos === -1) {
331 break;
332 }
333 locale = %_Call(StringSubstring, locale, 0, pos);
334 } while (true);
335 }
336
337 return matchedLocales;
338}
339
340
341/**
342 * Returns the subset of the provided BCP 47 language priority list for which
343 * this service has a matching locale when using the implementation
344 * dependent algorithm.
345 * Locales appear in the same order in the returned list as in the input list.
346 */
347function bestFitSupportedLocalesOf(requestedLocales, availableLocales) {
348 // TODO(cira): implement better best fit algorithm.
349 return lookupSupportedLocalesOf(requestedLocales, availableLocales);
350}
351
352
353/**
354 * Returns a getOption function that extracts property value for given
355 * options object. If property is missing it returns defaultValue. If value
356 * is out of range for that property it throws RangeError.
357 */
358function getGetOption(options, caller) {
359 if (IS_UNDEFINED(options)) throw MakeError(kDefaultOptionsMissing, caller);
360
361 var getOption = function getOption(property, type, values, defaultValue) {
362 if (!IS_UNDEFINED(options[property])) {
363 var value = options[property];
364 switch (type) {
365 case 'boolean':
Ben Murdoch61f157c2016-09-16 13:49:30 +0100366 value = TO_BOOLEAN(value);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000367 break;
368 case 'string':
Ben Murdoch61f157c2016-09-16 13:49:30 +0100369 value = TO_STRING(value);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000370 break;
371 case 'number':
Ben Murdoch61f157c2016-09-16 13:49:30 +0100372 value = TO_NUMBER(value);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000373 break;
374 default:
375 throw MakeError(kWrongValueType);
376 }
377
378 if (!IS_UNDEFINED(values) && %_Call(ArrayIndexOf, values, value) === -1) {
379 throw MakeRangeError(kValueOutOfRange, value, caller, property);
380 }
381
382 return value;
383 }
384
385 return defaultValue;
386 }
387
388 return getOption;
389}
390
391
392/**
393 * Compares a BCP 47 language priority list requestedLocales against the locales
394 * in availableLocales and determines the best available language to meet the
395 * request. Two algorithms are available to match the locales: the Lookup
396 * algorithm described in RFC 4647 section 3.4, and an implementation dependent
397 * best-fit algorithm. Independent of the locale matching algorithm, options
398 * specified through Unicode locale extension sequences are negotiated
399 * separately, taking the caller's relevant extension keys and locale data as
400 * well as client-provided options into consideration. Returns an object with
401 * a locale property whose value is the language tag of the selected locale,
402 * and properties for each key in relevantExtensionKeys providing the selected
403 * value for that key.
404 */
405function resolveLocale(service, requestedLocales, options) {
406 requestedLocales = initializeLocaleList(requestedLocales);
407
408 var getOption = getGetOption(options, service);
409 var matcher = getOption('localeMatcher', 'string',
410 ['lookup', 'best fit'], 'best fit');
411 var resolved;
412 if (matcher === 'lookup') {
413 resolved = lookupMatcher(service, requestedLocales);
414 } else {
415 resolved = bestFitMatcher(service, requestedLocales);
416 }
417
418 return resolved;
419}
420
421
422/**
423 * Returns best matched supported locale and extension info using basic
424 * lookup algorithm.
425 */
426function lookupMatcher(service, requestedLocales) {
Ben Murdochda12d292016-06-02 14:46:10 +0100427 if (IS_NULL(InternalRegExpMatch(GetServiceRE(), service))) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000428 throw MakeError(kWrongServiceType, service);
429 }
430
431 // Cache these, they don't ever change per service.
432 if (IS_UNDEFINED(AVAILABLE_LOCALES[service])) {
433 AVAILABLE_LOCALES[service] = getAvailableLocalesOf(service);
434 }
435
436 for (var i = 0; i < requestedLocales.length; ++i) {
437 // Remove all extensions.
Ben Murdochda12d292016-06-02 14:46:10 +0100438 var locale = InternalRegExpReplace(
439 GetAnyExtensionRE(), requestedLocales[i], '');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000440 do {
441 if (!IS_UNDEFINED(AVAILABLE_LOCALES[service][locale])) {
442 // Return the resolved locale and extension.
Ben Murdochda12d292016-06-02 14:46:10 +0100443 var extensionMatch = InternalRegExpMatch(
444 GetUnicodeExtensionRE(), requestedLocales[i]);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000445 var extension = IS_NULL(extensionMatch) ? '' : extensionMatch[0];
446 return {'locale': locale, 'extension': extension, 'position': i};
447 }
448 // Truncate locale if possible.
449 var pos = %_Call(StringLastIndexOf, locale, '-');
450 if (pos === -1) {
451 break;
452 }
453 locale = %_Call(StringSubstring, locale, 0, pos);
454 } while (true);
455 }
456
457 // Didn't find a match, return default.
Ben Murdochc5610432016-08-08 18:44:38 +0100458 return {'locale': GetDefaultICULocaleJS(), 'extension': '', 'position': -1};
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000459}
460
461
462/**
463 * Returns best matched supported locale and extension info using
464 * implementation dependend algorithm.
465 */
466function bestFitMatcher(service, requestedLocales) {
467 // TODO(cira): implement better best fit algorithm.
468 return lookupMatcher(service, requestedLocales);
469}
470
471
472/**
473 * Parses Unicode extension into key - value map.
474 * Returns empty object if the extension string is invalid.
475 * We are not concerned with the validity of the values at this point.
476 */
477function parseExtension(extension) {
478 var extensionSplit = %_Call(StringSplit, extension, '-');
479
480 // Assume ['', 'u', ...] input, but don't throw.
481 if (extensionSplit.length <= 2 ||
482 (extensionSplit[0] !== '' && extensionSplit[1] !== 'u')) {
483 return {};
484 }
485
486 // Key is {2}alphanum, value is {3,8}alphanum.
487 // Some keys may not have explicit values (booleans).
488 var extensionMap = {};
489 var previousKey = UNDEFINED;
490 for (var i = 2; i < extensionSplit.length; ++i) {
491 var length = extensionSplit[i].length;
492 var element = extensionSplit[i];
493 if (length === 2) {
494 extensionMap[element] = UNDEFINED;
495 previousKey = element;
496 } else if (length >= 3 && length <=8 && !IS_UNDEFINED(previousKey)) {
497 extensionMap[previousKey] = element;
498 previousKey = UNDEFINED;
499 } else {
500 // There is a value that's too long, or that doesn't have a key.
501 return {};
502 }
503 }
504
505 return extensionMap;
506}
507
508
509/**
510 * Populates internalOptions object with boolean key-value pairs
511 * from extensionMap and options.
512 * Returns filtered extension (number and date format constructors use
513 * Unicode extensions for passing parameters to ICU).
514 * It's used for extension-option pairs only, e.g. kn-normalization, but not
515 * for 'sensitivity' since it doesn't have extension equivalent.
516 * Extensions like nu and ca don't have options equivalent, so we place
517 * undefined in the map.property to denote that.
518 */
519function setOptions(inOptions, extensionMap, keyValues, getOption, outOptions) {
520 var extension = '';
521
522 var updateExtension = function updateExtension(key, value) {
Ben Murdoch61f157c2016-09-16 13:49:30 +0100523 return '-' + key + '-' + TO_STRING(value);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000524 }
525
526 var updateProperty = function updateProperty(property, type, value) {
527 if (type === 'boolean' && (typeof value === 'string')) {
528 value = (value === 'true') ? true : false;
529 }
530
531 if (!IS_UNDEFINED(property)) {
532 defineWEProperty(outOptions, property, value);
533 }
534 }
535
536 for (var key in keyValues) {
Ben Murdochda12d292016-06-02 14:46:10 +0100537 if (HAS_OWN_PROPERTY(keyValues, key)) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000538 var value = UNDEFINED;
539 var map = keyValues[key];
540 if (!IS_UNDEFINED(map.property)) {
541 // This may return true if user specifies numeric: 'false', since
542 // Boolean('nonempty') === true.
543 value = getOption(map.property, map.type, map.values);
544 }
545 if (!IS_UNDEFINED(value)) {
546 updateProperty(map.property, map.type, value);
547 extension += updateExtension(key, value);
548 continue;
549 }
550 // User options didn't have it, check Unicode extension.
551 // Here we want to convert strings 'true', 'false' into proper Boolean
552 // values (not a user error).
Ben Murdochda12d292016-06-02 14:46:10 +0100553 if (HAS_OWN_PROPERTY(extensionMap, key)) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000554 value = extensionMap[key];
555 if (!IS_UNDEFINED(value)) {
556 updateProperty(map.property, map.type, value);
557 extension += updateExtension(key, value);
558 } else if (map.type === 'boolean') {
559 // Boolean keys are allowed not to have values in Unicode extension.
560 // Those default to true.
561 updateProperty(map.property, map.type, true);
562 extension += updateExtension(key, true);
563 }
564 }
565 }
566 }
567
568 return extension === ''? '' : '-u' + extension;
569}
570
571
572/**
Ben Murdochc5610432016-08-08 18:44:38 +0100573 * Given an array-like, outputs an Array with the numbered
574 * properties copied over and defined
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000575 * configurable: false, writable: false, enumerable: true.
576 */
Ben Murdochc5610432016-08-08 18:44:38 +0100577function freezeArray(input) {
578 var array = [];
579 var l = input.length;
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000580 for (var i = 0; i < l; i++) {
Ben Murdochc5610432016-08-08 18:44:38 +0100581 if (i in input) {
582 %object_define_property(array, i, {value: input[i],
583 configurable: false,
584 writable: false,
585 enumerable: true});
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000586 }
587 }
588
Ben Murdochc5610432016-08-08 18:44:38 +0100589 %object_define_property(array, 'length', {value: l, writable: false});
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000590 return array;
591}
592
593
594/**
595 * It's sometimes desireable to leave user requested locale instead of ICU
596 * supported one (zh-TW is equivalent to zh-Hant-TW, so we should keep shorter
597 * one, if that was what user requested).
598 * This function returns user specified tag if its maximized form matches ICU
599 * resolved locale. If not we return ICU result.
600 */
601function getOptimalLanguageTag(original, resolved) {
602 // Returns Array<Object>, where each object has maximized and base properties.
603 // Maximized: zh -> zh-Hans-CN
604 // Base: zh-CN-u-ca-gregory -> zh-CN
605 // Take care of grandfathered or simple cases.
606 if (original === resolved) {
607 return original;
608 }
609
610 var locales = %GetLanguageTagVariants([original, resolved]);
611 if (locales[0].maximized !== locales[1].maximized) {
612 return resolved;
613 }
614
615 // Preserve extensions of resolved locale, but swap base tags with original.
Ben Murdochda12d292016-06-02 14:46:10 +0100616 var resolvedBase = new GlobalRegExp('^' + locales[1].base, 'g');
617 return InternalRegExpReplace(resolvedBase, resolved, locales[0].base);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000618}
619
620
621/**
622 * Returns an Object that contains all of supported locales for a given
623 * service.
624 * In addition to the supported locales we add xx-ZZ locale for each xx-Yyyy-ZZ
625 * that is supported. This is required by the spec.
626 */
627function getAvailableLocalesOf(service) {
628 var available = %AvailableLocalesOf(service);
629
630 for (var i in available) {
Ben Murdochda12d292016-06-02 14:46:10 +0100631 if (HAS_OWN_PROPERTY(available, i)) {
632 var parts = InternalRegExpMatch(
633 /^([a-z]{2,3})-([A-Z][a-z]{3})-([A-Z]{2})$/, i);
634 if (!IS_NULL(parts)) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000635 // Build xx-ZZ. We don't care about the actual value,
636 // as long it's not undefined.
637 available[parts[1] + '-' + parts[3]] = null;
638 }
639 }
640 }
641
642 return available;
643}
644
645
646/**
647 * Defines a property and sets writable and enumerable to true.
648 * Configurable is false by default.
649 */
650function defineWEProperty(object, property, value) {
Ben Murdochc5610432016-08-08 18:44:38 +0100651 %object_define_property(object, property,
652 {value: value, writable: true, enumerable: true});
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000653}
654
655
656/**
657 * Adds property to an object if the value is not undefined.
658 * Sets configurable descriptor to false.
659 */
660function addWEPropertyIfDefined(object, property, value) {
661 if (!IS_UNDEFINED(value)) {
662 defineWEProperty(object, property, value);
663 }
664}
665
666
667/**
668 * Defines a property and sets writable, enumerable and configurable to true.
669 */
670function defineWECProperty(object, property, value) {
Ben Murdochc5610432016-08-08 18:44:38 +0100671 %object_define_property(object, property, {value: value,
672 writable: true,
673 enumerable: true,
674 configurable: true});
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000675}
676
677
678/**
679 * Adds property to an object if the value is not undefined.
680 * Sets all descriptors to true.
681 */
682function addWECPropertyIfDefined(object, property, value) {
683 if (!IS_UNDEFINED(value)) {
684 defineWECProperty(object, property, value);
685 }
686}
687
688
689/**
690 * Returns titlecased word, aMeRricA -> America.
691 */
692function toTitleCaseWord(word) {
693 return %StringToUpperCase(%_Call(StringSubstr, word, 0, 1)) +
694 %StringToLowerCase(%_Call(StringSubstr, word, 1));
695}
696
697/**
698 * Returns titlecased location, bueNos_airES -> Buenos_Aires
699 * or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
700 * deals with ASCII only characters.
701 * 'of', 'au' and 'es' are special-cased and lowercased.
702 */
703function toTitleCaseTimezoneLocation(location) {
Ben Murdochda12d292016-06-02 14:46:10 +0100704 var match = InternalRegExpMatch(GetTimezoneNameLocationPartRE(), location)
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000705 if (IS_NULL(match)) throw MakeRangeError(kExpectedLocation, location);
706
707 var result = toTitleCaseWord(match[1]);
708 if (!IS_UNDEFINED(match[2]) && 2 < match.length) {
709 // The first character is a separator, '_' or '-'.
710 // None of IANA zone names has both '_' and '-'.
711 var separator = %_Call(StringSubstring, match[2], 0, 1);
712 var parts = %_Call(StringSplit, match[2], separator);
713 for (var i = 1; i < parts.length; i++) {
714 var part = parts[i]
715 var lowercasedPart = %StringToLowerCase(part);
716 result = result + separator +
717 ((lowercasedPart !== 'es' &&
718 lowercasedPart !== 'of' && lowercasedPart !== 'au') ?
719 toTitleCaseWord(part) : lowercasedPart);
720 }
721 }
722 return result;
723}
724
725/**
726 * Canonicalizes the language tag, or throws in case the tag is invalid.
727 */
728function canonicalizeLanguageTag(localeID) {
729 // null is typeof 'object' so we have to do extra check.
Ben Murdochc5610432016-08-08 18:44:38 +0100730 if ((!IS_STRING(localeID) && !IS_RECEIVER(localeID)) ||
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000731 IS_NULL(localeID)) {
732 throw MakeTypeError(kLanguageID);
733 }
734
Ben Murdochc5610432016-08-08 18:44:38 +0100735 // Optimize for the most common case; a language code alone in
736 // the canonical form/lowercase (e.g. "en", "fil").
737 if (IS_STRING(localeID) &&
738 !IS_NULL(InternalRegExpMatch(/^[a-z]{2,3}$/, localeID))) {
739 return localeID;
740 }
741
Ben Murdoch61f157c2016-09-16 13:49:30 +0100742 var localeString = TO_STRING(localeID);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000743
744 if (isValidLanguageTag(localeString) === false) {
745 throw MakeRangeError(kInvalidLanguageTag, localeString);
746 }
747
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000748 var tag = %CanonicalizeLanguageTag(localeString);
749 if (tag === 'invalid-tag') {
750 throw MakeRangeError(kInvalidLanguageTag, localeString);
751 }
752
753 return tag;
754}
755
756
757/**
758 * Returns an array where all locales are canonicalized and duplicates removed.
759 * Throws on locales that are not well formed BCP47 tags.
760 */
761function initializeLocaleList(locales) {
Ben Murdochc5610432016-08-08 18:44:38 +0100762 var seen = new InternalArray();
763 if (!IS_UNDEFINED(locales)) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000764 // We allow single string localeID.
765 if (typeof locales === 'string') {
766 %_Call(ArrayPush, seen, canonicalizeLanguageTag(locales));
767 return freezeArray(seen);
768 }
769
770 var o = TO_OBJECT(locales);
771 var len = TO_UINT32(o.length);
772
773 for (var k = 0; k < len; k++) {
774 if (k in o) {
775 var value = o[k];
776
777 var tag = canonicalizeLanguageTag(value);
778
779 if (%_Call(ArrayIndexOf, seen, tag) === -1) {
780 %_Call(ArrayPush, seen, tag);
781 }
782 }
783 }
784 }
785
786 return freezeArray(seen);
787}
788
789
790/**
791 * Validates the language tag. Section 2.2.9 of the bcp47 spec
792 * defines a valid tag.
793 *
794 * ICU is too permissible and lets invalid tags, like
795 * hant-cmn-cn, through.
796 *
797 * Returns false if the language tag is invalid.
798 */
799function isValidLanguageTag(locale) {
800 // Check if it's well-formed, including grandfadered tags.
Ben Murdochda12d292016-06-02 14:46:10 +0100801 if (IS_NULL(InternalRegExpMatch(GetLanguageTagRE(), locale))) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000802 return false;
803 }
804
805 // Just return if it's a x- form. It's all private.
806 if (%_Call(StringIndexOf, locale, 'x-') === 0) {
807 return true;
808 }
809
810 // Check if there are any duplicate variants or singletons (extensions).
811
812 // Remove private use section.
Ben Murdochda12d292016-06-02 14:46:10 +0100813 locale = %_Call(StringSplit, locale, '-x-')[0];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000814
815 // Skip language since it can match variant regex, so we start from 1.
816 // We are matching i-klingon here, but that's ok, since i-klingon-klingon
817 // is not valid and would fail LANGUAGE_TAG_RE test.
Ben Murdochc5610432016-08-08 18:44:38 +0100818 var variants = new InternalArray();
819 var extensions = new InternalArray();
Ben Murdochda12d292016-06-02 14:46:10 +0100820 var parts = %_Call(StringSplit, locale, '-');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000821 for (var i = 1; i < parts.length; i++) {
822 var value = parts[i];
Ben Murdochda12d292016-06-02 14:46:10 +0100823 if (!IS_NULL(InternalRegExpMatch(GetLanguageVariantRE(), value)) &&
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000824 extensions.length === 0) {
825 if (%_Call(ArrayIndexOf, variants, value) === -1) {
826 %_Call(ArrayPush, variants, value);
827 } else {
828 return false;
829 }
830 }
831
Ben Murdochda12d292016-06-02 14:46:10 +0100832 if (!IS_NULL(InternalRegExpMatch(GetLanguageSingletonRE(), value))) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000833 if (%_Call(ArrayIndexOf, extensions, value) === -1) {
834 %_Call(ArrayPush, extensions, value);
835 } else {
836 return false;
837 }
838 }
839 }
840
841 return true;
842 }
843
844
845/**
846 * Builds a regular expresion that validates the language tag
847 * against bcp47 spec.
848 * Uses http://tools.ietf.org/html/bcp47, section 2.1, ABNF.
849 * Runs on load and initializes the global REs.
850 */
851function BuildLanguageTagREs() {
852 var alpha = '[a-zA-Z]';
853 var digit = '[0-9]';
854 var alphanum = '(' + alpha + '|' + digit + ')';
855 var regular = '(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|' +
856 'zh-min|zh-min-nan|zh-xiang)';
857 var irregular = '(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|' +
858 'i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|' +
859 'i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)';
860 var grandfathered = '(' + irregular + '|' + regular + ')';
861 var privateUse = '(x(-' + alphanum + '{1,8})+)';
862
863 var singleton = '(' + digit + '|[A-WY-Za-wy-z])';
864 LANGUAGE_SINGLETON_RE = new GlobalRegExp('^' + singleton + '$', 'i');
865
866 var extension = '(' + singleton + '(-' + alphanum + '{2,8})+)';
867
868 var variant = '(' + alphanum + '{5,8}|(' + digit + alphanum + '{3}))';
869 LANGUAGE_VARIANT_RE = new GlobalRegExp('^' + variant + '$', 'i');
870
871 var region = '(' + alpha + '{2}|' + digit + '{3})';
872 var script = '(' + alpha + '{4})';
873 var extLang = '(' + alpha + '{3}(-' + alpha + '{3}){0,2})';
874 var language = '(' + alpha + '{2,3}(-' + extLang + ')?|' + alpha + '{4}|' +
875 alpha + '{5,8})';
876 var langTag = language + '(-' + script + ')?(-' + region + ')?(-' +
877 variant + ')*(-' + extension + ')*(-' + privateUse + ')?';
878
879 var languageTag =
880 '^(' + langTag + '|' + privateUse + '|' + grandfathered + ')$';
881 LANGUAGE_TAG_RE = new GlobalRegExp(languageTag, 'i');
882}
883
884var resolvedAccessor = {
885 get() {
886 %IncrementUseCounter(kIntlResolved);
887 return this[resolvedSymbol];
888 },
889 set(value) {
890 this[resolvedSymbol] = value;
891 }
892};
893
894/**
895 * Initializes the given object so it's a valid Collator instance.
896 * Useful for subclassing.
897 */
898function initializeCollator(collator, locales, options) {
899 if (%IsInitializedIntlObject(collator)) {
900 throw MakeTypeError(kReinitializeIntl, "Collator");
901 }
902
903 if (IS_UNDEFINED(options)) {
904 options = {};
905 }
906
907 var getOption = getGetOption(options, 'collator');
908
909 var internalOptions = {};
910
911 defineWEProperty(internalOptions, 'usage', getOption(
912 'usage', 'string', ['sort', 'search'], 'sort'));
913
914 var sensitivity = getOption('sensitivity', 'string',
915 ['base', 'accent', 'case', 'variant']);
916 if (IS_UNDEFINED(sensitivity) && internalOptions.usage === 'sort') {
917 sensitivity = 'variant';
918 }
919 defineWEProperty(internalOptions, 'sensitivity', sensitivity);
920
921 defineWEProperty(internalOptions, 'ignorePunctuation', getOption(
922 'ignorePunctuation', 'boolean', UNDEFINED, false));
923
924 var locale = resolveLocale('collator', locales, options);
925
926 // ICU can't take kb, kc... parameters through localeID, so we need to pass
927 // them as options.
928 // One exception is -co- which has to be part of the extension, but only for
929 // usage: sort, and its value can't be 'standard' or 'search'.
930 var extensionMap = parseExtension(locale.extension);
931
932 /**
933 * Map of Unicode extensions to option properties, and their values and types,
934 * for a collator.
935 */
936 var COLLATOR_KEY_MAP = {
937 'kn': {'property': 'numeric', 'type': 'boolean'},
938 'kf': {'property': 'caseFirst', 'type': 'string',
939 'values': ['false', 'lower', 'upper']}
940 };
941
942 setOptions(
943 options, extensionMap, COLLATOR_KEY_MAP, getOption, internalOptions);
944
945 var collation = 'default';
946 var extension = '';
Ben Murdochda12d292016-06-02 14:46:10 +0100947 if (HAS_OWN_PROPERTY(extensionMap, 'co') && internalOptions.usage === 'sort') {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000948
949 /**
950 * Allowed -u-co- values. List taken from:
951 * http://unicode.org/repos/cldr/trunk/common/bcp47/collation.xml
952 */
953 var ALLOWED_CO_VALUES = [
954 'big5han', 'dict', 'direct', 'ducet', 'gb2312', 'phonebk', 'phonetic',
955 'pinyin', 'reformed', 'searchjl', 'stroke', 'trad', 'unihan', 'zhuyin'
956 ];
957
958 if (%_Call(ArrayIndexOf, ALLOWED_CO_VALUES, extensionMap.co) !== -1) {
959 extension = '-u-co-' + extensionMap.co;
960 // ICU can't tell us what the collation is, so save user's input.
961 collation = extensionMap.co;
962 }
963 } else if (internalOptions.usage === 'search') {
964 extension = '-u-co-search';
965 }
966 defineWEProperty(internalOptions, 'collation', collation);
967
968 var requestedLocale = locale.locale + extension;
969
970 // We define all properties C++ code may produce, to prevent security
971 // problems. If malicious user decides to redefine Object.prototype.locale
972 // we can't just use plain x.locale = 'us' or in C++ Set("locale", "us").
Ben Murdochc5610432016-08-08 18:44:38 +0100973 // %object_define_properties will either succeed defining or throw an error.
974 var resolved = %object_define_properties({}, {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000975 caseFirst: {writable: true},
976 collation: {value: internalOptions.collation, writable: true},
977 ignorePunctuation: {writable: true},
978 locale: {writable: true},
979 numeric: {writable: true},
980 requestedLocale: {value: requestedLocale, writable: true},
981 sensitivity: {writable: true},
982 strength: {writable: true},
983 usage: {value: internalOptions.usage, writable: true}
984 });
985
986 var internalCollator = %CreateCollator(requestedLocale,
987 internalOptions,
988 resolved);
989
990 // Writable, configurable and enumerable are set to false by default.
991 %MarkAsInitializedIntlObjectOfType(collator, 'collator', internalCollator);
992 collator[resolvedSymbol] = resolved;
Ben Murdochc5610432016-08-08 18:44:38 +0100993 if (FLAG_intl_extra) {
994 %object_define_property(collator, 'resolved', resolvedAccessor);
995 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000996
997 return collator;
998}
999
1000
1001/**
1002 * Constructs Intl.Collator object given optional locales and options
1003 * parameters.
1004 *
1005 * @constructor
1006 */
Ben Murdochda12d292016-06-02 14:46:10 +01001007InstallConstructor(Intl, 'Collator', function() {
Ben Murdoch097c5b22016-05-18 11:27:45 +01001008 var locales = arguments[0];
1009 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001010
1011 if (!this || this === Intl) {
1012 // Constructor is called as a function.
1013 return new Intl.Collator(locales, options);
1014 }
1015
1016 return initializeCollator(TO_OBJECT(this), locales, options);
Ben Murdochda12d292016-06-02 14:46:10 +01001017 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001018);
1019
1020
1021/**
1022 * Collator resolvedOptions method.
1023 */
Ben Murdochda12d292016-06-02 14:46:10 +01001024InstallFunction(Intl.Collator.prototype, 'resolvedOptions', function() {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001025 if (!IS_UNDEFINED(new.target)) {
1026 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1027 }
1028
1029 if (!%IsInitializedIntlObjectOfType(this, 'collator')) {
1030 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "Collator");
1031 }
1032
1033 var coll = this;
1034 var locale = getOptimalLanguageTag(coll[resolvedSymbol].requestedLocale,
1035 coll[resolvedSymbol].locale);
1036
1037 return {
1038 locale: locale,
1039 usage: coll[resolvedSymbol].usage,
1040 sensitivity: coll[resolvedSymbol].sensitivity,
1041 ignorePunctuation: coll[resolvedSymbol].ignorePunctuation,
1042 numeric: coll[resolvedSymbol].numeric,
1043 caseFirst: coll[resolvedSymbol].caseFirst,
1044 collation: coll[resolvedSymbol].collation
1045 };
Ben Murdochda12d292016-06-02 14:46:10 +01001046 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001047);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001048
1049
1050/**
1051 * Returns the subset of the given locale list for which this locale list
1052 * has a matching (possibly fallback) locale. Locales appear in the same
1053 * order in the returned list as in the input list.
1054 * Options are optional parameter.
1055 */
Ben Murdochda12d292016-06-02 14:46:10 +01001056InstallFunction(Intl.Collator, 'supportedLocalesOf', function(locales) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001057 if (!IS_UNDEFINED(new.target)) {
1058 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1059 }
1060
Ben Murdoch097c5b22016-05-18 11:27:45 +01001061 return supportedLocalesOf('collator', locales, arguments[1]);
Ben Murdochda12d292016-06-02 14:46:10 +01001062 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001063);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001064
1065
1066/**
1067 * When the compare method is called with two arguments x and y, it returns a
1068 * Number other than NaN that represents the result of a locale-sensitive
1069 * String comparison of x with y.
1070 * The result is intended to order String values in the sort order specified
1071 * by the effective locale and collation options computed during construction
1072 * of this Collator object, and will be negative, zero, or positive, depending
1073 * on whether x comes before y in the sort order, the Strings are equal under
1074 * the sort order, or x comes after y in the sort order, respectively.
1075 */
1076function compare(collator, x, y) {
1077 return %InternalCompare(%GetImplFromInitializedIntlObject(collator),
Ben Murdoch61f157c2016-09-16 13:49:30 +01001078 TO_STRING(x), TO_STRING(y));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001079};
1080
1081
Ben Murdochc5610432016-08-08 18:44:38 +01001082AddBoundMethod(Intl.Collator, 'compare', compare, 2, 'collator');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001083
1084/**
1085 * Verifies that the input is a well-formed ISO 4217 currency code.
1086 * Don't uppercase to test. It could convert invalid code into a valid one.
1087 * For example \u00DFP (Eszett+P) becomes SSP.
1088 */
1089function isWellFormedCurrencyCode(currency) {
Ben Murdochda12d292016-06-02 14:46:10 +01001090 return typeof currency == "string" && currency.length == 3 &&
1091 IS_NULL(InternalRegExpMatch(/[^A-Za-z]/, currency));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001092}
1093
1094
1095/**
1096 * Returns the valid digit count for a property, or throws RangeError on
1097 * a value out of the range.
1098 */
1099function getNumberOption(options, property, min, max, fallback) {
1100 var value = options[property];
1101 if (!IS_UNDEFINED(value)) {
Ben Murdoch61f157c2016-09-16 13:49:30 +01001102 value = TO_NUMBER(value);
1103 if (NUMBER_IS_NAN(value) || value < min || value > max) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001104 throw MakeRangeError(kPropertyValueOutOfRange, property);
1105 }
Ben Murdochda12d292016-06-02 14:46:10 +01001106 return %math_floor(value);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001107 }
1108
1109 return fallback;
1110}
1111
1112var patternAccessor = {
1113 get() {
1114 %IncrementUseCounter(kIntlPattern);
1115 return this[patternSymbol];
1116 },
1117 set(value) {
1118 this[patternSymbol] = value;
1119 }
1120};
1121
1122/**
1123 * Initializes the given object so it's a valid NumberFormat instance.
1124 * Useful for subclassing.
1125 */
1126function initializeNumberFormat(numberFormat, locales, options) {
1127 if (%IsInitializedIntlObject(numberFormat)) {
1128 throw MakeTypeError(kReinitializeIntl, "NumberFormat");
1129 }
1130
1131 if (IS_UNDEFINED(options)) {
1132 options = {};
1133 }
1134
1135 var getOption = getGetOption(options, 'numberformat');
1136
1137 var locale = resolveLocale('numberformat', locales, options);
1138
1139 var internalOptions = {};
1140 defineWEProperty(internalOptions, 'style', getOption(
1141 'style', 'string', ['decimal', 'percent', 'currency'], 'decimal'));
1142
1143 var currency = getOption('currency', 'string');
1144 if (!IS_UNDEFINED(currency) && !isWellFormedCurrencyCode(currency)) {
1145 throw MakeRangeError(kInvalidCurrencyCode, currency);
1146 }
1147
1148 if (internalOptions.style === 'currency' && IS_UNDEFINED(currency)) {
1149 throw MakeTypeError(kCurrencyCode);
1150 }
1151
1152 var currencyDisplay = getOption(
1153 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
1154 if (internalOptions.style === 'currency') {
1155 defineWEProperty(internalOptions, 'currency', %StringToUpperCase(currency));
1156 defineWEProperty(internalOptions, 'currencyDisplay', currencyDisplay);
1157 }
1158
1159 // Digit ranges.
1160 var mnid = getNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
1161 defineWEProperty(internalOptions, 'minimumIntegerDigits', mnid);
1162
1163 var mnfd = options['minimumFractionDigits'];
1164 var mxfd = options['maximumFractionDigits'];
1165 if (!IS_UNDEFINED(mnfd) || internalOptions.style !== 'currency') {
1166 mnfd = getNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
1167 defineWEProperty(internalOptions, 'minimumFractionDigits', mnfd);
1168 }
1169
1170 if (!IS_UNDEFINED(mxfd) || internalOptions.style !== 'currency') {
1171 var min_mxfd = internalOptions.style === 'percent' ? 0 : 3;
1172 mnfd = IS_UNDEFINED(mnfd) ? 0 : mnfd;
1173 var fallback_limit = (mnfd > min_mxfd) ? mnfd : min_mxfd;
1174 mxfd = getNumberOption(options, 'maximumFractionDigits', mnfd, 20, fallback_limit);
1175 defineWEProperty(internalOptions, 'maximumFractionDigits', mxfd);
1176 }
1177
1178 var mnsd = options['minimumSignificantDigits'];
1179 var mxsd = options['maximumSignificantDigits'];
1180 if (!IS_UNDEFINED(mnsd) || !IS_UNDEFINED(mxsd)) {
1181 mnsd = getNumberOption(options, 'minimumSignificantDigits', 1, 21, 0);
1182 defineWEProperty(internalOptions, 'minimumSignificantDigits', mnsd);
1183
1184 mxsd = getNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
1185 defineWEProperty(internalOptions, 'maximumSignificantDigits', mxsd);
1186 }
1187
1188 // Grouping.
1189 defineWEProperty(internalOptions, 'useGrouping', getOption(
1190 'useGrouping', 'boolean', UNDEFINED, true));
1191
1192 // ICU prefers options to be passed using -u- extension key/values for
1193 // number format, so we need to build that.
1194 var extensionMap = parseExtension(locale.extension);
1195
1196 /**
1197 * Map of Unicode extensions to option properties, and their values and types,
1198 * for a number format.
1199 */
1200 var NUMBER_FORMAT_KEY_MAP = {
1201 'nu': {'property': UNDEFINED, 'type': 'string'}
1202 };
1203
1204 var extension = setOptions(options, extensionMap, NUMBER_FORMAT_KEY_MAP,
1205 getOption, internalOptions);
1206
1207 var requestedLocale = locale.locale + extension;
Ben Murdochc5610432016-08-08 18:44:38 +01001208 var resolved = %object_define_properties({}, {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001209 currency: {writable: true},
1210 currencyDisplay: {writable: true},
1211 locale: {writable: true},
1212 maximumFractionDigits: {writable: true},
1213 minimumFractionDigits: {writable: true},
1214 minimumIntegerDigits: {writable: true},
1215 numberingSystem: {writable: true},
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001216 requestedLocale: {value: requestedLocale, writable: true},
1217 style: {value: internalOptions.style, writable: true},
1218 useGrouping: {writable: true}
1219 });
Ben Murdochda12d292016-06-02 14:46:10 +01001220 if (HAS_OWN_PROPERTY(internalOptions, 'minimumSignificantDigits')) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001221 defineWEProperty(resolved, 'minimumSignificantDigits', UNDEFINED);
1222 }
Ben Murdochda12d292016-06-02 14:46:10 +01001223 if (HAS_OWN_PROPERTY(internalOptions, 'maximumSignificantDigits')) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001224 defineWEProperty(resolved, 'maximumSignificantDigits', UNDEFINED);
1225 }
1226 var formatter = %CreateNumberFormat(requestedLocale,
1227 internalOptions,
1228 resolved);
1229
1230 if (internalOptions.style === 'currency') {
Ben Murdochc5610432016-08-08 18:44:38 +01001231 %object_define_property(resolved, 'currencyDisplay',
1232 {value: currencyDisplay, writable: true});
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001233 }
1234
1235 %MarkAsInitializedIntlObjectOfType(numberFormat, 'numberformat', formatter);
1236 numberFormat[resolvedSymbol] = resolved;
Ben Murdochc5610432016-08-08 18:44:38 +01001237 if (FLAG_intl_extra) {
1238 %object_define_property(resolved, 'pattern', patternAccessor);
1239 %object_define_property(numberFormat, 'resolved', resolvedAccessor);
1240 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001241
1242 return numberFormat;
1243}
1244
1245
1246/**
1247 * Constructs Intl.NumberFormat object given optional locales and options
1248 * parameters.
1249 *
1250 * @constructor
1251 */
Ben Murdochda12d292016-06-02 14:46:10 +01001252InstallConstructor(Intl, 'NumberFormat', function() {
Ben Murdoch097c5b22016-05-18 11:27:45 +01001253 var locales = arguments[0];
1254 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001255
1256 if (!this || this === Intl) {
1257 // Constructor is called as a function.
1258 return new Intl.NumberFormat(locales, options);
1259 }
1260
1261 return initializeNumberFormat(TO_OBJECT(this), locales, options);
Ben Murdochda12d292016-06-02 14:46:10 +01001262 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001263);
1264
1265
1266/**
1267 * NumberFormat resolvedOptions method.
1268 */
Ben Murdochda12d292016-06-02 14:46:10 +01001269InstallFunction(Intl.NumberFormat.prototype, 'resolvedOptions', function() {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001270 if (!IS_UNDEFINED(new.target)) {
1271 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1272 }
1273
1274 if (!%IsInitializedIntlObjectOfType(this, 'numberformat')) {
1275 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "NumberFormat");
1276 }
1277
1278 var format = this;
1279 var locale = getOptimalLanguageTag(format[resolvedSymbol].requestedLocale,
1280 format[resolvedSymbol].locale);
1281
1282 var result = {
1283 locale: locale,
1284 numberingSystem: format[resolvedSymbol].numberingSystem,
1285 style: format[resolvedSymbol].style,
1286 useGrouping: format[resolvedSymbol].useGrouping,
1287 minimumIntegerDigits: format[resolvedSymbol].minimumIntegerDigits,
1288 minimumFractionDigits: format[resolvedSymbol].minimumFractionDigits,
1289 maximumFractionDigits: format[resolvedSymbol].maximumFractionDigits,
1290 };
1291
1292 if (result.style === 'currency') {
1293 defineWECProperty(result, 'currency', format[resolvedSymbol].currency);
1294 defineWECProperty(result, 'currencyDisplay',
1295 format[resolvedSymbol].currencyDisplay);
1296 }
1297
Ben Murdochda12d292016-06-02 14:46:10 +01001298 if (HAS_OWN_PROPERTY(format[resolvedSymbol], 'minimumSignificantDigits')) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001299 defineWECProperty(result, 'minimumSignificantDigits',
1300 format[resolvedSymbol].minimumSignificantDigits);
1301 }
1302
Ben Murdochda12d292016-06-02 14:46:10 +01001303 if (HAS_OWN_PROPERTY(format[resolvedSymbol], 'maximumSignificantDigits')) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001304 defineWECProperty(result, 'maximumSignificantDigits',
1305 format[resolvedSymbol].maximumSignificantDigits);
1306 }
1307
1308 return result;
Ben Murdochda12d292016-06-02 14:46:10 +01001309 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001310);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001311
1312
1313/**
1314 * Returns the subset of the given locale list for which this locale list
1315 * has a matching (possibly fallback) locale. Locales appear in the same
1316 * order in the returned list as in the input list.
1317 * Options are optional parameter.
1318 */
Ben Murdochda12d292016-06-02 14:46:10 +01001319InstallFunction(Intl.NumberFormat, 'supportedLocalesOf', function(locales) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001320 if (!IS_UNDEFINED(new.target)) {
1321 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1322 }
1323
Ben Murdoch097c5b22016-05-18 11:27:45 +01001324 return supportedLocalesOf('numberformat', locales, arguments[1]);
Ben Murdochda12d292016-06-02 14:46:10 +01001325 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001326);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001327
1328
1329/**
1330 * Returns a String value representing the result of calling ToNumber(value)
1331 * according to the effective locale and the formatting options of this
1332 * NumberFormat.
1333 */
1334function formatNumber(formatter, value) {
1335 // Spec treats -0 and +0 as 0.
1336 var number = TO_NUMBER(value) + 0;
1337
1338 return %InternalNumberFormat(%GetImplFromInitializedIntlObject(formatter),
1339 number);
1340}
1341
1342
1343/**
1344 * Returns a Number that represents string value that was passed in.
1345 */
Ben Murdochc5610432016-08-08 18:44:38 +01001346function IntlParseNumber(formatter, value) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001347 return %InternalNumberParse(%GetImplFromInitializedIntlObject(formatter),
Ben Murdoch61f157c2016-09-16 13:49:30 +01001348 TO_STRING(value));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001349}
1350
Ben Murdochc5610432016-08-08 18:44:38 +01001351AddBoundMethod(Intl.NumberFormat, 'format', formatNumber, 1, 'numberformat');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001352
1353/**
1354 * Returns a string that matches LDML representation of the options object.
1355 */
1356function toLDMLString(options) {
1357 var getOption = getGetOption(options, 'dateformat');
1358
1359 var ldmlString = '';
1360
1361 var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
1362 ldmlString += appendToLDMLString(
1363 option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
1364
1365 option = getOption('era', 'string', ['narrow', 'short', 'long']);
1366 ldmlString += appendToLDMLString(
1367 option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
1368
1369 option = getOption('year', 'string', ['2-digit', 'numeric']);
1370 ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
1371
1372 option = getOption('month', 'string',
1373 ['2-digit', 'numeric', 'narrow', 'short', 'long']);
1374 ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
1375 'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
1376
1377 option = getOption('day', 'string', ['2-digit', 'numeric']);
1378 ldmlString += appendToLDMLString(
1379 option, {'2-digit': 'dd', 'numeric': 'd'});
1380
1381 var hr12 = getOption('hour12', 'boolean');
1382 option = getOption('hour', 'string', ['2-digit', 'numeric']);
1383 if (IS_UNDEFINED(hr12)) {
1384 ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
1385 } else if (hr12 === true) {
1386 ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
1387 } else {
1388 ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
1389 }
1390
1391 option = getOption('minute', 'string', ['2-digit', 'numeric']);
1392 ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
1393
1394 option = getOption('second', 'string', ['2-digit', 'numeric']);
1395 ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
1396
1397 option = getOption('timeZoneName', 'string', ['short', 'long']);
1398 ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
1399
1400 return ldmlString;
1401}
1402
1403
1404/**
1405 * Returns either LDML equivalent of the current option or empty string.
1406 */
1407function appendToLDMLString(option, pairs) {
1408 if (!IS_UNDEFINED(option)) {
1409 return pairs[option];
1410 } else {
1411 return '';
1412 }
1413}
1414
1415
1416/**
1417 * Returns object that matches LDML representation of the date.
1418 */
1419function fromLDMLString(ldmlString) {
1420 // First remove '' quoted text, so we lose 'Uhr' strings.
Ben Murdochda12d292016-06-02 14:46:10 +01001421 ldmlString = InternalRegExpReplace(GetQuotedStringRE(), ldmlString, '');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001422
1423 var options = {};
Ben Murdochda12d292016-06-02 14:46:10 +01001424 var match = InternalRegExpMatch(/E{3,5}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001425 options = appendToDateTimeObject(
1426 options, 'weekday', match, {EEEEE: 'narrow', EEE: 'short', EEEE: 'long'});
1427
Ben Murdochda12d292016-06-02 14:46:10 +01001428 match = InternalRegExpMatch(/G{3,5}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001429 options = appendToDateTimeObject(
1430 options, 'era', match, {GGGGG: 'narrow', GGG: 'short', GGGG: 'long'});
1431
Ben Murdochda12d292016-06-02 14:46:10 +01001432 match = InternalRegExpMatch(/y{1,2}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001433 options = appendToDateTimeObject(
1434 options, 'year', match, {y: 'numeric', yy: '2-digit'});
1435
Ben Murdochda12d292016-06-02 14:46:10 +01001436 match = InternalRegExpMatch(/M{1,5}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001437 options = appendToDateTimeObject(options, 'month', match, {MM: '2-digit',
1438 M: 'numeric', MMMMM: 'narrow', MMM: 'short', MMMM: 'long'});
1439
1440 // Sometimes we get L instead of M for month - standalone name.
Ben Murdochda12d292016-06-02 14:46:10 +01001441 match = InternalRegExpMatch(/L{1,5}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001442 options = appendToDateTimeObject(options, 'month', match, {LL: '2-digit',
1443 L: 'numeric', LLLLL: 'narrow', LLL: 'short', LLLL: 'long'});
1444
Ben Murdochda12d292016-06-02 14:46:10 +01001445 match = InternalRegExpMatch(/d{1,2}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001446 options = appendToDateTimeObject(
1447 options, 'day', match, {d: 'numeric', dd: '2-digit'});
1448
Ben Murdochda12d292016-06-02 14:46:10 +01001449 match = InternalRegExpMatch(/h{1,2}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001450 if (match !== null) {
1451 options['hour12'] = true;
1452 }
1453 options = appendToDateTimeObject(
1454 options, 'hour', match, {h: 'numeric', hh: '2-digit'});
1455
Ben Murdochda12d292016-06-02 14:46:10 +01001456 match = InternalRegExpMatch(/H{1,2}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001457 if (match !== null) {
1458 options['hour12'] = false;
1459 }
1460 options = appendToDateTimeObject(
1461 options, 'hour', match, {H: 'numeric', HH: '2-digit'});
1462
Ben Murdochda12d292016-06-02 14:46:10 +01001463 match = InternalRegExpMatch(/m{1,2}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001464 options = appendToDateTimeObject(
1465 options, 'minute', match, {m: 'numeric', mm: '2-digit'});
1466
Ben Murdochda12d292016-06-02 14:46:10 +01001467 match = InternalRegExpMatch(/s{1,2}/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001468 options = appendToDateTimeObject(
1469 options, 'second', match, {s: 'numeric', ss: '2-digit'});
1470
Ben Murdochda12d292016-06-02 14:46:10 +01001471 match = InternalRegExpMatch(/z|zzzz/, ldmlString);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001472 options = appendToDateTimeObject(
1473 options, 'timeZoneName', match, {z: 'short', zzzz: 'long'});
1474
1475 return options;
1476}
1477
1478
1479function appendToDateTimeObject(options, option, match, pairs) {
1480 if (IS_NULL(match)) {
Ben Murdochda12d292016-06-02 14:46:10 +01001481 if (!HAS_OWN_PROPERTY(options, option)) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001482 defineWEProperty(options, option, UNDEFINED);
1483 }
1484 return options;
1485 }
1486
1487 var property = match[0];
1488 defineWEProperty(options, option, pairs[property]);
1489
1490 return options;
1491}
1492
1493
1494/**
1495 * Returns options with at least default values in it.
1496 */
1497function toDateTimeOptions(options, required, defaults) {
1498 if (IS_UNDEFINED(options)) {
1499 options = {};
1500 } else {
1501 options = TO_OBJECT(options);
1502 }
1503
1504 var needsDefault = true;
1505 if ((required === 'date' || required === 'any') &&
1506 (!IS_UNDEFINED(options.weekday) || !IS_UNDEFINED(options.year) ||
1507 !IS_UNDEFINED(options.month) || !IS_UNDEFINED(options.day))) {
1508 needsDefault = false;
1509 }
1510
1511 if ((required === 'time' || required === 'any') &&
1512 (!IS_UNDEFINED(options.hour) || !IS_UNDEFINED(options.minute) ||
1513 !IS_UNDEFINED(options.second))) {
1514 needsDefault = false;
1515 }
1516
1517 if (needsDefault && (defaults === 'date' || defaults === 'all')) {
Ben Murdochc5610432016-08-08 18:44:38 +01001518 %object_define_property(options, 'year', {value: 'numeric',
1519 writable: true,
1520 enumerable: true,
1521 configurable: true});
1522 %object_define_property(options, 'month', {value: 'numeric',
1523 writable: true,
1524 enumerable: true,
1525 configurable: true});
1526 %object_define_property(options, 'day', {value: 'numeric',
1527 writable: true,
1528 enumerable: true,
1529 configurable: true});
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001530 }
1531
1532 if (needsDefault && (defaults === 'time' || defaults === 'all')) {
Ben Murdochc5610432016-08-08 18:44:38 +01001533 %object_define_property(options, 'hour', {value: 'numeric',
1534 writable: true,
1535 enumerable: true,
1536 configurable: true});
1537 %object_define_property(options, 'minute', {value: 'numeric',
1538 writable: true,
1539 enumerable: true,
1540 configurable: true});
1541 %object_define_property(options, 'second', {value: 'numeric',
1542 writable: true,
1543 enumerable: true,
1544 configurable: true});
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001545 }
1546
1547 return options;
1548}
1549
1550
1551/**
1552 * Initializes the given object so it's a valid DateTimeFormat instance.
1553 * Useful for subclassing.
1554 */
1555function initializeDateTimeFormat(dateFormat, locales, options) {
1556
1557 if (%IsInitializedIntlObject(dateFormat)) {
1558 throw MakeTypeError(kReinitializeIntl, "DateTimeFormat");
1559 }
1560
1561 if (IS_UNDEFINED(options)) {
1562 options = {};
1563 }
1564
1565 var locale = resolveLocale('dateformat', locales, options);
1566
1567 options = toDateTimeOptions(options, 'any', 'date');
1568
1569 var getOption = getGetOption(options, 'dateformat');
1570
1571 // We implement only best fit algorithm, but still need to check
1572 // if the formatMatcher values are in range.
1573 var matcher = getOption('formatMatcher', 'string',
1574 ['basic', 'best fit'], 'best fit');
1575
1576 // Build LDML string for the skeleton that we pass to the formatter.
1577 var ldmlString = toLDMLString(options);
1578
1579 // Filter out supported extension keys so we know what to put in resolved
1580 // section later on.
1581 // We need to pass calendar and number system to the method.
1582 var tz = canonicalizeTimeZoneID(options.timeZone);
1583
1584 // ICU prefers options to be passed using -u- extension key/values, so
1585 // we need to build that.
1586 var internalOptions = {};
1587 var extensionMap = parseExtension(locale.extension);
1588
1589 /**
1590 * Map of Unicode extensions to option properties, and their values and types,
1591 * for a date/time format.
1592 */
1593 var DATETIME_FORMAT_KEY_MAP = {
1594 'ca': {'property': UNDEFINED, 'type': 'string'},
1595 'nu': {'property': UNDEFINED, 'type': 'string'}
1596 };
1597
1598 var extension = setOptions(options, extensionMap, DATETIME_FORMAT_KEY_MAP,
1599 getOption, internalOptions);
1600
1601 var requestedLocale = locale.locale + extension;
Ben Murdochc5610432016-08-08 18:44:38 +01001602 var resolved = %object_define_properties({}, {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001603 calendar: {writable: true},
1604 day: {writable: true},
1605 era: {writable: true},
1606 hour12: {writable: true},
1607 hour: {writable: true},
1608 locale: {writable: true},
1609 minute: {writable: true},
1610 month: {writable: true},
1611 numberingSystem: {writable: true},
1612 [patternSymbol]: {writable: true},
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001613 requestedLocale: {value: requestedLocale, writable: true},
1614 second: {writable: true},
1615 timeZone: {writable: true},
1616 timeZoneName: {writable: true},
1617 tz: {value: tz, writable: true},
1618 weekday: {writable: true},
1619 year: {writable: true}
1620 });
1621
1622 var formatter = %CreateDateTimeFormat(
1623 requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);
1624
1625 if (resolved.timeZone === "Etc/Unknown") {
1626 throw MakeRangeError(kUnsupportedTimeZone, tz);
1627 }
1628
1629 %MarkAsInitializedIntlObjectOfType(dateFormat, 'dateformat', formatter);
1630 dateFormat[resolvedSymbol] = resolved;
Ben Murdochc5610432016-08-08 18:44:38 +01001631 if (FLAG_intl_extra) {
1632 %object_define_property(resolved, 'pattern', patternAccessor);
1633 %object_define_property(dateFormat, 'resolved', resolvedAccessor);
1634 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001635
1636 return dateFormat;
1637}
1638
1639
1640/**
1641 * Constructs Intl.DateTimeFormat object given optional locales and options
1642 * parameters.
1643 *
1644 * @constructor
1645 */
Ben Murdochda12d292016-06-02 14:46:10 +01001646InstallConstructor(Intl, 'DateTimeFormat', function() {
Ben Murdoch097c5b22016-05-18 11:27:45 +01001647 var locales = arguments[0];
1648 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001649
1650 if (!this || this === Intl) {
1651 // Constructor is called as a function.
1652 return new Intl.DateTimeFormat(locales, options);
1653 }
1654
1655 return initializeDateTimeFormat(TO_OBJECT(this), locales, options);
Ben Murdochda12d292016-06-02 14:46:10 +01001656 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001657);
1658
1659
1660/**
1661 * DateTimeFormat resolvedOptions method.
1662 */
Ben Murdochda12d292016-06-02 14:46:10 +01001663InstallFunction(Intl.DateTimeFormat.prototype, 'resolvedOptions', function() {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001664 if (!IS_UNDEFINED(new.target)) {
1665 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1666 }
1667
1668 if (!%IsInitializedIntlObjectOfType(this, 'dateformat')) {
1669 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "DateTimeFormat");
1670 }
1671
1672 /**
1673 * Maps ICU calendar names into LDML type.
1674 */
1675 var ICU_CALENDAR_MAP = {
1676 'gregorian': 'gregory',
1677 'japanese': 'japanese',
1678 'buddhist': 'buddhist',
1679 'roc': 'roc',
1680 'persian': 'persian',
1681 'islamic-civil': 'islamicc',
1682 'islamic': 'islamic',
1683 'hebrew': 'hebrew',
1684 'chinese': 'chinese',
1685 'indian': 'indian',
1686 'coptic': 'coptic',
1687 'ethiopic': 'ethiopic',
1688 'ethiopic-amete-alem': 'ethioaa'
1689 };
1690
1691 var format = this;
1692 var fromPattern = fromLDMLString(format[resolvedSymbol][patternSymbol]);
1693 var userCalendar = ICU_CALENDAR_MAP[format[resolvedSymbol].calendar];
1694 if (IS_UNDEFINED(userCalendar)) {
1695 // Use ICU name if we don't have a match. It shouldn't happen, but
1696 // it would be too strict to throw for this.
1697 userCalendar = format[resolvedSymbol].calendar;
1698 }
1699
1700 var locale = getOptimalLanguageTag(format[resolvedSymbol].requestedLocale,
1701 format[resolvedSymbol].locale);
1702
1703 var result = {
1704 locale: locale,
1705 numberingSystem: format[resolvedSymbol].numberingSystem,
1706 calendar: userCalendar,
1707 timeZone: format[resolvedSymbol].timeZone
1708 };
1709
1710 addWECPropertyIfDefined(result, 'timeZoneName', fromPattern.timeZoneName);
1711 addWECPropertyIfDefined(result, 'era', fromPattern.era);
1712 addWECPropertyIfDefined(result, 'year', fromPattern.year);
1713 addWECPropertyIfDefined(result, 'month', fromPattern.month);
1714 addWECPropertyIfDefined(result, 'day', fromPattern.day);
1715 addWECPropertyIfDefined(result, 'weekday', fromPattern.weekday);
1716 addWECPropertyIfDefined(result, 'hour12', fromPattern.hour12);
1717 addWECPropertyIfDefined(result, 'hour', fromPattern.hour);
1718 addWECPropertyIfDefined(result, 'minute', fromPattern.minute);
1719 addWECPropertyIfDefined(result, 'second', fromPattern.second);
1720
1721 return result;
Ben Murdochda12d292016-06-02 14:46:10 +01001722 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001723);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001724
1725
1726/**
1727 * Returns the subset of the given locale list for which this locale list
1728 * has a matching (possibly fallback) locale. Locales appear in the same
1729 * order in the returned list as in the input list.
1730 * Options are optional parameter.
1731 */
Ben Murdochda12d292016-06-02 14:46:10 +01001732InstallFunction(Intl.DateTimeFormat, 'supportedLocalesOf', function(locales) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001733 if (!IS_UNDEFINED(new.target)) {
1734 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1735 }
1736
Ben Murdoch097c5b22016-05-18 11:27:45 +01001737 return supportedLocalesOf('dateformat', locales, arguments[1]);
Ben Murdochda12d292016-06-02 14:46:10 +01001738 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001739);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001740
1741
1742/**
1743 * Returns a String value representing the result of calling ToNumber(date)
1744 * according to the effective locale and the formatting options of this
1745 * DateTimeFormat.
1746 */
1747function formatDate(formatter, dateValue) {
1748 var dateMs;
1749 if (IS_UNDEFINED(dateValue)) {
1750 dateMs = %DateCurrentTime();
1751 } else {
1752 dateMs = TO_NUMBER(dateValue);
1753 }
1754
Ben Murdoch61f157c2016-09-16 13:49:30 +01001755 if (!NUMBER_IS_FINITE(dateMs)) throw MakeRangeError(kDateRange);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001756
1757 return %InternalDateFormat(%GetImplFromInitializedIntlObject(formatter),
1758 new GlobalDate(dateMs));
1759}
1760
1761
1762/**
1763 * Returns a Date object representing the result of calling ToString(value)
1764 * according to the effective locale and the formatting options of this
1765 * DateTimeFormat.
1766 * Returns undefined if date string cannot be parsed.
1767 */
Ben Murdochc5610432016-08-08 18:44:38 +01001768function IntlParseDate(formatter, value) {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001769 return %InternalDateParse(%GetImplFromInitializedIntlObject(formatter),
Ben Murdoch61f157c2016-09-16 13:49:30 +01001770 TO_STRING(value));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001771}
1772
1773
1774// 0 because date is optional argument.
Ben Murdochc5610432016-08-08 18:44:38 +01001775AddBoundMethod(Intl.DateTimeFormat, 'format', formatDate, 0, 'dateformat');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001776
1777
1778/**
1779 * Returns canonical Area/Location(/Location) name, or throws an exception
1780 * if the zone name is invalid IANA name.
1781 */
1782function canonicalizeTimeZoneID(tzID) {
1783 // Skip undefined zones.
1784 if (IS_UNDEFINED(tzID)) {
1785 return tzID;
1786 }
1787
1788 // Special case handling (UTC, GMT).
1789 var upperID = %StringToUpperCase(tzID);
1790 if (upperID === 'UTC' || upperID === 'GMT' ||
1791 upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
1792 return 'UTC';
1793 }
1794
1795 // TODO(jshin): Add support for Etc/GMT[+-]([1-9]|1[0-2])
1796
1797 // We expect only _, '-' and / beside ASCII letters.
1798 // All inputs should conform to Area/Location(/Location)* from now on.
Ben Murdochda12d292016-06-02 14:46:10 +01001799 var match = InternalRegExpMatch(GetTimezoneNameCheckRE(), tzID);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001800 if (IS_NULL(match)) throw MakeRangeError(kExpectedTimezoneID, tzID);
1801
1802 var result = toTitleCaseTimezoneLocation(match[1]) + '/' +
1803 toTitleCaseTimezoneLocation(match[2]);
1804
1805 if (!IS_UNDEFINED(match[3]) && 3 < match.length) {
1806 var locations = %_Call(StringSplit, match[3], '/');
1807 // The 1st element is empty. Starts with i=1.
1808 for (var i = 1; i < locations.length; i++) {
1809 result = result + '/' + toTitleCaseTimezoneLocation(locations[i]);
1810 }
1811 }
1812
1813 return result;
1814}
1815
1816/**
1817 * Initializes the given object so it's a valid BreakIterator instance.
1818 * Useful for subclassing.
1819 */
1820function initializeBreakIterator(iterator, locales, options) {
1821 if (%IsInitializedIntlObject(iterator)) {
1822 throw MakeTypeError(kReinitializeIntl, "v8BreakIterator");
1823 }
1824
1825 if (IS_UNDEFINED(options)) {
1826 options = {};
1827 }
1828
1829 var getOption = getGetOption(options, 'breakiterator');
1830
1831 var internalOptions = {};
1832
1833 defineWEProperty(internalOptions, 'type', getOption(
1834 'type', 'string', ['character', 'word', 'sentence', 'line'], 'word'));
1835
1836 var locale = resolveLocale('breakiterator', locales, options);
Ben Murdochc5610432016-08-08 18:44:38 +01001837 var resolved = %object_define_properties({}, {
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001838 requestedLocale: {value: locale.locale, writable: true},
1839 type: {value: internalOptions.type, writable: true},
1840 locale: {writable: true}
1841 });
1842
1843 var internalIterator = %CreateBreakIterator(locale.locale,
1844 internalOptions,
1845 resolved);
1846
1847 %MarkAsInitializedIntlObjectOfType(iterator, 'breakiterator',
1848 internalIterator);
1849 iterator[resolvedSymbol] = resolved;
Ben Murdochc5610432016-08-08 18:44:38 +01001850 if (FLAG_intl_extra) {
1851 %object_define_property(iterator, 'resolved', resolvedAccessor);
1852 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001853
1854 return iterator;
1855}
1856
1857
1858/**
1859 * Constructs Intl.v8BreakIterator object given optional locales and options
1860 * parameters.
1861 *
1862 * @constructor
1863 */
Ben Murdochda12d292016-06-02 14:46:10 +01001864InstallConstructor(Intl, 'v8BreakIterator', function() {
Ben Murdoch097c5b22016-05-18 11:27:45 +01001865 var locales = arguments[0];
1866 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001867
1868 if (!this || this === Intl) {
1869 // Constructor is called as a function.
1870 return new Intl.v8BreakIterator(locales, options);
1871 }
1872
1873 return initializeBreakIterator(TO_OBJECT(this), locales, options);
Ben Murdochda12d292016-06-02 14:46:10 +01001874 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001875);
1876
1877
1878/**
1879 * BreakIterator resolvedOptions method.
1880 */
Ben Murdochda12d292016-06-02 14:46:10 +01001881InstallFunction(Intl.v8BreakIterator.prototype, 'resolvedOptions',
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001882 function() {
1883 if (!IS_UNDEFINED(new.target)) {
1884 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1885 }
1886
1887 if (!%IsInitializedIntlObjectOfType(this, 'breakiterator')) {
1888 throw MakeTypeError(kResolvedOptionsCalledOnNonObject, "v8BreakIterator");
1889 }
1890
1891 var segmenter = this;
1892 var locale =
1893 getOptimalLanguageTag(segmenter[resolvedSymbol].requestedLocale,
1894 segmenter[resolvedSymbol].locale);
1895
1896 return {
1897 locale: locale,
1898 type: segmenter[resolvedSymbol].type
1899 };
Ben Murdochda12d292016-06-02 14:46:10 +01001900 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001901);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001902
1903
1904/**
1905 * Returns the subset of the given locale list for which this locale list
1906 * has a matching (possibly fallback) locale. Locales appear in the same
1907 * order in the returned list as in the input list.
1908 * Options are optional parameter.
1909 */
Ben Murdochda12d292016-06-02 14:46:10 +01001910InstallFunction(Intl.v8BreakIterator, 'supportedLocalesOf',
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001911 function(locales) {
1912 if (!IS_UNDEFINED(new.target)) {
1913 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
1914 }
1915
Ben Murdoch097c5b22016-05-18 11:27:45 +01001916 return supportedLocalesOf('breakiterator', locales, arguments[1]);
Ben Murdochda12d292016-06-02 14:46:10 +01001917 }
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001918);
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001919
1920
1921/**
1922 * Adopts text to segment using the iterator. Old text, if present,
1923 * gets discarded.
1924 */
1925function adoptText(iterator, text) {
1926 %BreakIteratorAdoptText(%GetImplFromInitializedIntlObject(iterator),
Ben Murdoch61f157c2016-09-16 13:49:30 +01001927 TO_STRING(text));
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001928}
1929
1930
1931/**
1932 * Returns index of the first break in the string and moves current pointer.
1933 */
1934function first(iterator) {
1935 return %BreakIteratorFirst(%GetImplFromInitializedIntlObject(iterator));
1936}
1937
1938
1939/**
1940 * Returns the index of the next break and moves the pointer.
1941 */
1942function next(iterator) {
1943 return %BreakIteratorNext(%GetImplFromInitializedIntlObject(iterator));
1944}
1945
1946
1947/**
1948 * Returns index of the current break.
1949 */
1950function current(iterator) {
1951 return %BreakIteratorCurrent(%GetImplFromInitializedIntlObject(iterator));
1952}
1953
1954
1955/**
1956 * Returns type of the current break.
1957 */
1958function breakType(iterator) {
1959 return %BreakIteratorBreakType(%GetImplFromInitializedIntlObject(iterator));
1960}
1961
1962
Ben Murdochc5610432016-08-08 18:44:38 +01001963AddBoundMethod(Intl.v8BreakIterator, 'adoptText', adoptText, 1,
1964 'breakiterator');
1965AddBoundMethod(Intl.v8BreakIterator, 'first', first, 0, 'breakiterator');
1966AddBoundMethod(Intl.v8BreakIterator, 'next', next, 0, 'breakiterator');
1967AddBoundMethod(Intl.v8BreakIterator, 'current', current, 0, 'breakiterator');
1968AddBoundMethod(Intl.v8BreakIterator, 'breakType', breakType, 0,
1969 'breakiterator');
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00001970
1971// Save references to Intl objects and methods we use, for added security.
1972var savedObjects = {
1973 'collator': Intl.Collator,
1974 'numberformat': Intl.NumberFormat,
1975 'dateformatall': Intl.DateTimeFormat,
1976 'dateformatdate': Intl.DateTimeFormat,
1977 'dateformattime': Intl.DateTimeFormat
1978};
1979
1980
1981// Default (created with undefined locales and options parameters) collator,
1982// number and date format instances. They'll be created as needed.
1983var defaultObjects = {
1984 'collator': UNDEFINED,
1985 'numberformat': UNDEFINED,
1986 'dateformatall': UNDEFINED,
1987 'dateformatdate': UNDEFINED,
1988 'dateformattime': UNDEFINED,
1989};
1990
Ben Murdoch61f157c2016-09-16 13:49:30 +01001991function clearDefaultObjects() {
1992 defaultObjects['dateformatall'] = UNDEFINED;
1993 defaultObjects['dateformatdate'] = UNDEFINED;
1994 defaultObjects['dateformattime'] = UNDEFINED;
1995}
1996
1997var date_cache_version = 0;
1998
1999function checkDateCacheCurrent() {
2000 var new_date_cache_version = %DateCacheVersion();
2001 if (new_date_cache_version == date_cache_version) {
2002 return;
2003 }
2004 date_cache_version = new_date_cache_version;
2005
2006 clearDefaultObjects();
2007}
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002008
2009/**
2010 * Returns cached or newly created instance of a given service.
2011 * We cache only default instances (where no locales or options are provided).
2012 */
2013function cachedOrNewService(service, locales, options, defaults) {
2014 var useOptions = (IS_UNDEFINED(defaults)) ? options : defaults;
2015 if (IS_UNDEFINED(locales) && IS_UNDEFINED(options)) {
Ben Murdoch61f157c2016-09-16 13:49:30 +01002016 checkDateCacheCurrent();
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002017 if (IS_UNDEFINED(defaultObjects[service])) {
2018 defaultObjects[service] = new savedObjects[service](locales, useOptions);
2019 }
2020 return defaultObjects[service];
2021 }
2022 return new savedObjects[service](locales, useOptions);
2023}
2024
Ben Murdochc5610432016-08-08 18:44:38 +01002025function LocaleConvertCase(s, locales, isToUpper) {
2026 // ECMA 402 section 13.1.2 steps 1 through 12.
2027 var language;
2028 // Optimize for the most common two cases. initializeLocaleList() can handle
2029 // them as well, but it's rather slow accounting for over 60% of
2030 // toLocale{U,L}Case() and about 40% of toLocale{U,L}Case("<locale>").
2031 if (IS_UNDEFINED(locales)) {
2032 language = GetDefaultICULocaleJS();
2033 } else if (IS_STRING(locales)) {
2034 language = canonicalizeLanguageTag(locales);
2035 } else {
2036 var locales = initializeLocaleList(locales);
2037 language = locales.length > 0 ? locales[0] : GetDefaultICULocaleJS();
2038 }
2039
2040 // StringSplit is slower than this.
2041 var pos = %_Call(StringIndexOf, language, '-');
2042 if (pos != -1) {
2043 language = %_Call(StringSubstring, language, 0, pos);
2044 }
2045
2046 var CUSTOM_CASE_LANGUAGES = ['az', 'el', 'lt', 'tr'];
2047 var langIndex = %_Call(ArrayIndexOf, CUSTOM_CASE_LANGUAGES, language);
2048 if (langIndex == -1) {
2049 // language-independent case conversion.
2050 return isToUpper ? %StringToUpperCaseI18N(s) : %StringToLowerCaseI18N(s);
2051 }
2052 return %StringLocaleConvertCase(s, isToUpper,
2053 CUSTOM_CASE_LANGUAGES[langIndex]);
2054}
2055
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002056/**
2057 * Compares this and that, and returns less than 0, 0 or greater than 0 value.
2058 * Overrides the built-in method.
2059 */
2060OverrideFunction(GlobalString.prototype, 'localeCompare', function(that) {
2061 if (!IS_UNDEFINED(new.target)) {
2062 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2063 }
2064
2065 if (IS_NULL_OR_UNDEFINED(this)) {
2066 throw MakeTypeError(kMethodInvokedOnNullOrUndefined);
2067 }
2068
Ben Murdoch097c5b22016-05-18 11:27:45 +01002069 var locales = arguments[1];
2070 var options = arguments[2];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002071 var collator = cachedOrNewService('collator', locales, options);
2072 return compare(collator, this, that);
2073 }
2074);
2075
2076
2077/**
2078 * Unicode normalization. This method is called with one argument that
2079 * specifies the normalization form.
2080 * If none is specified, "NFC" is assumed.
2081 * If the form is not one of "NFC", "NFD", "NFKC", or "NFKD", then throw
2082 * a RangeError Exception.
2083 */
2084
2085OverrideFunction(GlobalString.prototype, 'normalize', function() {
2086 if (!IS_UNDEFINED(new.target)) {
2087 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2088 }
2089
2090 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
2091 var s = TO_STRING(this);
2092
Ben Murdoch097c5b22016-05-18 11:27:45 +01002093 var formArg = arguments[0];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002094 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING(formArg);
2095
2096 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
2097
2098 var normalizationForm = %_Call(ArrayIndexOf, NORMALIZATION_FORMS, form);
2099 if (normalizationForm === -1) {
2100 throw MakeRangeError(kNormalizationForm,
2101 %_Call(ArrayJoin, NORMALIZATION_FORMS, ', '));
2102 }
2103
2104 return %StringNormalize(s, normalizationForm);
2105 }
2106);
2107
Ben Murdochc5610432016-08-08 18:44:38 +01002108function ToLowerCaseI18N() {
2109 if (!IS_UNDEFINED(new.target)) {
2110 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2111 }
2112 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
2113 var s = TO_STRING(this);
2114 return %StringToLowerCaseI18N(s);
2115}
2116
2117function ToUpperCaseI18N() {
2118 if (!IS_UNDEFINED(new.target)) {
2119 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2120 }
2121 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
2122 var s = TO_STRING(this);
2123 return %StringToUpperCaseI18N(s);
2124}
2125
2126function ToLocaleLowerCaseI18N(locales) {
2127 if (!IS_UNDEFINED(new.target)) {
2128 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2129 }
2130 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
2131 return LocaleConvertCase(TO_STRING(this), locales, false);
2132}
2133
2134%FunctionSetLength(ToLocaleLowerCaseI18N, 0);
2135
2136function ToLocaleUpperCaseI18N(locales) {
2137 if (!IS_UNDEFINED(new.target)) {
2138 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2139 }
2140 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
2141 return LocaleConvertCase(TO_STRING(this), locales, true);
2142}
2143
2144%FunctionSetLength(ToLocaleUpperCaseI18N, 0);
2145
2146%FunctionRemovePrototype(ToLowerCaseI18N);
2147%FunctionRemovePrototype(ToUpperCaseI18N);
2148%FunctionRemovePrototype(ToLocaleLowerCaseI18N);
2149%FunctionRemovePrototype(ToLocaleUpperCaseI18N);
2150
2151utils.Export(function(to) {
2152 to.ToLowerCaseI18N = ToLowerCaseI18N;
2153 to.ToUpperCaseI18N = ToUpperCaseI18N;
2154 to.ToLocaleLowerCaseI18N = ToLocaleLowerCaseI18N;
2155 to.ToLocaleUpperCaseI18N = ToLocaleUpperCaseI18N;
2156});
2157
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002158
2159/**
2160 * Formats a Number object (this) using locale and options values.
2161 * If locale or options are omitted, defaults are used.
2162 */
2163OverrideFunction(GlobalNumber.prototype, 'toLocaleString', function() {
2164 if (!IS_UNDEFINED(new.target)) {
2165 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2166 }
2167
2168 if (!(this instanceof GlobalNumber) && typeof(this) !== 'number') {
2169 throw MakeTypeError(kMethodInvokedOnWrongType, "Number");
2170 }
2171
Ben Murdoch097c5b22016-05-18 11:27:45 +01002172 var locales = arguments[0];
2173 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002174 var numberFormat = cachedOrNewService('numberformat', locales, options);
2175 return formatNumber(numberFormat, this);
2176 }
2177);
2178
2179
2180/**
2181 * Returns actual formatted date or fails if date parameter is invalid.
2182 */
2183function toLocaleDateTime(date, locales, options, required, defaults, service) {
2184 if (!(date instanceof GlobalDate)) {
2185 throw MakeTypeError(kMethodInvokedOnWrongType, "Date");
2186 }
2187
2188 if (IsNaN(date)) return 'Invalid Date';
2189
2190 var internalOptions = toDateTimeOptions(options, required, defaults);
2191
2192 var dateFormat =
2193 cachedOrNewService(service, locales, options, internalOptions);
2194
2195 return formatDate(dateFormat, date);
2196}
2197
2198
2199/**
2200 * Formats a Date object (this) using locale and options values.
2201 * If locale or options are omitted, defaults are used - both date and time are
2202 * present in the output.
2203 */
2204OverrideFunction(GlobalDate.prototype, 'toLocaleString', function() {
2205 if (!IS_UNDEFINED(new.target)) {
2206 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2207 }
2208
Ben Murdoch097c5b22016-05-18 11:27:45 +01002209 var locales = arguments[0];
2210 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002211 return toLocaleDateTime(
2212 this, locales, options, 'any', 'all', 'dateformatall');
2213 }
2214);
2215
2216
2217/**
2218 * Formats a Date object (this) using locale and options values.
2219 * If locale or options are omitted, defaults are used - only date is present
2220 * in the output.
2221 */
2222OverrideFunction(GlobalDate.prototype, 'toLocaleDateString', function() {
2223 if (!IS_UNDEFINED(new.target)) {
2224 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2225 }
2226
Ben Murdoch097c5b22016-05-18 11:27:45 +01002227 var locales = arguments[0];
2228 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002229 return toLocaleDateTime(
2230 this, locales, options, 'date', 'date', 'dateformatdate');
2231 }
2232);
2233
2234
2235/**
2236 * Formats a Date object (this) using locale and options values.
2237 * If locale or options are omitted, defaults are used - only time is present
2238 * in the output.
2239 */
2240OverrideFunction(GlobalDate.prototype, 'toLocaleTimeString', function() {
2241 if (!IS_UNDEFINED(new.target)) {
2242 throw MakeTypeError(kOrdinaryFunctionCalledAsConstructor);
2243 }
2244
Ben Murdoch097c5b22016-05-18 11:27:45 +01002245 var locales = arguments[0];
2246 var options = arguments[1];
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002247 return toLocaleDateTime(
2248 this, locales, options, 'time', 'time', 'dateformattime');
2249 }
2250);
2251
Ben Murdochc5610432016-08-08 18:44:38 +01002252utils.Export(function(to) {
2253 to.AddBoundMethod = AddBoundMethod;
2254 to.IntlParseDate = IntlParseDate;
2255 to.IntlParseNumber = IntlParseNumber;
2256});
2257
Ben Murdoch4a90d5f2016-03-22 12:00:34 +00002258})