intl
Version:
Polyfill the ECMA-402 Intl API (except collation)
1,142 lines (955 loc) • 45 kB
JavaScript
// 12.1 The Intl.DateTimeFormat constructor
// ==================================
import {
toLatinUpperCase,
} from './6.locales-currencies-tz.js';
import {
Intl,
} from "./8.intl.js";
import {
CanonicalizeLocaleList,
ResolveLocale,
GetOption,
SupportedLocales,
} from "./9.negotiation.js";
import {
FormatNumber,
} from "./11.numberformat.js";
import {
createDateTimeFormats,
} from "./cldr";
import {
internals,
es3,
fnBind,
defineProperty,
toObject,
getInternalProperties,
createRegExpRestore,
secret,
Record,
List,
hop,
objCreate,
arrPush,
arrIndexOf,
} from './util.js';
// An object map of date component keys, saves using a regex later
const dateWidths = objCreate(null, { narrow:{}, short:{}, long:{} });
/**
* Returns a string for a date component, resolved using multiple inheritance as specified
* as specified in the Unicode Technical Standard 35.
*/
function resolveDateString(data, ca, component, width, key) {
// From http://www.unicode.org/reports/tr35/tr35.html#Multiple_Inheritance:
// 'In clearly specified instances, resources may inherit from within the same locale.
// For example, ... the Buddhist calendar inherits from the Gregorian calendar.'
let obj = data[ca] && data[ca][component]
? data[ca][component]
: data.gregory[component],
// "sideways" inheritance resolves strings when a key doesn't exist
alts = {
narrow: ['short', 'long'],
short: ['long', 'narrow'],
long: ['short', 'narrow'],
},
//
resolved = hop.call(obj, width)
? obj[width]
: hop.call(obj, alts[width][0])
? obj[alts[width][0]]
: obj[alts[width][1]];
// `key` wouldn't be specified for components 'dayPeriods'
return key !== null ? resolved[key] : resolved;
}
// Define the DateTimeFormat constructor internally so it cannot be tainted
export function DateTimeFormatConstructor () {
let locales = arguments[0];
let options = arguments[1];
if (!this || this === Intl) {
return new Intl.DateTimeFormat(locales, options);
}
return InitializeDateTimeFormat(toObject(this), locales, options);
}
defineProperty(Intl, 'DateTimeFormat', {
configurable: true,
writable: true,
value: DateTimeFormatConstructor,
});
// Must explicitly set prototypes as unwritable
defineProperty(DateTimeFormatConstructor, 'prototype', {
writable: false,
});
/**
* The abstract operation InitializeDateTimeFormat accepts the arguments dateTimeFormat
* (which must be an object), locales, and options. It initializes dateTimeFormat as a
* DateTimeFormat object.
*/
export function/* 12.1.1.1 */InitializeDateTimeFormat (dateTimeFormat, locales, options) {
// This will be a internal properties object if we're not already initialized
let internal = getInternalProperties(dateTimeFormat);
// Create an object whose props can be used to restore the values of RegExp props
let regexpState = createRegExpRestore();
// 1. If dateTimeFormat has an [[initializedIntlObject]] internal property with
// value true, throw a TypeError exception.
if (internal['[[initializedIntlObject]]'] === true)
throw new TypeError('`this` object has already been initialized as an Intl object');
// Need this to access the `internal` object
defineProperty(dateTimeFormat, '__getInternalProperties', {
value: function () {
// NOTE: Non-standard, for internal use only
if (arguments[0] === secret)
return internal;
},
});
// 2. Set the [[initializedIntlObject]] internal property of numberFormat to true.
internal['[[initializedIntlObject]]'] = true;
// 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
// abstract operation (defined in 9.2.1) with argument locales.
let requestedLocales = CanonicalizeLocaleList(locales);
// 4. Let options be the result of calling the ToDateTimeOptions abstract
// operation (defined below) with arguments options, "any", and "date".
options = ToDateTimeOptions(options, 'any', 'date');
// 5. Let opt be a new Record.
let opt = new Record();
// 6. Let matcher be the result of calling the GetOption abstract operation
// (defined in 9.2.9) with arguments options, "localeMatcher", "string", a List
// containing the two String values "lookup" and "best fit", and "best fit".
let matcher = GetOption(options, 'localeMatcher', 'string', new List('lookup', 'best fit'), 'best fit');
// 7. Set opt.[[localeMatcher]] to matcher.
opt['[[localeMatcher]]'] = matcher;
// 8. Let DateTimeFormat be the standard built-in object that is the initial
// value of Intl.DateTimeFormat.
let DateTimeFormat = internals.DateTimeFormat; // This is what we *really* need
// 9. Let localeData be the value of the [[localeData]] internal property of
// DateTimeFormat.
let localeData = DateTimeFormat['[[localeData]]'];
// 10. Let r be the result of calling the ResolveLocale abstract operation
// (defined in 9.2.5) with the [[availableLocales]] internal property of
// DateTimeFormat, requestedLocales, opt, the [[relevantExtensionKeys]]
// internal property of DateTimeFormat, and localeData.
let r = ResolveLocale(DateTimeFormat['[[availableLocales]]'], requestedLocales,
opt, DateTimeFormat['[[relevantExtensionKeys]]'], localeData);
// 11. Set the [[locale]] internal property of dateTimeFormat to the value of
// r.[[locale]].
internal['[[locale]]'] = r['[[locale]]'];
// 12. Set the [[calendar]] internal property of dateTimeFormat to the value of
// r.[[ca]].
internal['[[calendar]]'] = r['[[ca]]'];
// 13. Set the [[numberingSystem]] internal property of dateTimeFormat to the value of
// r.[[nu]].
internal['[[numberingSystem]]'] = r['[[nu]]'];
// The specification doesn't tell us to do this, but it's helpful later on
internal['[[dataLocale]]'] = r['[[dataLocale]]'];
// 14. Let dataLocale be the value of r.[[dataLocale]].
let dataLocale = r['[[dataLocale]]'];
// 15. Let tz be the result of calling the [[Get]] internal method of options with
// argument "timeZone".
let tz = options.timeZone;
// 16. If tz is not undefined, then
if (tz !== undefined) {
// a. Let tz be ToString(tz).
// b. Convert tz to upper case as described in 6.1.
// NOTE: If an implementation accepts additional time zone values, as permitted
// under certain conditions by the Conformance clause, different casing
// rules apply.
tz = toLatinUpperCase(tz);
// c. If tz is not "UTC", then throw a RangeError exception.
// ###TODO: accept more time zones###
if (tz !== 'UTC')
throw new RangeError('timeZone is not supported.');
}
// 17. Set the [[timeZone]] internal property of dateTimeFormat to tz.
internal['[[timeZone]]'] = tz;
// 18. Let opt be a new Record.
opt = new Record();
// 19. For each row of Table 3, except the header row, do:
for (let prop in dateTimeComponents) {
if (!hop.call(dateTimeComponents, prop))
continue;
// 20. Let prop be the name given in the Property column of the row.
// 21. Let value be the result of calling the GetOption abstract operation,
// passing as argument options, the name given in the Property column of the
// row, "string", a List containing the strings given in the Values column of
// the row, and undefined.
let value = GetOption(options, prop, 'string', dateTimeComponents[prop]);
// 22. Set opt.[[<prop>]] to value.
opt['[['+prop+']]'] = value;
}
// Assigned a value below
let bestFormat;
// 23. Let dataLocaleData be the result of calling the [[Get]] internal method of
// localeData with argument dataLocale.
let dataLocaleData = localeData[dataLocale];
// 24. Let formats be the result of calling the [[Get]] internal method of
// dataLocaleData with argument "formats".
// Note: we process the CLDR formats into the spec'd structure
let formats = ToDateTimeFormats(dataLocaleData.formats);
// 25. Let matcher be the result of calling the GetOption abstract operation with
// arguments options, "formatMatcher", "string", a List containing the two String
// values "basic" and "best fit", and "best fit".
matcher = GetOption(options, 'formatMatcher', 'string', new List('basic', 'best fit'), 'best fit');
// Optimization: caching the processed formats as a one time operation by
// replacing the initial structure from localeData
dataLocaleData.formats = formats;
// 26. If matcher is "basic", then
if (matcher === 'basic') {
// 27. Let bestFormat be the result of calling the BasicFormatMatcher abstract
// operation (defined below) with opt and formats.
bestFormat = BasicFormatMatcher(opt, formats);
// 28. Else
} else {
{
// diverging
let hr12 = GetOption(options, 'hour12', 'boolean'/*, undefined, undefined*/);
opt.hour12 = hr12 === undefined ? dataLocaleData.hour12 : hr12;
}
// 29. Let bestFormat be the result of calling the BestFitFormatMatcher
// abstract operation (defined below) with opt and formats.
bestFormat = BestFitFormatMatcher(opt, formats);
}
// 30. For each row in Table 3, except the header row, do
for (let prop in dateTimeComponents) {
if (!hop.call(dateTimeComponents, prop))
continue;
// a. Let prop be the name given in the Property column of the row.
// b. Let pDesc be the result of calling the [[GetOwnProperty]] internal method of
// bestFormat with argument prop.
// c. If pDesc is not undefined, then
if (hop.call(bestFormat, prop)) {
// i. Let p be the result of calling the [[Get]] internal method of bestFormat
// with argument prop.
let p = bestFormat[prop];
{
// diverging
p = bestFormat._ && hop.call(bestFormat._, prop) ? bestFormat._[prop] : p;
}
// ii. Set the [[<prop>]] internal property of dateTimeFormat to p.
internal['[['+prop+']]'] = p;
}
}
let pattern; // Assigned a value below
// 31. Let hr12 be the result of calling the GetOption abstract operation with
// arguments options, "hour12", "boolean", undefined, and undefined.
let hr12 = GetOption(options, 'hour12', 'boolean'/*, undefined, undefined*/);
// 32. If dateTimeFormat has an internal property [[hour]], then
if (internal['[[hour]]']) {
// a. If hr12 is undefined, then let hr12 be the result of calling the [[Get]]
// internal method of dataLocaleData with argument "hour12".
hr12 = hr12 === undefined ? dataLocaleData.hour12 : hr12;
// b. Set the [[hour12]] internal property of dateTimeFormat to hr12.
internal['[[hour12]]'] = hr12;
// c. If hr12 is true, then
if (hr12 === true) {
// i. Let hourNo0 be the result of calling the [[Get]] internal method of
// dataLocaleData with argument "hourNo0".
let hourNo0 = dataLocaleData.hourNo0;
// ii. Set the [[hourNo0]] internal property of dateTimeFormat to hourNo0.
internal['[[hourNo0]]'] = hourNo0;
// iii. Let pattern be the result of calling the [[Get]] internal method of
// bestFormat with argument "pattern12".
pattern = bestFormat.pattern12;
}
// d. Else
else
// i. Let pattern be the result of calling the [[Get]] internal method of
// bestFormat with argument "pattern".
pattern = bestFormat.pattern;
}
// 33. Else
else
// a. Let pattern be the result of calling the [[Get]] internal method of
// bestFormat with argument "pattern".
pattern = bestFormat.pattern;
// 34. Set the [[pattern]] internal property of dateTimeFormat to pattern.
internal['[[pattern]]'] = pattern;
// 35. Set the [[boundFormat]] internal property of dateTimeFormat to undefined.
internal['[[boundFormat]]'] = undefined;
// 36. Set the [[initializedDateTimeFormat]] internal property of dateTimeFormat to
// true.
internal['[[initializedDateTimeFormat]]'] = true;
// In ES3, we need to pre-bind the format() function
if (es3)
dateTimeFormat.format = GetFormatDateTime.call(dateTimeFormat);
// Restore the RegExp properties
regexpState.exp.test(regexpState.input);
// Return the newly initialised object
return dateTimeFormat;
}
/**
* Several DateTimeFormat algorithms use values from the following table, which provides
* property names and allowable values for the components of date and time formats:
*/
let dateTimeComponents = {
weekday: [ "narrow", "short", "long" ],
era: [ "narrow", "short", "long" ],
year: [ "2-digit", "numeric" ],
month: [ "2-digit", "numeric", "narrow", "short", "long" ],
day: [ "2-digit", "numeric" ],
hour: [ "2-digit", "numeric" ],
minute: [ "2-digit", "numeric" ],
second: [ "2-digit", "numeric" ],
timeZoneName: [ "short", "long" ],
};
/**
* When the ToDateTimeOptions abstract operation is called with arguments options,
* required, and defaults, the following steps are taken:
*/
function ToDateTimeFormats(formats) {
if (Object.prototype.toString.call(formats) === '[object Array]') {
return formats;
}
return createDateTimeFormats(formats);
}
/**
* When the ToDateTimeOptions abstract operation is called with arguments options,
* required, and defaults, the following steps are taken:
*/
export function ToDateTimeOptions (options, required, defaults) {
// 1. If options is undefined, then let options be null, else let options be
// ToObject(options).
if (options === undefined)
options = null;
else {
// (#12) options needs to be a Record, but it also needs to inherit properties
let opt2 = toObject(options);
options = new Record();
for (let k in opt2)
options[k] = opt2[k];
}
// 2. Let create be the standard built-in function object defined in ES5, 15.2.3.5.
let create = objCreate;
// 3. Let options be the result of calling the [[Call]] internal method of create with
// undefined as the this value and an argument list containing the single item
// options.
options = create(options);
// 4. Let needDefaults be true.
let needDefaults = true;
// 5. If required is "date" or "any", then
if (required === 'date' || required === 'any') {
// a. For each of the property names "weekday", "year", "month", "day":
// i. If the result of calling the [[Get]] internal method of options with the
// property name is not undefined, then let needDefaults be false.
if (options.weekday !== undefined || options.year !== undefined
|| options.month !== undefined || options.day !== undefined)
needDefaults = false;
}
// 6. If required is "time" or "any", then
if (required === 'time' || required === 'any') {
// a. For each of the property names "hour", "minute", "second":
// i. If the result of calling the [[Get]] internal method of options with the
// property name is not undefined, then let needDefaults be false.
if (options.hour !== undefined || options.minute !== undefined || options.second !== undefined)
needDefaults = false;
}
// 7. If needDefaults is true and defaults is either "date" or "all", then
if (needDefaults && (defaults === 'date' || defaults === 'all'))
// a. For each of the property names "year", "month", "day":
// i. Call the [[DefineOwnProperty]] internal method of options with the
// property name, Property Descriptor {[[Value]]: "numeric", [[Writable]]:
// true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
options.year = options.month = options.day = 'numeric';
// 8. If needDefaults is true and defaults is either "time" or "all", then
if (needDefaults && (defaults === 'time' || defaults === 'all'))
// a. For each of the property names "hour", "minute", "second":
// i. Call the [[DefineOwnProperty]] internal method of options with the
// property name, Property Descriptor {[[Value]]: "numeric", [[Writable]]:
// true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
options.hour = options.minute = options.second = 'numeric';
// 9. Return options.
return options;
}
/**
* When the BasicFormatMatcher abstract operation is called with two arguments options and
* formats, the following steps are taken:
*/
function BasicFormatMatcher (options, formats) {
// 1. Let removalPenalty be 120.
let removalPenalty = 120;
// 2. Let additionPenalty be 20.
let additionPenalty = 20;
// 3. Let longLessPenalty be 8.
let longLessPenalty = 8;
// 4. Let longMorePenalty be 6.
let longMorePenalty = 6;
// 5. Let shortLessPenalty be 6.
let shortLessPenalty = 6;
// 6. Let shortMorePenalty be 3.
let shortMorePenalty = 3;
// 7. Let bestScore be -Infinity.
let bestScore = -Infinity;
// 8. Let bestFormat be undefined.
let bestFormat;
// 9. Let i be 0.
let i = 0;
// 10. Assert: formats is an Array object.
// 11. Let len be the result of calling the [[Get]] internal method of formats with argument "length".
let len = formats.length;
// 12. Repeat while i < len:
while (i < len) {
// a. Let format be the result of calling the [[Get]] internal method of formats with argument ToString(i).
let format = formats[i];
// b. Let score be 0.
let score = 0;
// c. For each property shown in Table 3:
for (let property in dateTimeComponents) {
if (!hop.call(dateTimeComponents, property))
continue;
// i. Let optionsProp be options.[[<property>]].
let optionsProp = options['[['+ property +']]'];
// ii. Let formatPropDesc be the result of calling the [[GetOwnProperty]] internal method of format
// with argument property.
// iii. If formatPropDesc is not undefined, then
// 1. Let formatProp be the result of calling the [[Get]] internal method of format with argument property.
let formatProp = hop.call(format, property) ? format[property] : undefined;
// iv. If optionsProp is undefined and formatProp is not undefined, then decrease score by
// additionPenalty.
if (optionsProp === undefined && formatProp !== undefined)
score -= additionPenalty;
// v. Else if optionsProp is not undefined and formatProp is undefined, then decrease score by
// removalPenalty.
else if (optionsProp !== undefined && formatProp === undefined)
score -= removalPenalty;
// vi. Else
else {
// 1. Let values be the array ["2-digit", "numeric", "narrow", "short",
// "long"].
let values = [ '2-digit', 'numeric', 'narrow', 'short', 'long' ];
// 2. Let optionsPropIndex be the index of optionsProp within values.
let optionsPropIndex = arrIndexOf.call(values, optionsProp);
// 3. Let formatPropIndex be the index of formatProp within values.
let formatPropIndex = arrIndexOf.call(values, formatProp);
// 4. Let delta be max(min(formatPropIndex - optionsPropIndex, 2), -2).
let delta = Math.max(Math.min(formatPropIndex - optionsPropIndex, 2), -2);
// 5. If delta = 2, decrease score by longMorePenalty.
if (delta === 2)
score -= longMorePenalty;
// 6. Else if delta = 1, decrease score by shortMorePenalty.
else if (delta === 1)
score -= shortMorePenalty;
// 7. Else if delta = -1, decrease score by shortLessPenalty.
else if (delta === -1)
score -= shortLessPenalty;
// 8. Else if delta = -2, decrease score by longLessPenalty.
else if (delta === -2)
score -= longLessPenalty;
}
}
// d. If score > bestScore, then
if (score > bestScore) {
// i. Let bestScore be score.
bestScore = score;
// ii. Let bestFormat be format.
bestFormat = format;
}
// e. Increase i by 1.
i++;
}
// 13. Return bestFormat.
return bestFormat;
}
/**
* When the BestFitFormatMatcher abstract operation is called with two arguments options
* and formats, it performs implementation dependent steps, which should return a set of
* component representations that a typical user of the selected locale would perceive as
* at least as good as the one returned by BasicFormatMatcher.
*
* This polyfill defines the algorithm to be the same as BasicFormatMatcher,
* with the addition of bonus points awarded where the requested format is of
* the same data type as the potentially matching format.
*
* This algo relies on the concept of closest distance matching described here:
* http://unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons
* Typically a “best match” is found using a closest distance match, such as:
*
* Symbols requesting a best choice for the locale are replaced.
* j → one of {H, k, h, K}; C → one of {a, b, B}
* -> Covered by cldr.js matching process
*
* For fields with symbols representing the same type (year, month, day, etc):
* Most symbols have a small distance from each other.
* M ≅ L; E ≅ c; a ≅ b ≅ B; H ≅ k ≅ h ≅ K; ...
* -> Covered by cldr.js matching process
*
* Width differences among fields, other than those marking text vs numeric, are given small distance from each other.
* MMM ≅ MMMM
* MM ≅ M
* Numeric and text fields are given a larger distance from each other.
* MMM ≈ MM
* Symbols representing substantial differences (week of year vs week of month) are given much larger a distances from each other.
* d ≋ D; ...
* Missing or extra fields cause a match to fail. (But see Missing Skeleton Fields).
*
*
* For example,
*
* { month: 'numeric', day: 'numeric' }
*
* should match
*
* { month: '2-digit', day: '2-digit' }
*
* rather than
*
* { month: 'short', day: 'numeric' }
*
* This makes sense because a user requesting a formatted date with numeric parts would
* not expect to see the returned format containing narrow, short or long part names
*/
function BestFitFormatMatcher (options, formats) {
// 1. Let removalPenalty be 120.
let removalPenalty = 120;
// 2. Let additionPenalty be 20.
let additionPenalty = 20;
// 3. Let longLessPenalty be 8.
let longLessPenalty = 8;
// 4. Let longMorePenalty be 6.
let longMorePenalty = 6;
// 5. Let shortLessPenalty be 6.
let shortLessPenalty = 6;
// 6. Let shortMorePenalty be 3.
let shortMorePenalty = 3;
let hour12Penalty = 1;
// 7. Let bestScore be -Infinity.
let bestScore = -Infinity;
// 8. Let bestFormat be undefined.
let bestFormat;
// 9. Let i be 0.
let i = 0;
// 10. Assert: formats is an Array object.
// 11. Let len be the result of calling the [[Get]] internal method of formats with argument "length".
let len = formats.length;
// 12. Repeat while i < len:
while (i < len) {
// a. Let format be the result of calling the [[Get]] internal method of formats with argument ToString(i).
let format = formats[i];
// b. Let score be 0.
let score = 0;
// c. For each property shown in Table 3:
for (let property in dateTimeComponents) {
if (!hop.call(dateTimeComponents, property))
continue;
// i. Let optionsProp be options.[[<property>]].
let optionsProp = options['[['+ property +']]'];
// ii. Let formatPropDesc be the result of calling the [[GetOwnProperty]] internal method of format
// with argument property.
// iii. If formatPropDesc is not undefined, then
// 1. Let formatProp be the result of calling the [[Get]] internal method of format with argument property.
let formatProp = hop.call(format, property) ? format[property] : undefined;
// iv. If optionsProp is undefined and formatProp is not undefined, then decrease score by
// additionPenalty.
if (optionsProp === undefined && formatProp !== undefined)
score -= additionPenalty;
// v. Else if optionsProp is not undefined and formatProp is undefined, then decrease score by
// removalPenalty.
else if (optionsProp !== undefined && formatProp === undefined)
score -= removalPenalty;
// vi. Else
else {
// 1. Let values be the array ["2-digit", "numeric", "narrow", "short",
// "long"].
let values = [ '2-digit', 'numeric', 'narrow', 'short', 'long' ];
// 2. Let optionsPropIndex be the index of optionsProp within values.
let optionsPropIndex = arrIndexOf.call(values, optionsProp);
// 3. Let formatPropIndex be the index of formatProp within values.
let formatPropIndex = arrIndexOf.call(values, formatProp);
// 4. Let delta be max(min(formatPropIndex - optionsPropIndex, 2), -2).
let delta = Math.max(Math.min(formatPropIndex - optionsPropIndex, 2), -2);
{
// diverging from spec
// When the bestFit argument is true, subtract additional penalty where data types are not the same
if ((formatPropIndex <= 1 && optionsPropIndex >= 2) || (formatPropIndex >= 2 && optionsPropIndex <= 1)) {
// 5. If delta = 2, decrease score by longMorePenalty.
if (delta > 0)
score -= longMorePenalty;
else if (delta < 0)
score -= longLessPenalty;
} else {
// 5. If delta = 2, decrease score by longMorePenalty.
if (delta > 1)
score -= shortMorePenalty;
else if (delta < -1)
score -= shortLessPenalty;
}
}
}
}
{
// diverging to also take into consideration differences between 12 or 24 hours
// which is special for the best fit only.
if (format._.hour12 !== options.hour12) {
score -= hour12Penalty;
}
}
// d. If score > bestScore, then
if (score > bestScore) {
// i. Let bestScore be score.
bestScore = score;
// ii. Let bestFormat be format.
bestFormat = format;
}
// e. Increase i by 1.
i++;
}
// 13. Return bestFormat.
return bestFormat;
}
/* 12.2.3 */internals.DateTimeFormat = {
'[[availableLocales]]': [],
'[[relevantExtensionKeys]]': ['ca', 'nu'],
'[[localeData]]': {},
};
/**
* When the supportedLocalesOf method of Intl.DateTimeFormat is called, the
* following steps are taken:
*/
/* 12.2.2 */
defineProperty(Intl.DateTimeFormat, 'supportedLocalesOf', {
configurable: true,
writable: true,
value: fnBind.call(function (locales) {
// Bound functions only have the `this` value altered if being used as a constructor,
// this lets us imitate a native function that has no constructor
if (!hop.call(this, '[[availableLocales]]'))
throw new TypeError('supportedLocalesOf() is not a constructor');
// Create an object whose props can be used to restore the values of RegExp props
let regexpState = createRegExpRestore(),
// 1. If options is not provided, then let options be undefined.
options = arguments[1],
// 2. Let availableLocales be the value of the [[availableLocales]] internal
// property of the standard built-in object that is the initial value of
// Intl.NumberFormat.
availableLocales = this['[[availableLocales]]'],
// 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
// abstract operation (defined in 9.2.1) with argument locales.
requestedLocales = CanonicalizeLocaleList(locales);
// Restore the RegExp properties
regexpState.exp.test(regexpState.input);
// 4. Return the result of calling the SupportedLocales abstract operation
// (defined in 9.2.8) with arguments availableLocales, requestedLocales,
// and options.
return SupportedLocales(availableLocales, requestedLocales, options);
}, internals.NumberFormat),
});
/**
* This named accessor property returns a function that formats a number
* according to the effective locale and the formatting options of this
* DateTimeFormat object.
*/
/* 12.3.2 */defineProperty(Intl.DateTimeFormat.prototype, 'format', {
configurable: true,
get: GetFormatDateTime,
});
defineProperty(Intl.DateTimeFormat.prototype, 'formatToParts', {
configurable: true,
get: GetFormatToPartsDateTime,
});
function GetFormatDateTime() {
let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
// Satisfy test 12.3_b
if (!internal || !internal['[[initializedDateTimeFormat]]'])
throw new TypeError('`this` value for format() is not an initialized Intl.DateTimeFormat object.');
// The value of the [[Get]] attribute is a function that takes the following
// steps:
// 1. If the [[boundFormat]] internal property of this DateTimeFormat object
// is undefined, then:
if (internal['[[boundFormat]]'] === undefined) {
// a. Let F be a Function object, with internal properties set as
// specified for built-in functions in ES5, 15, or successor, and the
// length property set to 0, that takes the argument date and
// performs the following steps:
let F = function () {
// i. If date is not provided or is undefined, then let x be the
// result as if by the expression Date.now() where Date.now is
// the standard built-in function defined in ES5, 15.9.4.4.
// ii. Else let x be ToNumber(date).
// iii. Return the result of calling the FormatDateTime abstract
// operation (defined below) with arguments this and x.
let x = Number(arguments.length === 0 ? Date.now() : arguments[0]);
return FormatDateTime(this, x);
};
// b. Let bind be the standard built-in function object defined in ES5,
// 15.3.4.5.
// c. Let bf be the result of calling the [[Call]] internal method of
// bind with F as the this value and an argument list containing
// the single item this.
let bf = fnBind.call(F, this);
// d. Set the [[boundFormat]] internal property of this NumberFormat
// object to bf.
internal['[[boundFormat]]'] = bf;
}
// Return the value of the [[boundFormat]] internal property of this
// NumberFormat object.
return internal['[[boundFormat]]'];
}
function GetFormatToPartsDateTime() {
let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
if (!internal || !internal['[[initializedDateTimeFormat]]'])
throw new TypeError('`this` value for formatToParts() is not an initialized Intl.DateTimeFormat object.');
if (internal['[[boundFormatToParts]]'] === undefined) {
let F = function () {
let x = Number(arguments.length === 0 ? Date.now() : arguments[0]);
return FormatToPartsDateTime(this, x);
};
let bf = fnBind.call(F, this);
internal['[[boundFormatToParts]]'] = bf;
}
return internal['[[boundFormatToParts]]'];
}
function CreateDateTimeParts(dateTimeFormat, x) {
// 1. If x is not a finite Number, then throw a RangeError exception.
if (!isFinite(x))
throw new RangeError('Invalid valid date passed to format');
let internal = dateTimeFormat.__getInternalProperties(secret);
// Creating restore point for properties on the RegExp object... please wait
/* let regexpState = */createRegExpRestore(); // ###TODO: review this
// 2. Let locale be the value of the [[locale]] internal property of dateTimeFormat.
let locale = internal['[[locale]]'];
// 3. Let nf be the result of creating a new NumberFormat object as if by the
// expression new Intl.NumberFormat([locale], {useGrouping: false}) where
// Intl.NumberFormat is the standard built-in constructor defined in 11.1.3.
let nf = new Intl.NumberFormat([locale], {useGrouping: false});
// 4. Let nf2 be the result of creating a new NumberFormat object as if by the
// expression new Intl.NumberFormat([locale], {minimumIntegerDigits: 2, useGrouping:
// false}) where Intl.NumberFormat is the standard built-in constructor defined in
// 11.1.3.
let nf2 = new Intl.NumberFormat([locale], {minimumIntegerDigits: 2, useGrouping: false});
// 5. Let tm be the result of calling the ToLocalTime abstract operation (defined
// below) with x, the value of the [[calendar]] internal property of dateTimeFormat,
// and the value of the [[timeZone]] internal property of dateTimeFormat.
let tm = ToLocalTime(x, internal['[[calendar]]'], internal['[[timeZone]]']);
// 6. Let result be the value of the [[pattern]] internal property of dateTimeFormat.
let pattern = internal['[[pattern]]'];
// 7.
let result = new List();
// 8.
let index = 0;
// 9.
let beginIndex = pattern.indexOf('{');
// 10.
let endIndex = 0;
// Need the locale minus any extensions
let dataLocale = internal['[[dataLocale]]'];
// Need the calendar data from CLDR
let localeData = internals.DateTimeFormat['[[localeData]]'][dataLocale].calendars;
let ca = internal['[[calendar]]'];
// 11.
while (beginIndex !== -1) {
let fv;
// a.
endIndex = pattern.indexOf('}', beginIndex);
// b.
if (endIndex === -1) {
throw new Error('Unclosed pattern');
}
// c.
if (beginIndex > index) {
arrPush.call(result, {
type: 'literal',
value: pattern.substring(index, beginIndex),
});
}
// d.
let p = pattern.substring(beginIndex + 1, endIndex);
// e.
if (dateTimeComponents.hasOwnProperty(p)) {
// i. Let f be the value of the [[<p>]] internal property of dateTimeFormat.
let f = internal['[['+ p +']]'];
// ii. Let v be the value of tm.[[<p>]].
let v = tm['[['+ p +']]'];
// iii. If p is "year" and v ≤ 0, then let v be 1 - v.
if (p === 'year' && v <= 0) {
v = 1 - v;
}
// iv. If p is "month", then increase v by 1.
else if (p === 'month') {
v++;
}
// v. If p is "hour" and the value of the [[hour12]] internal property of
// dateTimeFormat is true, then
else if (p === 'hour' && internal['[[hour12]]'] === true) {
// 1. Let v be v modulo 12.
v = v % 12;
// 2. If v is 0 and the value of the [[hourNo0]] internal property of
// dateTimeFormat is true, then let v be 12.
if (v === 0 && internal['[[hourNo0]]'] === true) {
v = 12;
}
}
// vi. If f is "numeric", then
if (f === 'numeric') {
// 1. Let fv be the result of calling the FormatNumber abstract operation
// (defined in 11.3.2) with arguments nf and v.
fv = FormatNumber(nf, v);
}
// vii. Else if f is "2-digit", then
else if (f === '2-digit') {
// 1. Let fv be the result of calling the FormatNumber abstract operation
// with arguments nf2 and v.
fv = FormatNumber(nf2, v);
// 2. If the length of fv is greater than 2, let fv be the substring of fv
// containing the last two characters.
if (fv.length > 2) {
fv = fv.slice(-2);
}
}
// viii. Else if f is "narrow", "short", or "long", then let fv be a String
// value representing f in the desired form; the String value depends upon
// the implementation and the effective locale and calendar of
// dateTimeFormat. If p is "month", then the String value may also depend
// on whether dateTimeFormat has a [[day]] internal property. If p is
// "timeZoneName", then the String value may also depend on the value of
// the [[inDST]] field of tm.
else if (f in dateWidths) {
switch (p) {
case 'month':
fv = resolveDateString(localeData, ca, 'months', f, tm['[['+ p +']]']);
break;
case 'weekday':
try {
fv = resolveDateString(localeData, ca, 'days', f, tm['[['+ p +']]']);
// fv = resolveDateString(ca.days, f)[tm['[['+ p +']]']];
} catch (e) {
throw new Error('Could not find weekday data for locale '+locale);
}
break;
case 'timeZoneName':
fv = ''; // ###TODO
break;
case 'era':
try {
fv = resolveDateString(localeData, ca, 'eras', f, tm['[['+ p +']]']);
} catch (e) {
throw new Error('Could not find era data for locale '+locale);
}
break;
default:
fv = tm['[['+ p +']]'];
}
}
// ix
arrPush.call(result, {
type: p,
value: fv,
});
// f.
} else if (p === 'ampm') {
// i.
let v = tm['[[hour]]'];
// ii./iii.
fv = resolveDateString(localeData, ca, 'dayPeriods', v > 11 ? 'pm' : 'am', null);
// iv.
arrPush.call(result, {
type: 'dayPeriod',
value: fv,
});
// g.
} else {
arrPush.call(result, {
type: 'literal',
value: pattern.substring(beginIndex, endIndex + 1),
});
}
// h.
index = endIndex + 1;
// i.
beginIndex = pattern.indexOf('{', index);
}
// 12.
if (endIndex < pattern.length - 1) {
arrPush.call(result, {
type: 'literal',
value: pattern.substr(endIndex + 1),
});
}
// 13.
return result;
}
/**
* When the FormatDateTime abstract operation is called with arguments dateTimeFormat
* (which must be an object initialized as a DateTimeFormat) and x (which must be a Number
* value), it returns a String value representing x (interpreted as a time value as
* specified in ES5, 15.9.1.1) according to the effective locale and the formatting
* options of dateTimeFormat.
*/
export function FormatDateTime(dateTimeFormat, x) {
let parts = CreateDateTimeParts(dateTimeFormat, x);
let result = '';
for (let i = 0; parts.length > i; i++) {
let part = parts[i];
result += part.value;
}
return result;
}
function FormatToPartsDateTime(dateTimeFormat, x) {
let parts = CreateDateTimeParts(dateTimeFormat, x);
let result = [];
for (let i = 0; parts.length > i; i++) {
let part = parts[i];
result.push({
type: part.type,
value: part.value,
});
}
return result;
}
/**
* When the ToLocalTime abstract operation is called with arguments date, calendar, and
* timeZone, the following steps are taken:
*/
function ToLocalTime(date, calendar, timeZone) {
// 1. Apply calendrical calculations on date for the given calendar and time zone to
// produce weekday, era, year, month, day, hour, minute, second, and inDST values.
// The calculations should use best available information about the specified
// calendar and time zone. If the calendar is "gregory", then the calculations must
// match the algorithms specified in ES5, 15.9.1, except that calculations are not
// bound by the restrictions on the use of best available information on time zones
// for local time zone adjustment and daylight saving time adjustment imposed by
// ES5, 15.9.1.7 and 15.9.1.8.
// ###TODO###
let d = new Date(date),
m = 'get' + (timeZone || '');
// 2. Return a Record with fields [[weekday]], [[era]], [[year]], [[month]], [[day]],
// [[hour]], [[minute]], [[second]], and [[inDST]], each with the corresponding
// calculated value.
return new Record({
'[[weekday]]': d[m + 'Day'](),
'[[era]]' : +(d[m + 'FullYear']() >= 0),
'[[year]]' : d[m + 'FullYear'](),
'[[month]]' : d[m + 'Month'](),
'[[day]]' : d[m + 'Date'](),
'[[hour]]' : d[m + 'Hours'](),
'[[minute]]' : d[m + 'Minutes'](),
'[[second]]' : d[m + 'Seconds'](),
'[[inDST]]' : false, // ###TODO###
});
}
/**
* The function returns a new object whose properties and attributes are set as if
* constructed by an object literal assigning to each of the following properties the
* value of the corresponding internal property of this DateTimeFormat object (see 12.4):
* locale, calendar, numberingSystem, timeZone, hour12, weekday, era, year, month, day,
* hour, minute, second, and timeZoneName. Properties whose corresponding internal
* properties are not present are not assigned.
*/
/* 12.3.3 */defineProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', {
writable: true,
configurable: true,
value: function () {
let prop,
descs = new Record(),
props = [
'locale', 'calendar', 'numberingSystem', 'timeZone', 'hour12', 'weekday',
'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName',
],
internal = this !== null && typeof this === 'object' && getInternalProperties(this);
// Satisfy test 12.3_b
if (!internal || !internal['[[initializedDateTimeFormat]]'])
throw new TypeError('`this` value for resolvedOptions() is not an initialized Intl.DateTimeFormat object.');
for (let i = 0, max = props.length; i < max; i++) {
if (hop.call(internal, prop = '[[' + props[i] + ']]'))
descs[props[i]] = { value: internal[prop], writable: true, configurable: true, enumerable: true };
}
return objCreate({}, descs);
},
});