Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1794 lines (1505 sloc) 89.4 KB
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
"use strict";
// Core intl lib
(function (EngineInterface, InitType) {
var platform = EngineInterface.Intl;
// allow unit tests to disable caching behavior for testing convenience but have this always `true` in real scenarios
platform.useCaches = true;
if (platform.localeLookupCache === undefined) {
platform.localeLookupCache = new platform.Map();
}
if (platform.localeBestFitCache === undefined) {
platform.localeBestFitCache = new platform.Map();
}
// determine what backing library we are using
// making these vars in JS allows us to more change how we
// determine the backing library
let isPlatformUsingICU = !platform.winglob;
let isPlatformUsingWinGlob = platform.winglob;
// constants
const NOT_FOUND = "NOT_FOUND";
// Built-Ins
var setPrototype = platform.builtInSetPrototype;
var getArrayLength = platform.builtInGetArrayLength;
var callInstanceFunc = platform.builtInCallInstanceFunction;
var Boolean = platform.Boolean;
var Object = platform.Object;
var RegExp = platform.RegExp;
var Number = platform.Number;
var String = platform.String;
var Date = platform.Date;
var Error = platform.Error;
var Map = platform.Map;
var RaiseAssert = platform.raiseAssert;
var Math = setPrototype({
abs: platform.builtInMathAbs,
floor: platform.builtInMathFloor,
max: platform.builtInMathMax,
pow: platform.builtInMathPow
}, null);
var ObjectGetPrototypeOf = platform.builtInJavascriptObjectEntryGetPrototypeOf;
var ObjectIsExtensible = platform.builtInJavascriptObjectEntryIsExtensible;
var ObjectGetOwnPropertyNames = platform.builtInJavascriptObjectEntryGetOwnPropertyNames;
var ObjectInstanceHasOwnProperty = platform.builtInJavascriptObjectEntryHasOwnProperty;
// Because we don't keep track of the attributes object, and neither does the internals of Object.defineProperty;
// We don't need to restore it's prototype.
var _objectDefineProperty = platform.builtInJavascriptObjectEntryDefineProperty;
var ObjectDefineProperty = function (obj, prop, attributes) {
_objectDefineProperty(obj, prop, setPrototype(attributes, null));
};
var ArrayInstanceForEach = platform.builtInJavascriptArrayEntryForEach;
var ArrayInstanceIndexOf = platform.builtInJavascriptArrayEntryIndexOf;
var ArrayInstancePush = platform.builtInJavascriptArrayEntryPush;
var ArrayInstanceJoin = platform.builtInJavascriptArrayEntryJoin;
var FunctionInstanceBind = platform.builtInJavascriptFunctionEntryBind;
var DateInstanceGetDate = platform.builtInJavascriptDateEntryGetDate;
var DateNow = platform.builtInJavascriptDateEntryNow;
var StringInstanceReplace = platform.builtInJavascriptStringEntryReplace;
var StringInstanceToLowerCase = platform.builtInJavascriptStringEntryToLowerCase;
var StringInstanceToUpperCase = platform.builtInJavascriptStringEntryToUpperCase;
var ObjectPrototype = ObjectGetPrototypeOf({});
var isFinite = platform.builtInGlobalObjectEntryIsFinite;
var isNaN = platform.builtInGlobalObjectEntryIsNaN;
// Keep this "enum" in sync with IntlEngineInterfaceExtensionObject::EntryIntl_RegisterBuiltInFunction
const IntlBuiltInFunctionID = setPrototype({
MIN: 0,
DateToLocaleString: 0,
DateToLocaleDateString: 1,
DateToLocaleTimeString: 2,
NumberToLocaleString: 3,
StringLocaleCompare: 4,
MAX: 5
}, null);
let __defaultLocale = undefined;
const GetDefaultLocale = function () {
if (__defaultLocale && platform.useCaches) {
return __defaultLocale;
}
const locale = platform.getDefaultLocale();
if (!locale) {
// if the system locale is undefined/null/empty string, we have to
// do something or else we will crash
__defaultLocale = "en";
} else {
__defaultLocale = locale;
}
return __defaultLocale;
};
let CreateDateTimeFormat = function (dateTimeFormat, condition) {
let retVal = platform.createDateTimeFormat(dateTimeFormat, condition);
if (retVal === null) {
// TODO (doilij): remove this fallback when implemented under ICU
dateTimeFormat.__numberingSystem = "";
dateTimeFormat.__patternStrings = [
"{month.a}{day.b}{hour.c}{minute.d}{second.e}",
"" // another entry for fun
]
}
// no return value
};
let IsWellFormedLanguageTag = function (langTag) {
let retVal = platform.isWellFormedLanguageTag(langTag);
if (retVal === null) {
if (!LANG_TAG_RE) {
InitializeLangTagREs();
}
let match = platform.builtInRegexMatch(langTag, LANG_TAG_RE);
return !!match;
} else {
return retVal;
}
};
var forEachIfPresent = function (obj, length, func) {
let current = 0;
while (current < length) {
if (current in obj) {
func(obj[current]);
}
current++;
}
};
// A helper function that is meant to rethrow SOE and OOM exceptions allowing them to propagate.
var throwExIfOOMOrSOE = function (ex) {
if (ex.number === -2146828260 || ex.number === -2146828281) {
throw ex;
}
};
var tagPublicFunction = function (name, f) {
return platform.tagPublicLibraryCode(f, name);
};
var resolveLocaleBestFit = function (locale, defaultLocale) {
var resolvedLocale = platform.localeBestFitCache.get(locale);
if (resolvedLocale === undefined) {
resolvedLocale = platform.resolveLocaleBestFit(locale);
if (resolvedLocale === null) {
if (!LANG_TAG_BASE_RE) {
InitializeLangTagREs();
}
let match = platform.builtInRegexMatch(locale, LANG_TAG_BASE_RE);
resolvedLocale = match[1] + (match[2] ? ('-' + match[2]) : '') + (match[3] ? ('-' + match[3]) : '');
}
// If resolvedLocale is undefined, cache that we got undefined
// so we don't try to resolve for `locale` in future.
platform.localeBestFitCache.set(locale, resolvedLocale === undefined ? NOT_FOUND : resolvedLocale);
} else if (resolvedLocale === NOT_FOUND) {
resolvedLocale = undefined;
}
if (defaultLocale === locale) {
return resolvedLocale;
} else if (defaultLocale === resolvedLocale) {
return undefined;
} else {
return resolvedLocale;
}
}
var resolveLocaleLookup = function (localeWithoutSubtags) {
let resolvedLocale = platform.localeLookupCache.get(localeWithoutSubtags);
if (resolvedLocale === undefined) {
resolvedLocale = platform.resolveLocaleLookup(localeWithoutSubtags);
if (resolvedLocale === null) {
if (!LANG_TAG_BASE_RE) {
InitializeLangTagREs();
}
let match = platform.builtInRegexMatch(localeWithoutSubtags, LANG_TAG_BASE_RE);
// match: [1] language; [2] script; [3] region (e.g. en-Latn-US)
resolvedLocale = match[1]
+ (match[2] ? ('-' + match[2]) : '')
+ (match[3] ? ('-' + match[3]) : '');
}
// If resolvedLocale is undefined, cache that we got undefined
// so we don't try to resolve for `locale` in future.
platform.localeLookupCache.set(localeWithoutSubtags, resolvedLocale === undefined ? NOT_FOUND : resolvedLocale);
} else if (resolvedLocale === NOT_FOUND) {
resolvedLocale = undefined;
}
return resolvedLocale;
}
var getExtensionSubtags = function (locale) {
if (!LANG_TAG_EXT_RE) {
InitializeLangTagREs();
}
const match = platform.builtInRegexMatch(locale, LANG_TAG_EXT_RE);
if (!match) {
return undefined;
}
// Note: extensions are /((${extension})-)*/ and are made up of \\b(?:${singleton}(?:-${alphanum}{2,8})+)\\b
// where the ${alphanum}{2,8} fields are of the form `${key}-${value}`.
// TODO (doilij): return an array of `${key}-${value}` pairs
// REVIEW (doilij): leading - might mean we need to filter: // ss.match(rr)[4].split('-').filter((x)=>!!x)
// In that case:
// TODO StringInstanceSplit
// TODO ArrayInstanceFilter
// let extSubtags = ArrayInstanceFilter(extensionsString.split('-'), (x)=>!!x);
const extSubtags = match[0].split('-').filter((x) => !!x);
// REVIEW (doilij): performance (testing for str[0]==='-' and using the string after that or updating the regex might be faster)
return extSubtags;
}
var resolveLocaleHelper = function (locale, fitter, extensionFilter, defaultLocale) {
var subTags = platform.getExtensions(locale);
if (subTags === null) {
// platform.getExtensions returns null to indicate fallback to JS implementation
subTags = getExtensionSubtags(locale);
}
if (subTags) {
callInstanceFunc(ArrayInstanceForEach, subTags, function (subTag) {
locale = callInstanceFunc(StringInstanceReplace, locale, "-" + subTag, "");
});
}
// Instead of using replace, we will match two groups, one capturing, one not. The non capturing group just strips away -u if present.
// We are substituting for the function replace; which will only make a change if /-u$/ was found (-u at the end of the line)
// And because match will return null if we don't match entire sequence, we are using the two groups stated above.
locale = platform.builtInRegexMatch(locale, /(.*?)(?:-u)?$/)[1];
var resolved = fitter(locale, defaultLocale);
if (extensionFilter !== undefined) { // Filter to expected sub-tags
var filtered = [];
callInstanceFunc(ArrayInstanceForEach, subTags, (function (subTag) {
var parts = platform.builtInRegexMatch(subTag, /([^-]*)-?(.*)?/); // [0] entire thing; [1] key; [2] value
var key = parts[1];
if (callInstanceFunc(ArrayInstanceIndexOf, extensionFilter, key) !== -1) {
callInstanceFunc(ArrayInstancePush, filtered, subTag);
}
}));
subTags = filtered;
}
// As long as we are using the JS version of getExtensions on ICU, "u" will be considered an extension
// of a locale like "de-u-co-phonebk"
// Thus, we can't add the -u- ourselves here
const withoutSubTags = resolved;
if (resolved) {
if (subTags && getArrayLength(subTags) > 0) {
if (isPlatformUsingICU) {
resolved += "-";
} else {
resolved += "-u-";
}
}
resolved += callInstanceFunc(ArrayInstanceJoin, subTags, "-");
} else {
resolved = undefined;
}
return setPrototype({
locale: resolved,
subTags: subTags,
localeWithoutSubtags: withoutSubTags
}, null);
}
var resolveLocales = function (givenLocales, matcher, extensionFilter, defaultLocaleFunc) {
var fitter = matcher === "lookup" ? resolveLocaleLookup : resolveLocaleBestFit;
var length = getArrayLength(givenLocales);
var defaultLocale = defaultLocaleFunc();
length = length !== undefined ? length : 0;
for (var i = 0; i < length; i++) {
var resolved = resolveLocaleHelper(givenLocales[i], fitter, extensionFilter, defaultLocale);
if (resolved.locale !== undefined) {
return resolved;
}
}
return resolveLocaleHelper(defaultLocale, fitter, undefined, defaultLocale);
}
// get just the language-script-region from the default locale
let __strippedDefaultLocale = undefined;
var strippedDefaultLocale = function () {
if (__strippedDefaultLocale) {
return __strippedDefaultLocale;
}
if (isPlatformUsingICU) {
if (!LANG_TAG_BASE_RE) {
InitializeLangTagREs();
}
const def = GetDefaultLocale();
const match = platform.builtInRegexMatch(def, LANG_TAG_BASE_RE);
if (match) {
// strip extensions by matching only the base
__strippedDefaultLocale = match[0];
} else {
__strippedDefaultLocale = def;
}
} else {
// the only thing to strip off of a WinGlob locale is the collation,
// which comes after the underscore
__strippedDefaultLocale = platform.builtInRegexMatch(GetDefaultLocale(), /([^_]*).*/)[1];
}
return __strippedDefaultLocale;
};
var Internal = (function () {
return setPrototype({
ToObject: function (o) {
if (o === null) {
platform.raiseNeedObject();
}
return o !== undefined ? Object(o) : undefined;
},
ToString: function (s) {
return s !== undefined ? String(s) : undefined;
},
ToNumber: function (n) {
return n === undefined ? NaN : Number(n);
},
ToLogicalBoolean: function (v) {
return v !== undefined ? Boolean(v) : undefined;
},
ToUint32: function (n) {
var num = Number(n),
ret = 0;
if (!isNaN(num) && isFinite(num)) {
ret = Math.abs(num % Math.pow(2, 32));
}
return ret;
},
HasProperty: function (o, p) {
// Walk the prototype chain
while (o) {
if (callInstanceFunc(ObjectInstanceHasOwnProperty, o, p)) {
return true;
}
o = ObjectGetPrototypeOf(o);
}
}
}, null)
})();
// Internal ops implemented in JS:
function GetOption(options, property, type, values, fallback) {
let value = options[property];
if (value !== undefined) {
if (type == "boolean") {
value = Internal.ToLogicalBoolean(value);
}
if (type == "string") {
value = Internal.ToString(value);
}
if (type == "number") {
value = Internal.ToNumber(value);
}
if (values !== undefined && callInstanceFunc(ArrayInstanceIndexOf, values, value) == -1) {
platform.raiseOptionValueOutOfRange_3(String(value), String(property), "['" + callInstanceFunc(ArrayInstanceJoin, values, "', '") + "']");
}
return value;
}
return fallback;
}
function GetNumberOption(options, property, minimum, maximum, fallback) {
const rawValue = options[property];
if (typeof rawValue !== 'undefined') {
const formattedValue = Internal.ToNumber(rawValue);
if (isNaN(formattedValue) || formattedValue < minimum || formattedValue > maximum) {
platform.raiseOptionValueOutOfRange_3(String(rawValue), String(property), "[" + minimum + " - " + maximum + "]");
}
return Math.floor(formattedValue);
} else {
return fallback;
}
}
let CURRENCY_CODE_RE;
function InitializeCurrencyRegExp() {
CURRENCY_CODE_RE = /^[A-Z]{3}$/i;
}
let LANG_TAG_BASE_RE; // language[-script[-region]]
let LANG_TAG_EXT_RE; // extension part (variant, extension, privateuse)
let LANG_TAG_RE; // full syntax of language tags (including privateuse and grandfathered)
function InitializeLangTagREs() {
// Language Tag Syntax as described in RFC 5646 #section-2.1
// Note: All language tags are comprised only of ASCII characters (makes our job easy here)
// Note: Language tags in canonical form have case conventions, but language tags are case-insensitive for our purposes
// Note: The ABNF syntax used in RFC 5646 #section-2.1 uses the following numeric quantifier conventions:
// - (Parentheses) are used for grouping
// - PRODUCTION => exactly 1 of PRODUCTION /PRODUCTION/
// - [PRODUCTION] => 0 or 1 of PRODUCTION /(PRODUCTION)?/
// - #PRODUCTION => exactly # of PRODUCTION /(PRODUCTION){#}/
// - a*bPRODUCTION (where a and b are optional)
// - *PRODUCTION => any number of PRODUCTION /(PRODUCTION)*/
// - 1*PRODUCTION => 1 or more of PRODUCTION /(PRODUCTION)+/
// - #*PRODUCTION => # or more of PRODUCTION /(PRODUCTION){#,}/
// - *#PRODUCTION => 0 to # (inclusive) of PRODUCTION /(PRODUCTION){,#}/ or /(PRODUCTION){0,#}/
// - a*bPRODUCTION => a to b (inclusive) of PRODUCTION /(PRODUCTION){a,b}/
const ALPHA = "[A-Z]";
const DIGIT = "[0-9]";
const alphanum = `(?:${ALPHA}|${DIGIT})`;
const regular = "\\b(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)\\b";
const irregular = "\\b(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo" +
"|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)\\b";
const grandfathered = `\\b(?:${regular}|${irregular})\\b`;
const privateuse = `\\b(?:x(?:-${alphanum}{1,8}\\b)+)\\b`; // privateuse = "x" 1*("-" (1*8alphanum))
const singleton = `\\b(?:${DIGIT}|[A-WY-Z])\\b`; // singleton ~= alphanum except for 'x' ; (paraphrased)
const extension = `\\b(?:${singleton}(?:-${alphanum}{2,8})+)\\b`; // extension = singleton 1*("-" (2*8alphanum))
const variant = `\\b(?:${alphanum}{5,8}|${DIGIT}${alphanum}{3})\\b`; // variant = 5*8alphanum / (DIGIT 3alphanum)
const region = `\\b(?:${ALPHA}{2}|${DIGIT}{3})\\b`; // region = 2ALPHA / 3DIGIT
const script = `\\b(?:${ALPHA}{4})\\b`; // script = 4ALPHA
const extlang = `\\b(?:${ALPHA}{3}\\b(?:-${ALPHA}{3}){0,2})\\b`; // extlang = 3ALPHA *2("-" 3ALPHA)
const language = '\\b(?:' + // language =
`${ALPHA}{2,3}` + // 2*3ALPHA ; shortest ISO 639 code
`\\b(?:-${extlang})?` + // ["-" extlang] ; sometimes followed by extended language subtags
// `|${ALPHA}{4}` + // / 4ALPHA ; or reserved for future use
// `|${ALPHA}{5,8}` + // / 5*8ALPHA ; or registered language subtag
`|${ALPHA}{4,8}` + // ~/ 4*8ALPHA ; (paraphrased: combined previous two lines)
')\\b';
// below: ${language}, ${script}, and ${region} are wrapped in parens because matching groups are useful for replacement
const LANG_TAG_BASE = `\\b(${language})\\b` + // langtag = language
`\\b(?:-(${script}))?\\b` + // ["-" script]
`\\b(?:-(${region}))?\\b` ; // ["-" region]
const LANG_TAG_EXT = `\\b(?:-${variant})*\\b` + // *("-" variant)
`\\b((?:-${extension})*)\\b` + // *("-" extension)
`\\b(?:-${privateuse})?\\b` ; // ["-" privateuse]
const langtag = `\\b${LANG_TAG_BASE}\\b${LANG_TAG_EXT}\\b`;
const LANG_TAG = `\\b(?:${langtag}|${privateuse}|${grandfathered})\\b`; // Language-Tag = ...
LANG_TAG_BASE_RE = new RegExp(LANG_TAG_BASE, 'i'); // [1] language; [2] script; [3] region
LANG_TAG_EXT_RE = new RegExp(LANG_TAG_EXT, 'i'); // [1] extensions /((${extension})-)*/
LANG_TAG_RE = new RegExp(LANG_TAG, 'i'); // [1] language; [2] script; [3] region; [4] extensions
}
function IsWellFormedCurrencyCode(code) {
code = Internal.ToString(code);
if (!CURRENCY_CODE_RE) {
InitializeCurrencyRegExp();
}
return platform.builtInRegexMatch(code, CURRENCY_CODE_RE) !== null;
}
// Make sure locales is an array, remove duplicate locales, make sure each locale is valid, and canonicalize each.
function CanonicalizeLocaleList(locales) {
if (typeof locales === 'undefined') {
return [];
}
if (typeof locales === 'string') {
locales = [locales];
}
locales = Internal.ToObject(locales);
const length = Internal.ToUint32(locales.length);
// TODO: Use sets here to prevent duplicates
let seen = [];
forEachIfPresent(locales, length, function (locale) {
if ((typeof locale !== 'string' && typeof locale !== 'object') || locale === null) {
platform.raiseNeedObjectOrString("Locale");
}
let tag = Internal.ToString(locale);
if (!IsWellFormedLanguageTag(tag)) {
platform.raiseLocaleNotWellFormed(String(tag));
}
tag = platform.normalizeLanguageTag(tag);
if (tag !== undefined && callInstanceFunc(ArrayInstanceIndexOf, seen, tag) === -1) {
callInstanceFunc(ArrayInstancePush, seen, tag);
}
});
return seen;
}
function LookupSupportedLocales(requestedLocales, fitter, defaultLocale) {
var subset = [];
var count = 0;
callInstanceFunc(ArrayInstanceForEach, requestedLocales, function (locale) {
try {
var resolved = resolveLocaleHelper(locale, fitter, undefined, defaultLocale);
if (resolved.locale) {
ObjectDefineProperty(subset, count, { value: resolved.locale, writable: false, configurable: false, enumerable: true });
count = count + 1;
}
} catch (ex) {
throwExIfOOMOrSOE(ex);
// Expecting an error (other than OOM or SOE), same as fitter returning undefined
}
});
ObjectDefineProperty(subset, "length", { value: count, writable: false, configurable: false });
return subset;
}
var supportedLocalesOfWrapper = function (that, functionName, locales, options) {
if (that === null || that === undefined) {
platform.raiseNotAConstructor(functionName);
}
var hiddenObj = platform.getHiddenObject(that);
if (!hiddenObj || hiddenObj.isValid !== "Valid") {
platform.raiseNotAConstructor(functionName);
}
return supportedLocalesOf(locales, options);
}
var canonicalizeLocaleListWrapper = function (that, functionName, locales) {
if (that === null || that === undefined) {
platform.raiseNotAConstructor(functionName);
}
var hiddenObj = platform.getHiddenObject(that);
if (!hiddenObj || hiddenObj.isValid !== "Valid") {
platform.raiseNotAConstructor(functionName);
}
return CanonicalizeLocaleList(locales);
}
// Shared among all the constructors
var supportedLocalesOf = function (locales, options) {
var matcher;
locales = CanonicalizeLocaleList(locales);
if (typeof options !== 'undefined') {
matcher = options.localeMatcher;
if (typeof matcher !== 'undefined') {
matcher = Internal.ToString(matcher);
if (matcher !== 'lookup' && matcher !== 'best fit') {
platform.raiseOptionValueOutOfRange_3(String(matcher), "localeMatcher", "['best fit', 'lookup']");
}
}
}
if (typeof matcher === 'undefined' || matcher === 'best fit') {
return LookupSupportedLocales(locales, resolveLocaleBestFit, platform.normalizeLanguageTag(strippedDefaultLocale()));
} else {
return LookupSupportedLocales(locales, resolveLocaleLookup, strippedDefaultLocale());
}
};
const intlStaticMethodThisArg = setPrototype({}, null);
platform.setHiddenObject(intlStaticMethodThisArg, setPrototype({ isValid: "Valid" }, null));
// We wrap these functions so that we can define the correct name for this function for each Intl constructor,
// which allows us to display the correct error message for each Intl type.
const collator_supportedLocalesOf_name = "Intl.Collator.supportedLocalesOf";
const collator_supportedLocalesOf = callInstanceFunc(FunctionInstanceBind, tagPublicFunction(collator_supportedLocalesOf_name,
function collator_supportedLocalesOf_dummyName(locales, options = undefined) {
return supportedLocalesOfWrapper(this, collator_supportedLocalesOf_name, locales, options);
}), intlStaticMethodThisArg);
const numberFormat_supportedLocalesOf_name = "Intl.NumberFormat.supportedLocalesOf";
const numberFormat_supportedLocalesOf = callInstanceFunc(FunctionInstanceBind, tagPublicFunction(numberFormat_supportedLocalesOf_name,
function numberFormat_supportedLocalesOf_dummyName(locales, options = undefined) {
return supportedLocalesOfWrapper(this, numberFormat_supportedLocalesOf_name, locales, options);
}), intlStaticMethodThisArg);
const dateTimeFormat_supportedLocalesOf_name = "Intl.DateTimeFormat.supportedLocalesOf";
const dateTimeFormat_supportedLocalesOf = callInstanceFunc(FunctionInstanceBind, tagPublicFunction(dateTimeFormat_supportedLocalesOf_name,
function dateTimeFormat_supportedLocalesOf_dummyName(locales, options = undefined) {
return supportedLocalesOfWrapper(this, dateTimeFormat_supportedLocalesOf_name, locales, options);
}), intlStaticMethodThisArg);
const getCanonicalLocales_name = "Intl.getCanonicalLocales";
const getCanonicalLocales = callInstanceFunc(FunctionInstanceBind, tagPublicFunction(getCanonicalLocales_name,
function getCanonicalLocales_dummyName(locales) {
return canonicalizeLocaleListWrapper(this, getCanonicalLocales_name, locales);
}), intlStaticMethodThisArg);
// TODO: Bound functions get the "bound" prefix by default, so we need to remove it.
// When https://github.com/Microsoft/ChakraCore/issues/637 is fixed and we have a way
// to make built-in functions non-constructible, we can remove the call to
// Function.prototype.bind (i.e. FunctionInstanceBind) and just rely on tagging instead of setting the "name" manually.
ObjectDefineProperty(collator_supportedLocalesOf, 'name', { value: 'supportedLocalesOf' });
ObjectDefineProperty(numberFormat_supportedLocalesOf, 'name', { value: 'supportedLocalesOf' });
ObjectDefineProperty(dateTimeFormat_supportedLocalesOf, 'name', { value: 'supportedLocalesOf' });
ObjectDefineProperty(getCanonicalLocales, 'name', { value: 'getCanonicalLocales' });
// If an empty string is encountered for the value of the property; that means that is by default.
// So in the case of zh-TW; "default" and "stroke" are the same.
// This list was discussed with AnBorod, AnGlass and SureshJa.
var localesAcceptingCollationValues = setPrototype({
"es-ES": setPrototype({ "trad": "tradnl" }, null),
"lv-LV": setPrototype({ "trad": "tradnl" }, null),
"de-DE": setPrototype({ "phonebk": "phoneb" }, null),
"ja-JP": setPrototype({ "unihan": "radstr" }, null),
// We believe "pronun" means "pronunciation"
"zh-TW": setPrototype({ "phonetic": "pronun", "unihan": "radstr", "stroke": "" }, null),
"zh-HK": setPrototype({ "unihan": "radstr", "stroke": "" }, null),
"zh-MO": setPrototype({ "unihan": "radstr", "stroke": "" }, null),
"zh-CN": setPrototype({ "stroke": "stroke", "pinyin": "" }, null),
"zh-SG": setPrototype({ "stroke": "stroke", "pinyin": "" }, null)
// The following locales are supported by Windows; however, no BCP47 equivalent collation values were found for these.
// In future releases; this list (plus most of the Collator implementation) will be changed/removed as the platform support is expected to change.
// "hu-HU": ["technl"],
// "ka-GE": ["modern"],
// "x-IV": ["mathan"]
}, null);
// reverses the keys and values in each locale's sub-object in localesAcceptingCollationValues
// localesAcceptingCollationValues[locale][key] = value -> reverseLocalesAcceptingCollationValues[locale][value] = key
var reverseLocalesAcceptingCollationValues = (function () {
const toReturn = setPrototype({}, null);
callInstanceFunc(ArrayInstanceForEach, ObjectGetOwnPropertyNames(localesAcceptingCollationValues), function (locale) {
const collationValuesForLocale = localesAcceptingCollationValues[locale];
const reversedCollationValues = setPrototype({}, null);
callInstanceFunc(ArrayInstanceForEach, ObjectGetOwnPropertyNames(collationValuesForLocale), function (collation) {
const windowsTag = collationValuesForLocale[collation];
if (windowsTag !== "") {
reversedCollationValues[windowsTag] = collation;
}
});
toReturn[locale] = reversedCollationValues;
});
return toReturn;
}());
// mappedDefaultLocale will get the default locale and update any deprecated
// collation/sort order values it may use
let __mappedDefaultLocale = undefined;
var mappedDefaultLocale = function () {
if (__mappedDefaultLocale && platform.useCaches) {
return __mappedDefaultLocale;
}
let locale = undefined;
let collation = undefined;
if (isPlatformUsingICU) {
// ICU's getDefaultLocale() will return a valid BCP-47/RFC 5646 langtag
locale = GetDefaultLocale();
const match = platform.builtInRegexMatch(locale, /-u(?:-[^\-][^\-]?-[^\-]+)*-co-([^\-]+).*/);
if (match) {
// if the system default locale had a collation, strip it for now
// we will add the collation back later in this function
collation = match[1];
locale = callInstanceFunc(StringInstanceReplace, locale, `-co-${collation}`, "");
}
} else {
// Windows' getDefaultLocale() will return a RFC4646 langtag
const parts = platform.builtInRegexMatch(GetDefaultLocale(), /([^_]*)_?(.+)?/);
locale = parts[1];
collation = parts[2];
}
if (collation === undefined) {
__mappedDefaultLocale = locale;
return __mappedDefaultLocale;
}
// we stripped the -co-collation or _collation above, so this function adds it back
const createLocaleCollationString = function (finalLocale, finalCollation) {
if (isPlatformUsingICU) {
return `${finalLocale}-co-${finalCollation}`;
} else {
return `${finalLocale}-u-co-${finalCollation}`;
}
};
const collationMapForLocale = reverseLocalesAcceptingCollationValues[locale];
if (collationMapForLocale === undefined) {
// Assume the system wouldn't give us back a bad collation value
__mappedDefaultLocale = createLocaleCollationString(locale, collation);
return __mappedDefaultLocale;
}
const mappedCollation = collationMapForLocale[collation];
if (mappedCollation !== undefined) {
__mappedDefaultLocale = createLocaleCollationString(locale, mappedCollation);
} else {
__mappedDefaultLocale = createLocaleCollationString(locale, collation);
}
return __mappedDefaultLocale;
};
// Intl.Collator, String.prototype.localeCompare
var Collator = (function () {
if (InitType === 'Intl' || InitType === 'String') {
function InitializeCollator(collator, localeList, options) {
if (typeof collator != "object") {
platform.raiseNeedObject();
}
if (callInstanceFunc(ObjectInstanceHasOwnProperty, collator, '__initializedIntlObject') && collator.__initializedIntlObject) {
platform.raiseObjectIsAlreadyInitialized("Collator", "Collator");
}
collator.__initializedIntlObject = true;
// Extract options
if (typeof options === 'undefined') {
options = setPrototype({}, null);
} else {
options = Internal.ToObject(options);
}
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
var usage = GetOption(options, "usage", "string", ["sort", "search"], "sort");
var sensitivity = GetOption(options, "sensitivity", "string", ["base", "accent", "case", "variant"], undefined);
var ignorePunctuation = GetOption(options, "ignorePunctuation", "boolean", undefined, false);
var caseFirst = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined);
var numeric = GetOption(options, "numeric", "boolean", [true, false], undefined);
// Deal with the locales and extensions
localeList = CanonicalizeLocaleList(localeList);
var resolvedLocaleInfo = resolveLocales(localeList, matcher, undefined, mappedDefaultLocale);
var collation = "default";
var resolvedLocaleLookup = resolveLocaleLookup(resolvedLocaleInfo.localeWithoutSubtags);
var collationAugmentedLocale = resolvedLocaleLookup;
if (resolvedLocaleInfo.subTags) {
callInstanceFunc(ArrayInstanceForEach, resolvedLocaleInfo.subTags, function (subTag) {
var parts = platform.builtInRegexMatch(subTag, /([^-]*)-?(.*)?/); // [0] entire thing; [1] key; [2] value
var key = parts[1];
var value = parts[2] === "" ? undefined : parts[2];
if (key === "kf" && caseFirst === undefined) {
caseFirst = GetOption(setPrototype({ caseFirst: value }, null), "caseFirst", "string", ["upper", "lower", "false"], undefined);
} else if (key === "kn" && numeric === undefined) {
if (value !== undefined) {
numeric = Internal.ToLogicalBoolean(callInstanceFunc(StringInstanceToLowerCase, value) === "true");
} else {
numeric = true;
}
} else if (key === "co" && value !== undefined && value !== "default" && value !== "search" && value !== "sort" && value !== "standard") {
// Ignore these collation values as they shouldn't have any impact
collation = value;
}
});
}
if (collation !== "default") {
var accepedCollationForLocale = localesAcceptingCollationValues[collationAugmentedLocale];
var windowsCollation = "";
if (accepedCollationForLocale !== undefined && (windowsCollation = accepedCollationForLocale[collation]) !== undefined) {
if (windowsCollation !== "") {
collationAugmentedLocale = collationAugmentedLocale + "_" + windowsCollation;
}
}
else {
collation = "default";
}
}
// Correct options if need be.
if (caseFirst === undefined) {
try {
var num = platform.compareString('A', 'a', resolvedLocaleLookup, undefined, undefined, undefined);
} catch (e) {
// Rethrow OOM or SOE
throwExIfOOMOrSOE(e);
// Otherwise, Generic message to cover the exception throw from the CompareStringEx api.
// The platform's exception is also generic and in most if not all cases specifies that "a" argument is invalid.
// We have no other information from the platform on the cause of the exception.
platform.raiseOptionValueOutOfRange();
}
if (num === 0) {
caseFirst = 'false';
} else if (num === -1) {
caseFirst = 'upper';
} else {
caseFirst = 'lower';
}
}
if (sensitivity === undefined) {
sensitivity = "variant";
}
if (numeric === undefined) {
numeric = false;
}
// Set the options on the object
collator.__matcher = matcher;
collator.__locale = resolvedLocaleInfo.localeWithoutSubtags;
collator.__localeForCompare = collationAugmentedLocale;
collator.__usage = usage;
collator.__sensitivity = sensitivity;
collator.__ignorePunctuation = ignorePunctuation;
collator.__caseFirst = caseFirst;
collator.__numeric = numeric;
collator.__collation = collation;
collator.__initializedCollator = true;
}
platform.registerBuiltInFunction(tagPublicFunction("String.prototype.localeCompare", function () {
var that = arguments[0];
if (this === undefined || this === null) {
platform.raiseThis_NullOrUndefined("String.prototype.localeCompare");
}
else if (that === null) {
platform.raiseNeedObject();
}
// ToString must be called on this/that argument before we do any other operation, as other operations in InitializeCollator may also be observable
var thisArg = String(this);
var that = String(that);
var stateObject = setPrototype({}, null);
InitializeCollator(stateObject, arguments[1], arguments[2]);
return Number(platform.compareString(thisArg, that, stateObject.__localeForCompare, stateObject.__sensitivity, stateObject.__ignorePunctuation, stateObject.__numeric));
}), IntlBuiltInFunctionID.StringLocaleCompare);
if (InitType === 'Intl') {
function Collator(locales = undefined, options = undefined) {
if (this === Intl || this === undefined) {
return new Collator(locales, options);
}
let obj = Internal.ToObject(this);
if (!ObjectIsExtensible(obj)) {
platform.raiseObjectIsNonExtensible("Collator");
}
// Use the hidden object to store data
let hiddenObject = platform.getHiddenObject(obj);
if (hiddenObject === undefined) {
hiddenObject = setPrototype({}, null);
platform.setHiddenObject(obj, hiddenObject);
}
InitializeCollator(hiddenObject, locales, options);
// Add the bound compare
hiddenObject.__boundCompare = callInstanceFunc(FunctionInstanceBind, compare, obj);
delete hiddenObject.__boundCompare.name;
return obj;
}
tagPublicFunction("Intl.Collator", Collator);
function compare(a, b) {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("Collator.prototype.compare", "Collator");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedCollator) {
platform.raiseNeedObjectOfType("Collator.prototype.compare", "Collator");
}
a = String(a);
b = String(b);
return Number(platform.compareString(a, b, hiddenObject.__localeForCompare, hiddenObject.__sensitivity, hiddenObject.__ignorePunctuation, hiddenObject.__numeric));
}
tagPublicFunction("Intl.Collator.prototype.compare", compare);
ObjectDefineProperty(Collator, 'supportedLocalesOf', { value: collator_supportedLocalesOf, writable: true, configurable: true });
ObjectDefineProperty(Collator, 'prototype', { value: new Collator(), writable: false, enumerable: false, configurable: false });
setPrototype(Collator.prototype, Object.prototype);
ObjectDefineProperty(Collator.prototype, 'constructor', { value: Collator, writable: true, enumerable: false, configurable: true });
ObjectDefineProperty(Collator.prototype, 'resolvedOptions', {
value: function resolvedOptions() {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("Collator.prototype.resolvedOptions", "Collator");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedCollator) {
platform.raiseNeedObjectOfType("Collator.prototype.resolvedOptions", "Collator");
}
return {
locale: hiddenObject.__locale,
usage: hiddenObject.__usage,
sensitivity: hiddenObject.__sensitivity,
ignorePunctuation: hiddenObject.__ignorePunctuation,
collation: hiddenObject.__collation, // "co" unicode extension
numeric: hiddenObject.__numeric, // "ka" unicode extension TODO: Determine if this is supported (doesn't have to be)
caseFirst: hiddenObject.__caseFirst // "kf" unicode extension TODO: Determine if this is supported (doesn't have to be)
}
}, writable: true, enumerable: false, configurable: true
});
ObjectDefineProperty(Collator.prototype, 'compare', {
get: tagPublicFunction('get compare', function () {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("Collator.prototype.compare", "Collator");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedCollator) {
platform.raiseNeedObjectOfType("Collator.prototype.compare", "Collator");
}
return hiddenObject.__boundCompare;
}), enumerable: false, configurable: true
});
return Collator;
}
}
// 'Init.Collator' not defined if reached here. Return 'undefined'
return undefined;
})();
// Intl.NumberFormat, Number.prototype.toLocaleString
var NumberFormat = (function () {
// Keep these "enums" in sync with lib/Runtime/PlatformAgnostic/Intl.h
const NumberFormatStyle = setPrototype({
DEFAULT: 0, // "decimal" is the default
DECIMAL: 0, // Intl.NumberFormat(locale, { style: "decimal" }); // aka in our code as "number"
PERCENT: 1, // Intl.NumberFormat(locale, { style: "percent" });
CURRENCY: 2, // Intl.NumberFormat(locale, { style: "currency", ... });
MAX: 3
}, null);
const NumberFormatCurrencyDisplay = setPrototype({
DEFAULT: 0, // "symbol" is the default
SYMBOL: 0, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "symbol" }); // e.g. "$" or "US$" depeding on locale
CODE: 1, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "code" }); // e.g. "USD"
NAME: 2, // Intl.NumberFormat(locale, { style: "currency", currencyDisplay: "name" }); // e.g. "US dollar"
MAX: 3
}, null);
if (InitType === 'Intl' || InitType === 'Number') {
function InitializeNumberFormat(numberFormat, localeList, options) {
if (typeof numberFormat != "object") {
platform.raiseNeedObject();
}
if (callInstanceFunc(ObjectInstanceHasOwnProperty, numberFormat, '__initializedIntlObject') && numberFormat.__initializedIntlObject) {
platform.raiseObjectIsAlreadyInitialized("NumberFormat", "NumberFormat");
}
numberFormat.__initializedIntlObject = true;
// Extract options
if (typeof options === 'undefined') {
options = setPrototype({}, null);
} else {
options = Internal.ToObject(options);
}
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
var style = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
var formatterToUse = NumberFormatStyle.DECIMAL; // DEFAULT
if (style === 'percent') {
formatterToUse = NumberFormatStyle.PERCENT;
} else if (style === 'currency') {
formatterToUse = NumberFormatStyle.CURRENCY;
}
var currency = GetOption(options, "currency", "string", undefined, undefined);
var currencyDisplay = GetOption(options, 'currencyDisplay', 'string', ['code', 'symbol', 'name'], 'symbol');
var currencyDigits = undefined;
var minimumIntegerDigits = GetNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
var minimumFractionDigits = undefined;
var maximumFractionDigits = undefined;
var maximumFractionDigitsDefault = undefined;
var minimumSignificantDigits = options.minimumSignificantDigits;
var maximumSignificantDigits = options.maximumSignificantDigits;
if (typeof minimumSignificantDigits !== 'undefined' || typeof maximumSignificantDigits !== 'undefined') {
minimumSignificantDigits = GetNumberOption(options, 'minimumSignificantDigits', 1, 21, 1);
maximumSignificantDigits = GetNumberOption(options, 'maximumSignificantDigits', minimumSignificantDigits, 21, 21);
}
var useGrouping = GetOption(options, 'useGrouping', 'boolean', undefined, true);
// Deal with the locales and extensions
localeList = CanonicalizeLocaleList(localeList);
var resolvedLocaleInfo = resolveLocales(localeList, matcher, ["nu"], strippedDefaultLocale);
// Correct the options if necessary
if (typeof currency !== 'undefined' && !IsWellFormedCurrencyCode(currency)) {
platform.raiseInvalidCurrencyCode(String(currency));
}
if (style === "currency") {
if (typeof currency === 'undefined') {
platform.raiseMissingCurrencyCode();
}
currency = callInstanceFunc(StringInstanceToUpperCase, currency);
try {
currencyDigits = platform.currencyDigits(currency);
} catch (e) {
throwExIfOOMOrSOE(e);
platform.raiseInvalidCurrencyCode(String(currency));
}
minimumFractionDigits = GetNumberOption(options, 'minimumFractionDigits', 0, 20, currencyDigits);
maximumFractionDigitsDefault = Math.max(currencyDigits, minimumFractionDigits);
} else {
currency = undefined;
currencyDisplay = undefined;
minimumFractionDigits = GetNumberOption(options, 'minimumFractionDigits', 0, 20, 0);
if (style === "percent") {
maximumFractionDigitsDefault = Math.max(minimumFractionDigits, 0);
} else {
maximumFractionDigitsDefault = Math.max(minimumFractionDigits, 3)
}
}
maximumFractionDigits = GetNumberOption(options, 'maximumFractionDigits', minimumFractionDigits, 20, maximumFractionDigitsDefault);
// Set the options on the object
numberFormat.__localeMatcher = matcher;
numberFormat.__locale = resolvedLocaleInfo.locale;
numberFormat.__style = style;
if (currency !== undefined) {
numberFormat.__currency = currency;
}
if (currencyDisplay !== undefined) {
numberFormat.__currencyDisplay = currencyDisplay;
numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.DEFAULT;
if (currencyDisplay === "symbol") {
numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.SYMBOL;
} else if (currencyDisplay === "code") {
numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.CODE;
} else if (currencyDisplay === "name") {
numberFormat.__currencyDisplayToUse = NumberFormatCurrencyDisplay.NAME;
}
}
numberFormat.__minimumIntegerDigits = minimumIntegerDigits;
numberFormat.__minimumFractionDigits = minimumFractionDigits;
numberFormat.__maximumFractionDigits = maximumFractionDigits;
if (maximumSignificantDigits !== undefined) {
numberFormat.__minimumSignificantDigits = minimumSignificantDigits;
numberFormat.__maximumSignificantDigits = maximumSignificantDigits;
}
numberFormat.__formatterToUse = formatterToUse;
numberFormat.__useGrouping = useGrouping;
try {
// Cache api instance and update numbering system on the object
platform.cacheNumberFormat(numberFormat);
} catch (e) {
throwExIfOOMOrSOE(e);
// Generic message to cover the exception throw from the platform.
// The platform's exception is also generic and in most if not all cases specifies that "a" argument is invalid.
// We have no other information from the platform on the cause of the exception.
platform.raiseOptionValueOutOfRange();
}
if (!numberFormat.__numberingSystem) {
numberFormat.__numberingSystem = "latn"; // assume Latin numerals by default
}
numberFormat.__numberingSystem = callInstanceFunc(StringInstanceToLowerCase, numberFormat.__numberingSystem);
numberFormat.__initializedNumberFormat = true;
}
platform.registerBuiltInFunction(tagPublicFunction("Number.prototype.toLocaleString", function () {
if ((typeof this) !== 'number' && !(this instanceof Number)) {
platform.raiseNeedObjectOfType("Number.prototype.toLocaleString", "Number");
}
var stateObject = setPrototype({}, null);
InitializeNumberFormat(stateObject, arguments[0], arguments[1]);
var n = Internal.ToNumber(this);
// Need to special case the '-0' case to format as 0 instead of -0.
return String(platform.formatNumber(n === -0 ? 0 : n, stateObject));
}), IntlBuiltInFunctionID.NumberToLocaleString);
if (InitType === 'Intl') {
function NumberFormat(locales = undefined, options = undefined) {
if (this === Intl || this === undefined) {
return new NumberFormat(locales, options);
}
let obj = Internal.ToObject(this);
if (!ObjectIsExtensible(obj)) {
platform.raiseObjectIsNonExtensible("NumberFormat");
}
// Use the hidden object to store data
let hiddenObject = platform.getHiddenObject(obj);
if (hiddenObject === undefined) {
hiddenObject = setPrototype({}, null);
platform.setHiddenObject(obj, hiddenObject);
}
InitializeNumberFormat(hiddenObject, locales, options);
hiddenObject.__boundFormat = callInstanceFunc(FunctionInstanceBind, format, obj)
delete hiddenObject.__boundFormat.name;
return obj;
}
tagPublicFunction("Intl.NumberFormat", NumberFormat);
function format(n) {
n = Internal.ToNumber(n);
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("NumberFormat.prototype.format", "NumberFormat");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedNumberFormat) {
platform.raiseNeedObjectOfType("NumberFormat.prototype.format", "NumberFormat");
}
// Need to special case the '-0' case to format as 0 instead of -0.
return String(platform.formatNumber(n === -0 ? 0 : n, hiddenObject));
}
tagPublicFunction("Intl.NumberFormat.prototype.format", format);
ObjectDefineProperty(NumberFormat, 'supportedLocalesOf', { value: numberFormat_supportedLocalesOf, writable: true, configurable: true });
var options = ['locale', 'numberingSystem', 'style', 'currency', 'currencyDisplay', 'minimumIntegerDigits',
'minimumFractionDigits', 'maximumFractionDigits', 'minimumSignificantDigits', 'maximumSignificantDigits',
'useGrouping'];
ObjectDefineProperty(NumberFormat, 'prototype', { value: new NumberFormat(), writable: false, enumerable: false, configurable: false });
setPrototype(NumberFormat.prototype, Object.prototype);
ObjectDefineProperty(NumberFormat.prototype, 'constructor', { value: NumberFormat, writable: true, enumerable: false, configurable: true });
ObjectDefineProperty(NumberFormat.prototype, 'resolvedOptions', {
value: function resolvedOptions() {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("NumberFormat.prototype.resolvedOptions", "NumberFormat");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedNumberFormat) {
platform.raiseNeedObjectOfType("NumberFormat.prototype.resolvedOptions", "NumberFormat");
}
var resolvedOptions = setPrototype({}, null);
callInstanceFunc(ArrayInstanceForEach, options, function (option) {
if (typeof hiddenObject['__' + option] !== 'undefined') {
resolvedOptions[option] = hiddenObject['__' + option];
}
});
return setPrototype(resolvedOptions, {});
}, writable: true, enumerable: false, configurable: true
});
ObjectDefineProperty(NumberFormat.prototype, 'format', {
get: tagPublicFunction('get format', function () {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("NumberFormat.prototype.format", "NumberFormat");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedNumberFormat) {
platform.raiseNeedObjectOfType("NumberFormat.prototype.format", "NumberFormat");
}
return hiddenObject.__boundFormat;
}), enumerable: false, configurable: true
});
return NumberFormat;
}
}
// 'Init.NumberFormat' not defined if reached here. Return 'undefined'
return undefined;
})();
// Intl.DateTimeFormat, Date.prototype.toLocaleString, Date.prototype.toLocaleDateString, Date.prototype.toLocaleTimeString
var DateTimeFormat = (function () {
if (InitType === 'Intl' || InitType === 'Date') {
function ToDateTimeOptions(options, required, defaults) {
if (options === undefined) {
options = setPrototype({}, null);
} else {
options = Internal.ToObject(options);
}
var needDefaults = true;
if (required === "date" || required === "any") {
if (options.weekday !== undefined || options.year !== undefined || options.month !== undefined || options.day !== undefined) {
needDefaults = false;
}
}
if (required === "time" || required === "any") {
if (options.hour !== undefined || options.minute !== undefined || options.second !== undefined) {
needDefaults = false;
}
}
if (needDefaults && (defaults === "date" || defaults === "all")) {
ObjectDefineProperty(options, "year", {
value: "numeric", writable: true,
enumerable: true, configurable: true
});
ObjectDefineProperty(options, "month", {
value: "numeric", writable: true,
enumerable: true, configurable: true
});
ObjectDefineProperty(options, "day", {
value: "numeric", writable: true,
enumerable: true, configurable: true
});
}
if (needDefaults && (defaults === "time" || defaults === "all")) {
ObjectDefineProperty(options, "hour", {
value: "numeric", writable: true,
enumerable: true, configurable: true
});
ObjectDefineProperty(options, "minute", {
value: "numeric", writable: true,
enumerable: true, configurable: true
});
ObjectDefineProperty(options, "second", {
value: "numeric", writable: true,
enumerable: true, configurable: true
});
}
return options;
}
// Currently you cannot format date pieces and time pieces together, so this builds up a format template for each separately.
function EcmaOptionsToWindowsTemplate(options) {
var template = [];
if (options.weekday) {
if (options.weekday === 'narrow' || options.weekday === 'short') {
callInstanceFunc(ArrayInstancePush, template, 'dayofweek.abbreviated');
} else if (options.weekday === 'long') {
callInstanceFunc(ArrayInstancePush, template, 'dayofweek.full');
}
}
// TODO: Era not supported
if (options.year) {
if (options.year === '2-digit') {
callInstanceFunc(ArrayInstancePush, template, 'year.abbreviated');
} else if (options.year === 'numeric') {
callInstanceFunc(ArrayInstancePush, template, 'year.full');
}
}
if (options.month) {
if (options.month === '2-digit' || options.month === 'numeric') {
callInstanceFunc(ArrayInstancePush, template, 'month.numeric')
} else if (options.month === 'short' || options.month === 'narrow') {
callInstanceFunc(ArrayInstancePush, template, 'month.abbreviated');
} else if (options.month === 'long') {
callInstanceFunc(ArrayInstancePush, template, 'month.full');
}
}
if (options.day) {
callInstanceFunc(ArrayInstancePush, template, 'day');
}
if (options.timeZoneName) {
if (options.timeZoneName === "short") {
callInstanceFunc(ArrayInstancePush, template, 'timezone.abbreviated');
} else if (options.timeZoneName === "long") {
callInstanceFunc(ArrayInstancePush, template, 'timezone.full');
}
}
callInstanceFunc(ArrayInstanceForEach, ['hour', 'minute', 'second'], function (opt) {
if (options[opt]) {
callInstanceFunc(ArrayInstancePush, template, opt);
}
});
// TODO: Timezone Name not supported.
return getArrayLength(template) > 0 ? callInstanceFunc(ArrayInstanceJoin, template, ' ') : undefined;
}
var WindowsToEcmaCalendarMap = {
'GregorianCalendar': 'gregory',
'HebrewCalendar': 'hebrew',
'HijriCalendar': 'islamic',
'JapaneseCalendar': 'japanese',
'JulianCalendar': 'julian',
'KoreanCalendar': 'korean',
'UmAlQuraCalendar': 'islamic-civil',
'ThaiCalendar': 'thai',
'TaiwanCalendar': 'taiwan'
};
function WindowsToEcmaCalendar(calendar) {
if (typeof calendar === 'undefined') {
return '';
}
return WindowsToEcmaCalendarMap[calendar] || 'gregory';
}
// Certain formats have similar patterns on both ecma and windows; will use helper methods for them
function correctWeekdayEraMonthPattern(patternString, userValue, searchParam) {
// parts[1] is either dayofweek.solo, dayofweek, era or month; parts[2] is either abbreviated or full
var parts = platform.builtInRegexMatch(patternString, RegExp("{(" + searchParam + "(?:\\.solo)?)\\.([a-z]*)(?:\\([0-9]\\))?}"));
// If this happens that means windows removed the specific pattern (which isn't expected; but better be safe)
if (parts === null) {
RaiseAssert(new Error("Error when correcting windows returned weekday/Era/Month pattern; regex returned null. \nInput was: '" + patternString + "'\nRegex: '" + "{(" + searchParam + "(\\.solo)?)\\.([a-z]*)(\\([0-9]\\))?}'"));
return patternString;
}
if (parts[2] !== "full" && userValue === "long") {
return callInstanceFunc(StringInstanceReplace, patternString, parts[0], "{" + parts[1] + "." + "full" + "}");
} else if (userValue !== "long") {
return callInstanceFunc(StringInstanceReplace, patternString, parts[0], "{" + parts[1] + "." + (userValue === "short" ? "abbreviated" : "abbreviated(1)") + "}");
}
return patternString;
}
function correctDayHourMinuteSecondMonthPattern(patternString, userValue, searchParam) {
// parts[1] is either month, day, hour, minute, or second
// REVIEW (doilij) is it even possible to have a '.solo' (i.e. /(?:\\.solo)?/ ) in the above cases?
var parts = platform.builtInRegexMatch(patternString, RegExp("{(" + searchParam + ")(?:\\.solo)?\\.([a-z]*)(?:\\([0-9]\\))?}"));
if (parts === null) {
RaiseAssert(new Error("Error when correcting windows returned day/hour/minute/second/month pattern; regex returned null. \nInput was: '" + patternString + "'\nRegex: '" + "{(" + searchParam + "(\\.solo)?)\\.([a-z]*)(\\([0-9]\\))?}'"));
return patternString;
}
// Only correct the 2 digit; unless part[2] isn't integer
if (userValue === "2-digit") {
return callInstanceFunc(StringInstanceReplace, patternString, parts[0], "{" + parts[1] + ".integer(2)}");
} else if (parts[2] !== "integer") {
return callInstanceFunc(StringInstanceReplace, patternString, parts[0], "{" + parts[1] + ".integer}");
}
return patternString;
}
// Perhaps the level of validation that we have might not be required for this method
function updatePatternStrings(patternString, dateTimeFormat) {
if (dateTimeFormat.__weekday !== undefined) {
patternString = correctWeekdayEraMonthPattern(patternString, dateTimeFormat.__weekday, "dayofweek");
}
if (dateTimeFormat.__era !== undefined) {
// This is commented because not all options are supported for locales that do have era;
// In addition, we can't force era to be part of a locale using templates.
// patternString = correctWeekdayEraMonthPattern(patternString, dateTimeFormat.__era, "era", 2);
}
if (dateTimeFormat.__year === "2-digit") {
var parts = platform.builtInRegexMatch(patternString, /\{year\.[a-z]*(\([0-9]\))?\}/);
if (parts === null) {
RaiseAssert(new Error("Error when correcting windows returned year; regex returned null"));
} else {
patternString = callInstanceFunc(StringInstanceReplace, patternString, parts[0], "{year.abbreviated(2)}");
}
} else if (dateTimeFormat.__year === "full") {
var parts = platform.builtInRegexMatch(patternString, /\{year\.[a-z]*(\([0-9]\))?\}/);
if (parts === null) {
RaiseAssert(new Error("Error when correcting windows returned year; regex returned null"));
} else {
patternString = callInstanceFunc(StringInstanceReplace, patternString, parts[0], "{year.full}");
}
}
// Month partially overlaps with weekday/month; unless it's 2-digit or numeric in which case it overlaps with day/hour/minute/second
if (dateTimeFormat.__month !== undefined && dateTimeFormat.__month !== "2-digit" && dateTimeFormat.__month !== "numeric") {
patternString = correctWeekdayEraMonthPattern(patternString, dateTimeFormat.__month, "month");
} else if (dateTimeFormat.__month !== undefined) {
patternString = correctDayHourMinuteSecondMonthPattern(patternString, dateTimeFormat.__month, "month");
}
if (dateTimeFormat.__day !== undefined) {
patternString = correctDayHourMinuteSecondMonthPattern(patternString, dateTimeFormat.__day, "day");
}
if (dateTimeFormat.__hour !== undefined) {
patternString = correctDayHourMinuteSecondMonthPattern(patternString, dateTimeFormat.__hour, "hour");
}
if (dateTimeFormat.__minute !== undefined) {
patternString = correctDayHourMinuteSecondMonthPattern(patternString, dateTimeFormat.__minute, "minute");
}
if (dateTimeFormat.__second !== undefined) {
patternString = correctDayHourMinuteSecondMonthPattern(patternString, dateTimeFormat.__second, "second");
}
if (dateTimeFormat.__timeZoneName !== undefined) {
patternString = correctWeekdayEraMonthPattern(patternString, dateTimeFormat.__timeZoneName, "timezone");
}
return patternString;
}
function InitializeDateTimeFormat(dateTimeFormat, localeList, options) {
if (typeof dateTimeFormat != "object") {
platform.raiseNeedObject();
}
if (callInstanceFunc(ObjectInstanceHasOwnProperty, dateTimeFormat, '__initializedIntlObject') && dateTimeFormat.__initializedIntlObject) {
platform.raiseObjectIsAlreadyInitialized("DateTimeFormat", "DateTimeFormat");
}
dateTimeFormat.__initializedIntlObject = true;
// Extract the options
options = ToDateTimeOptions(options, "any", "date");
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
var timeZone = GetOption(options, "timeZone", "string", undefined, undefined);
if (timeZone !== undefined) {
timeZone = platform.validateAndCanonicalizeTimeZone(timeZone);
} else {
timeZone = platform.getDefaultTimeZone();
}
if (timeZone === undefined) {
platform.raiseOptionValueOutOfRange();
}
// Format options
var weekday = GetOption(options, "weekday", "string", ['narrow', 'short', 'long'], undefined);
var era = GetOption(options, "era", "string", ['narrow', 'short', 'long'], undefined);
var year = GetOption(options, "year", "string", ['2-digit', 'numeric'], undefined);
var month = GetOption(options, "month", "string", ['2-digit', 'numeric', 'narrow', 'short', 'long'], undefined);
var day = GetOption(options, "day", "string", ['2-digit', 'numeric'], undefined);
var hour = GetOption(options, "hour", "string", ['2-digit', 'numeric'], undefined);
var minute = GetOption(options, "minute", "string", ['2-digit', 'numeric'], undefined);
var second = GetOption(options, "second", "string", ['2-digit', 'numeric'], undefined);
var timeZoneName = GetOption(options, "timeZoneName", "string", ['short', 'long'], undefined);
var hour12 = hour ? GetOption(options, "hour12", "boolean", undefined, undefined) : undefined;
var formatMatcher = GetOption(options, "formatMatcher", "string", ["basic", "best fit"], "best fit");
var windowsClock = hour12 !== undefined ? (hour12 ? "12HourClock" : "24HourClock") : undefined;
var templateString = EcmaOptionsToWindowsTemplate(setPrototype({
weekday: weekday,
era: era,
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
timeZoneName: timeZoneName
}, null));
// Deal with the locale
localeList = CanonicalizeLocaleList(localeList);
var resolvedLocaleInfo = resolveLocales(localeList, matcher, ["nu", "ca"], strippedDefaultLocale);
// Assign the options
dateTimeFormat.__matcher = matcher;
dateTimeFormat.__timeZone = timeZone;
dateTimeFormat.__locale = resolvedLocaleInfo.locale;
// Format options
dateTimeFormat.__weekday = weekday;
dateTimeFormat.__era = era;
dateTimeFormat.__year = year;
dateTimeFormat.__month = month;
dateTimeFormat.__day = day;
dateTimeFormat.__hour = hour;
dateTimeFormat.__minute = minute;
dateTimeFormat.__second = second;
dateTimeFormat.__timeZoneName = timeZoneName;
dateTimeFormat.__hour12 = hour12;
dateTimeFormat.__formatMatcher = formatMatcher;
dateTimeFormat.__windowsClock = windowsClock;
dateTimeFormat.__templateString = templateString;
/*
* NOTE:
* Pattern string's are position-sensitive; while templates are not.
* If we specify {hour.integer(2)}:{minute.integer(2)} pattern string; we will always format as HH:mm.
* On the other hand, template strings don't give as fine granularity for options; and the platform decides how long month.abbreviated should be.
* Therefore, we have to create using template strings; and then change the .abbreivated/.integer values to have correct digits count if necessary.
* Thus, this results in this redundant looking code to create dateTimeFormat twice.
*/
var errorThrown = false;
try {
// Create the DateTimeFormatter to extract pattern strings
CreateDateTimeFormat(dateTimeFormat, false);
} catch (e) {
// Rethrow SOE or OOM
throwExIfOOMOrSOE(e);
// We won't throw for the first exception, but assume the template strings were rejected.
// Instead, we will try to fall back to default template strings.
var defaultOptions = ToDateTimeOptions(options, "none", "all");
dateTimeFormat.__templateString = EcmaOptionsToWindowsTemplate(defaultOptions, null);
errorThrown = true;
}
if (!errorThrown) {
// Update the pattern strings
dateTimeFormat.__templateString = updatePatternStrings(dateTimeFormat.__patternStrings[0], dateTimeFormat);
}
try {
// Cache the date time formatter
CreateDateTimeFormat(dateTimeFormat, true);
} catch (e) {
// Rethrow SOE or OOM
throwExIfOOMOrSOE(e);
// Otherwise, Generic message to cover the exception throw from the platform.
// The platform's exception is also generic and in most if not all cases specifies that "a" argument is invalid.
// We have no other information from the platform on the cause of the exception.
platform.raiseOptionValueOutOfRange();
}
// Correct the api's updated
dateTimeFormat.__calendar = WindowsToEcmaCalendar(dateTimeFormat.__windowsCalendar);
dateTimeFormat.__numberingSystem = callInstanceFunc(StringInstanceToLowerCase, dateTimeFormat.__numberingSystem);
if (dateTimeFormat.__hour !== undefined) {
dateTimeFormat.__hour12 = dateTimeFormat.__windowsClock === "12HourClock";
}
dateTimeFormat.__initializedDateTimeFormat = true;
}
// caches for objects constructed with default parameters for each method
let __DateInstanceToLocaleStringDefaultCache = [undefined, undefined, undefined];
const __DateInstanceToLocaleStringDefaultCacheSlot = setPrototype({
toLocaleString: 0,
toLocaleDateString: 1,
toLocaleTimeString: 2
}, null);
function DateInstanceToLocaleStringImplementation(name, option1, option2, cacheSlot, locales, options) {
if (typeof this !== 'object' || !(this instanceof Date)) {
platform.raiseNeedObjectOfType(name, "Date");
}
let value = callInstanceFunc(DateInstanceGetDate, new Date(this));
if (isNaN(value) || !isFinite(value)) {
return "Invalid Date";
}
let stateObject = undefined;
if (platform.useCaches && !locales && !options) {
// All default parameters (locales and options): this is the most valuable case to cache.
if (__DateInstanceToLocaleStringDefaultCache[cacheSlot]) {
// retrieve cached value
stateObject = __DateInstanceToLocaleStringDefaultCache[cacheSlot];
} else {
// populate cache
stateObject = setPrototype({}, null);
InitializeDateTimeFormat(stateObject, undefined, ToDateTimeOptions(undefined, option1, option2));
__DateInstanceToLocaleStringDefaultCache[cacheSlot] = stateObject;
}
}
if (!stateObject) {
stateObject = setPrototype({}, null);
InitializeDateTimeFormat(stateObject, locales, ToDateTimeOptions(options, option1, option2));
}
return String(platform.formatDateTime(Internal.ToNumber(this), stateObject));
}
// Note: tagPublicFunction (platform.tagPublicLibraryCode) messes with declared name of the FunctionBody so that
// the functions called appear correctly in the debugger and stack traces. Thus, we we cannot call tagPublicFunction in a loop.
// Each entry point needs to have its own unique FunctionBody (which is a function as defined in the source code);
// this is why we have seemingly repeated ourselves below, instead of having one function and calling it multiple times with
// different parameters.
//
// The following invocations of `platform.registerBuiltInFunction(tagPublicFunction(name, entryPoint))` are enclosed in IIFEs.
// The IIFEs are used to group all of the meaningful differences between each entry point into the arguments to the IIFE.
// The exception to this are the different entryPoint names which are only significant for debugging (and cannot be passed in
// as arguments, as the name is intrinsic to the function declaration).
//
// The `date_toLocale*String_entryPoint` function names are placeholder names that will never be seen from user code.
// The function name property and FunctionBody declared name are overwritten by `tagPublicFunction`.
// The fact that they are declared with unique names is helpful for debugging.
// The functions *must not* be declared as anonymous functions (must be declared with a name);
// converting from an unnnamed function to a named function is not readily supported by the platform code and
// this has caused us to hit assertions in debug builds in the past.
//
// See invocations of `tagPublicFunction` on the `supportedLocalesOf` entry points for a similar pattern.
//
// The entryPoint functions will be called as `Date.prototype.toLocale*String` and thus their `this` parameters will be a Date.
// `DateInstanceToLocaleStringImplementation` is not on `Date.prototype`, so we must propagate `this` into the call by using
// `DateInstanceToLocaleStringImplementation.call(this, ...)`.
(function (name, option1, option2, cacheSlot, platformFunctionID) {
platform.registerBuiltInFunction(tagPublicFunction(name, function date_toLocaleString_entryPoint(locales = undefined, options = undefined) {
return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
}), platformFunctionID);
})("Date.prototype.toLocaleString", "any", "all", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleString, IntlBuiltInFunctionID.DateToLocaleString);
(function (name, option1, option2, cacheSlot, platformFunctionID) {
platform.registerBuiltInFunction(tagPublicFunction(name, function date_toLocaleDateString_entryPoint(locales = undefined, options = undefined) {
return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
}), platformFunctionID);
})("Date.prototype.toLocaleDateString", "date", "date", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleDateString, IntlBuiltInFunctionID.DateToLocaleDateString);
(function (name, option1, option2, cacheSlot, platformFunctionID) {
platform.registerBuiltInFunction(tagPublicFunction(name, function date_toLocaleTimeString_entryPoint(locales = undefined, options = undefined) {
return DateInstanceToLocaleStringImplementation.call(this, name, option1, option2, cacheSlot, locales, options);
}), platformFunctionID);
})("Date.prototype.toLocaleTimeString", "time", "time", __DateInstanceToLocaleStringDefaultCacheSlot.toLocaleTimeString, IntlBuiltInFunctionID.DateToLocaleTimeString);
if (InitType === 'Intl') {
function DateTimeFormat(locales = undefined, options = undefined) {
if (this === Intl || this === undefined) {
return new DateTimeFormat(locales, options);
}
let obj = Internal.ToObject(this);
if (!ObjectIsExtensible(obj)) {
platform.raiseObjectIsNonExtensible("DateTimeFormat");
}
// Use the hidden object to store data
let hiddenObject = platform.getHiddenObject(obj);
if (hiddenObject === undefined) {
hiddenObject = setPrototype({}, null);
platform.setHiddenObject(obj, hiddenObject);
}
InitializeDateTimeFormat(hiddenObject, locales, options);
hiddenObject.__boundFormat = callInstanceFunc(FunctionInstanceBind, format, obj);
delete hiddenObject.__boundFormat.name;
return obj;
}
tagPublicFunction("Intl.DateTimeFormat", DateTimeFormat);
function format(date) {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("DateTimeFormat.prototype.format", "DateTimeFormat");
}
let hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedDateTimeFormat) {
platform.raiseNeedObjectOfType("DateTimeFormat.prototype.format", "DateTimeFormat");
}
if (isNaN(date) || !isFinite(date)) {
platform.raiseInvalidDate();
}
let dateValue = undefined;
if (date) {
dateValue = Internal.ToNumber(date)
} else {
dateValue = DateNow();
}
return String(platform.formatDateTime(dateValue, hiddenObject));
}
tagPublicFunction("Intl.DateTimeFormat.prototype.format", format);
DateTimeFormat.__relevantExtensionKeys = ['ca', 'nu'];
ObjectDefineProperty(DateTimeFormat, 'prototype', { value: new DateTimeFormat(), writable: false, enumerable: false, configurable: false });
setPrototype(DateTimeFormat.prototype, Object.prototype);
ObjectDefineProperty(DateTimeFormat.prototype, 'constructor', { value: DateTimeFormat, writable: true, enumerable: false, configurable: true });
ObjectDefineProperty(DateTimeFormat.prototype, 'format', {
get: tagPublicFunction('get format', function () {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("DateTimeFormat.prototype.format", "DateTimeFormat");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedDateTimeFormat) {
platform.raiseNeedObjectOfType("DateTimeFormat.prototype.format", "DateTimeFormat");
}
return hiddenObject.__boundFormat;
}), enumerable: false, configurable: true
});
ObjectDefineProperty(DateTimeFormat.prototype, 'resolvedOptions', {
value: function resolvedOptions() {
if (typeof this !== 'object') {
platform.raiseNeedObjectOfType("DateTimeFormat.prototype.resolvedOptions", "DateTimeFormat");
}
var hiddenObject = platform.getHiddenObject(this);
if (hiddenObject === undefined || !hiddenObject.__initializedDateTimeFormat) {
platform.raiseNeedObjectOfType("DateTimeFormat.prototype.resolvedOptions", "DateTimeFormat");
}
var temp = setPrototype({
locale: hiddenObject.__locale,
calendar: hiddenObject.__calendar, // ca unicode extension
numberingSystem: hiddenObject.__numberingSystem, // nu unicode extension
timeZone: hiddenObject.__timeZone,
hour12: hiddenObject.__hour12,
weekday: hiddenObject.__weekday,
era: hiddenObject.__era,
year: hiddenObject.__year,
month: hiddenObject.__month,
day: hiddenObject.__day,
hour: hiddenObject.__hour,
minute: hiddenObject.__minute,
second: hiddenObject.__second,
timeZoneName: hiddenObject.__timeZoneName
}, null)
var options = setPrototype({}, null);
callInstanceFunc(ArrayInstanceForEach, ObjectGetOwnPropertyNames(temp), function (prop) {
if ((temp[prop] !== undefined || prop === 'timeZone') && callInstanceFunc(ObjectInstanceHasOwnProperty, hiddenObject, "__" + prop)) {
options[prop] = temp[prop];
}
}, hiddenObject);
return setPrototype(options, Object.prototype);
}, writable: true, enumerable: false, configurable: true
});
ObjectDefineProperty(DateTimeFormat, 'supportedLocalesOf', { value: dateTimeFormat_supportedLocalesOf, writable: true, configurable: true });
return DateTimeFormat;
}
}
// 'Init.DateTimeFormat' not defined if reached here. Return 'undefined'
return undefined;
})();
// Initialize Intl properties only if needed
if (InitType === 'Intl') {
ObjectDefineProperty(Intl, "Collator", { value: Collator, writable: true, enumerable: false, configurable: true });
ObjectDefineProperty(Intl, "NumberFormat", { value: NumberFormat, writable: true, enumerable: false, configurable: true });
ObjectDefineProperty(Intl, "DateTimeFormat", { value: DateTimeFormat, writable: true, enumerable: false, configurable: true });
ObjectDefineProperty(Intl, "getCanonicalLocales", { value: getCanonicalLocales, writable: true, enumerable: false, configurable: true });
}
});