diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js index 950d02d4bb..f1ec0163a9 100644 --- a/js/src/builtin/Intl.js +++ b/js/src/builtin/Intl.js @@ -2228,812 +2228,3 @@ function Intl_NumberFormat_resolvedOptions() { } -/********** Intl.DateTimeFormat **********/ - - -/** - * Compute an internal properties object from |lazyDateTimeFormatData|. - */ -function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { - assert(IsObject(lazyDateTimeFormatData), "lazy data not an object?"); - - // Lazy DateTimeFormat data has the following structure: - // - // { - // requestedLocales: List of locales, - // - // localeOpt: // *first* opt computed in InitializeDateTimeFormat - // { - // localeMatcher: "lookup" / "best fit", - // - // hour12: true / false, // optional - // } - // - // timeZone: IANA time zone name, - // - // formatOpt: // *second* opt computed in InitializeDateTimeFormat - // { - // // all the properties/values listed in Table 3 - // // (weekday, era, year, month, day, &c.) - // } - // - // formatMatcher: "basic" / "best fit", - // } - // - // Note that lazy data is only installed as a final step of initialization, - // so every DateTimeFormat lazy data object has *all* these properties, - // never a subset of them. - - var internalProps = std_Object_create(null); - - // Compute effective locale. - // Step 8. - var DateTimeFormat = dateTimeFormatInternalProperties; - - // Step 9. - var localeData = DateTimeFormat.localeData; - - // Step 10. - var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), - lazyDateTimeFormatData.requestedLocales, - lazyDateTimeFormatData.localeOpt, - DateTimeFormat.relevantExtensionKeys, - localeData); - - // Steps 11-13. - internalProps.locale = r.locale; - internalProps.calendar = r.ca; - internalProps.numberingSystem = r.nu; - - // Compute formatting options. - // Step 14. - var dataLocale = r.dataLocale; - - // Steps 15-17. - var tz = lazyDateTimeFormatData.timeZone; - if (tz === undefined) { - // Step 16. - tz = DefaultTimeZone(); - } - internalProps.timeZone = tz; - - // Step 18. - var formatOpt = lazyDateTimeFormatData.formatOpt; - - // Steps 27-28, more or less - see comment after this function. - var pattern = toBestICUPattern(dataLocale, formatOpt); - - // Step 29. - internalProps.pattern = pattern; - - // Step 30. - internalProps.boundFormat = undefined; - - // The caller is responsible for associating |internalProps| with the right - // object using |setInternalProperties|. - return internalProps; -} - - -/** - * Returns an object containing the DateTimeFormat internal properties of |obj|, - * or throws a TypeError if |obj| isn't DateTimeFormat-initialized. - */ -function getDateTimeFormatInternals(obj, methodName) { - var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName); - assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals"); - - // If internal properties have already been computed, use them. - var internalProps = maybeInternalProperties(internals); - if (internalProps) - return internalProps; - - // Otherwise it's time to fully create them. - internalProps = resolveDateTimeFormatInternals(internals.lazyData); - setInternalProperties(internals, internalProps); - return internalProps; -} - -/** - * Components of date and time formats and their values. - * - * Spec: ECMAScript Internationalization API Specification, 12.1.1. - */ -var dateTimeComponentValues = { - 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"] -}; - - -var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues); - - -/** - * Initializes an object as a DateTimeFormat. - * - * This method is complicated a moderate bit by its implementing initialization - * as a *lazy* concept. Everything that must happen now, does -- but we defer - * all the work we can until the object is actually used as a DateTimeFormat. - * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted - * here occur there. - * - * Spec: ECMAScript Internationalization API Specification, 12.1.1. - */ -function InitializeDateTimeFormat(dateTimeFormat, locales, options) { - assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat"); - - // Step 1. - if (isInitializedIntlObject(dateTimeFormat)) - ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); - - // Step 2. - var internals = initializeIntlObject(dateTimeFormat); - - // Lazy DateTimeFormat data has the following structure: - // - // { - // requestedLocales: List of locales, - // - // localeOpt: // *first* opt computed in InitializeDateTimeFormat - // { - // localeMatcher: "lookup" / "best fit", - // } - // - // timeZone: IANA time zone name, - // - // formatOpt: // *second* opt computed in InitializeDateTimeFormat - // { - // // all the properties/values listed in Table 3 - // // (weekday, era, year, month, day, &c.) - // - // hour12: true / false // optional - // } - // - // formatMatcher: "basic" / "best fit", - // } - // - // Note that lazy data is only installed as a final step of initialization, - // so every DateTimeFormat lazy data object has *all* these properties, - // never a subset of them. - var lazyDateTimeFormatData = std_Object_create(null); - - // Step 3. - var requestedLocales = CanonicalizeLocaleList(locales); - lazyDateTimeFormatData.requestedLocales = requestedLocales; - - // Step 4. - options = ToDateTimeOptions(options, "any", "date"); - - // Compute options that impact interpretation of locale. - // Step 5. - var localeOpt = new Record(); - lazyDateTimeFormatData.localeOpt = localeOpt; - - // Steps 6-7. - var localeMatcher = - GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], - "best fit"); - localeOpt.localeMatcher = localeMatcher; - - // Steps 15-17. - var tz = options.timeZone; - if (tz !== undefined) { - // Step 15.a. - tz = ToString(tz); - - // Step 15.b. - var timeZone = intl_IsValidTimeZoneName(tz); - if (timeZone === null) - ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz); - - // Step 15.c. - tz = CanonicalizeTimeZoneName(timeZone); - } - lazyDateTimeFormatData.timeZone = tz; - - // Step 18. - var formatOpt = new Record(); - lazyDateTimeFormatData.formatOpt = formatOpt; - - // Step 19. - var i, prop; - for (i = 0; i < dateTimeComponents.length; i++) { - prop = dateTimeComponents[i]; - var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined); - formatOpt[prop] = value; - } - - // Steps 20-21 provided by ICU - see comment after this function. - - // Step 22. - // - // For some reason (ICU not exposing enough interface?) we drop the - // requested format matcher on the floor after this. In any case, even if - // doing so is justified, we have to do this work here in case it triggers - // getters or similar. (bug 852837) - var formatMatcher = - GetOption(options, "formatMatcher", "string", ["basic", "best fit"], - "best fit"); - - // Steps 23-25 provided by ICU, more or less - see comment after this function. - - // Step 26. - var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined); - - // Pass hr12 on to ICU. - if (hr12 !== undefined) - formatOpt.hour12 = hr12; - - // Step 31. - // - // We've done everything that must be done now: mark the lazy data as fully - // computed and install it. - setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData); -} - - -// Intl.DateTimeFormat and ICU skeletons and patterns -// ================================================== -// -// Different locales have different ways to display dates using the same -// basic components. For example, en-US might use "Sept. 24, 2012" while -// fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to -// permit production of a format for the locale that best matches the -// set of date-time components and their desired representation as specified -// by the API client. -// -// ICU supports specification of date and time formats in three ways: -// -// 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT. -// The date-time components included in each style and their representation -// are defined by ICU using CLDR locale data (CLDR is the Unicode -// Consortium's Common Locale Data Repository). -// -// 2) A skeleton is a string specifying which date-time components to include, -// and which representations to use for them. For example, "yyyyMMMMdd" -// specifies a year with at least four digits, a full month name, and a -// two-digit day. It does not specify in which order the components appear, -// how they are separated, the localized strings for textual components -// (such as weekday or month), whether the month is in format or -// stand-alone form¹, or the numbering system used for numeric components. -// All that information is filled in by ICU using CLDR locale data. -// ¹ The format form is the one used in formatted strings that include a -// day; the stand-alone form is used when not including days, e.g., in -// calendar headers. The two forms differ at least in some Slavic languages, -// e.g. Russian: "22 марта 2013 г." vs. "Март 2013". -// -// 3) A pattern is a string specifying which date-time components to include, -// in which order, with which separators, in which grammatical case. For -// example, "EEEE, d MMMM y" specifies the full localized weekday name, -// followed by comma and space, followed by the day, followed by space, -// followed by the full month name in format form, followed by space, -// followed by the full year. It -// still does not specify localized strings for textual components and the -// numbering system - these are determined by ICU using CLDR locale data or -// possibly API parameters. -// -// All actual formatting in ICU is done with patterns; styles and skeletons -// have to be mapped to patterns before processing. -// -// The options of DateTimeFormat most closely correspond to ICU skeletons. This -// implementation therefore, in the toBestICUPattern function, converts -// DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to -// actual ICU patterns. The pattern may not directly correspond to what the -// skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained -// by the available locale data for the locale. The resulting ICU pattern is -// kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU -// in the format method. -// -// An ICU pattern represents the information of the following DateTimeFormat -// internal properties described in the specification, which therefore don't -// exist separately in the implementation: -// - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]], -// [[second]], [[timeZoneName]] -// - [[hour12]] -// - [[hourNo0]] -// When needed for the resolvedOptions method, the resolveICUPattern function -// maps the instance's ICU pattern back to the specified properties of the -// object returned by resolvedOptions. -// -// ICU date-time skeletons and patterns aren't fully documented in the ICU -// documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best -// documentation at this point is in UTR 35: -// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns - - -/** - * Returns an ICU pattern string for the given locale and representing the - * specified options as closely as possible given available locale data. - */ -function toBestICUPattern(locale, options) { - // Create an ICU skeleton representing the specified options. See - // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table - var skeleton = ""; - switch (options.weekday) { - case "narrow": - skeleton += "EEEEE"; - break; - case "short": - skeleton += "E"; - break; - case "long": - skeleton += "EEEE"; - } - switch (options.era) { - case "narrow": - skeleton += "GGGGG"; - break; - case "short": - skeleton += "G"; - break; - case "long": - skeleton += "GGGG"; - break; - } - switch (options.year) { - case "2-digit": - skeleton += "yy"; - break; - case "numeric": - skeleton += "y"; - break; - } - switch (options.month) { - case "2-digit": - skeleton += "MM"; - break; - case "numeric": - skeleton += "M"; - break; - case "narrow": - skeleton += "MMMMM"; - break; - case "short": - skeleton += "MMM"; - break; - case "long": - skeleton += "MMMM"; - break; - } - switch (options.day) { - case "2-digit": - skeleton += "dd"; - break; - case "numeric": - skeleton += "d"; - break; - } - var hourSkeletonChar = "j"; - if (options.hour12 !== undefined) { - if (options.hour12) - hourSkeletonChar = "h"; - else - hourSkeletonChar = "H"; - } - switch (options.hour) { - case "2-digit": - skeleton += hourSkeletonChar + hourSkeletonChar; - break; - case "numeric": - skeleton += hourSkeletonChar; - break; - } - switch (options.minute) { - case "2-digit": - skeleton += "mm"; - break; - case "numeric": - skeleton += "m"; - break; - } - switch (options.second) { - case "2-digit": - skeleton += "ss"; - break; - case "numeric": - skeleton += "s"; - break; - } - switch (options.timeZoneName) { - case "short": - skeleton += "z"; - break; - case "long": - skeleton += "zzzz"; - break; - } - - // Let ICU convert the ICU skeleton to an ICU pattern for the given locale. - return intl_patternForSkeleton(locale, skeleton); -} - - -/** - * Returns a new options object that includes the provided options (if any) - * and fills in default components if required components are not defined. - * Required can be "date", "time", or "any". - * Defaults can be "date", "time", or "all". - * - * Spec: ECMAScript Internationalization API Specification, 12.1.1. - */ -function ToDateTimeOptions(options, required, defaults) { - assert(typeof required === "string", "ToDateTimeOptions"); - assert(typeof defaults === "string", "ToDateTimeOptions"); - - // Steps 1-3. - if (options === undefined) - options = null; - else - options = ToObject(options); - options = std_Object_create(options); - - // Step 4. - var needDefaults = true; - - // Step 5. - if ((required === "date" || required === "any") && - (options.weekday !== undefined || options.year !== undefined || - options.month !== undefined || options.day !== undefined)) - { - needDefaults = false; - } - - // Step 6. - if ((required === "time" || required === "any") && - (options.hour !== undefined || options.minute !== undefined || - options.second !== undefined)) - { - needDefaults = false; - } - - // Step 7. - if (needDefaults && (defaults === "date" || defaults === "all")) { - // The specification says to call [[DefineOwnProperty]] with false for - // the Throw parameter, while Object.defineProperty uses true. For the - // calls here, the difference doesn't matter because we're adding - // properties to a new object. - _DefineDataProperty(options, "year", "numeric"); - _DefineDataProperty(options, "month", "numeric"); - _DefineDataProperty(options, "day", "numeric"); - } - - // Step 8. - if (needDefaults && (defaults === "time" || defaults === "all")) { - // See comment for step 7. - _DefineDataProperty(options, "hour", "numeric"); - _DefineDataProperty(options, "minute", "numeric"); - _DefineDataProperty(options, "second", "numeric"); - } - - // Step 9. - return options; -} - - -/** - * Compares the date and time components requested by options with the available - * date and time formats in formats, and selects the best match according - * to a specified basic matching algorithm. - * - * Spec: ECMAScript Internationalization API Specification, 12.1.1. - */ -function BasicFormatMatcher(options, formats) { - // Steps 1-6. - var removalPenalty = 120, - additionPenalty = 20, - longLessPenalty = 8, - longMorePenalty = 6, - shortLessPenalty = 6, - shortMorePenalty = 3; - - // Table 3. - var properties = ["weekday", "era", "year", "month", "day", - "hour", "minute", "second", "timeZoneName"]; - - // Step 11.c.vi.1. - var values = ["2-digit", "numeric", "narrow", "short", "long"]; - - // Steps 7-8. - var bestScore = -Infinity; - var bestFormat; - - // Steps 9-11. - var i = 0; - var len = formats.length; - while (i < len) { - // Steps 11.a-b. - var format = formats[i]; - var score = 0; - - // Step 11.c. - var formatProp; - for (var j = 0; j < properties.length; j++) { - var property = properties[j]; - - // Step 11.c.i. - var optionsProp = options[property]; - // Step missing from spec. - // https://bugs.ecmascript.org/show_bug.cgi?id=1254 - formatProp = undefined; - - // Steps 11.c.ii-iii. - if (callFunction(std_Object_hasOwnProperty, format, property)) - formatProp = format[property]; - - if (optionsProp === undefined && formatProp !== undefined) { - // Step 11.c.iv. - score -= additionPenalty; - } else if (optionsProp !== undefined && formatProp === undefined) { - // Step 11.c.v. - score -= removalPenalty; - } else { - // Step 11.c.vi. - var optionsPropIndex = callFunction(ArrayIndexOf, values, optionsProp); - var formatPropIndex = callFunction(ArrayIndexOf, values, formatProp); - var delta = std_Math_max(std_Math_min(formatPropIndex - optionsPropIndex, 2), -2); - if (delta === 2) - score -= longMorePenalty; - else if (delta === 1) - score -= shortMorePenalty; - else if (delta === -1) - score -= shortLessPenalty; - else if (delta === -2) - score -= longLessPenalty; - } - } - - // Step 11.d. - if (score > bestScore) { - bestScore = score; - bestFormat = format; - } - - // Step 11.e. - i++; - } - - // Step 12. - return bestFormat; -} - - -/** - * Compares the date and time components requested by options with the available - * date and time formats in formats, and selects the best match according - * to an unspecified best-fit matching algorithm. - * - * Spec: ECMAScript Internationalization API Specification, 12.1.1. - */ -function BestFitFormatMatcher(options, formats) { - // this implementation doesn't have anything better - return BasicFormatMatcher(options, formats); -} - - -/** - * Returns the subset of the given locale list for which this locale list has a - * matching (possibly fallback) locale. Locales appear in the same order in the - * returned list as in the input list. - * - * Spec: ECMAScript Internationalization API Specification, 12.2.2. - */ -function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) { - var options = arguments.length > 1 ? arguments[1] : undefined; - - var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales, - dateTimeFormatInternalProperties); - var requestedLocales = CanonicalizeLocaleList(locales); - return SupportedLocales(availableLocales, requestedLocales, options); -} - - -/** - * DateTimeFormat internal properties. - * - * Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3. - */ -var dateTimeFormatInternalProperties = { - localeData: dateTimeFormatLocaleData, - _availableLocales: null, - availableLocales: function() - { - var locales = this._availableLocales; - if (locales) - return locales; - - locales = intl_DateTimeFormat_availableLocales(); - addSpecialMissingLanguageTags(locales); - return (this._availableLocales = locales); - }, - relevantExtensionKeys: ["ca", "nu"] -}; - - -function dateTimeFormatLocaleData(locale) { - return { - ca: intl_availableCalendars(locale), - nu: getNumberingSystems(locale) - }; -} - - -/** - * Function to be bound and returned by Intl.DateTimeFormat.prototype.format. - * - * Spec: ECMAScript Internationalization API Specification, 12.3.2. - */ -function dateTimeFormatFormatToBind() { - // Steps 1.a.i-ii - var date = arguments.length > 0 ? arguments[0] : undefined; - var x = (date === undefined) ? std_Date_now() : ToNumber(date); - - // Step 1.a.iii. - return intl_FormatDateTime(this, x, false); -} - -/** - * Returns a function bound to this DateTimeFormat that returns a String value - * representing the result of calling ToNumber(date) according to the - * effective locale and the formatting options of this DateTimeFormat. - * - * Spec: ECMAScript Internationalization API Specification, 12.3.2. - */ -function Intl_DateTimeFormat_format_get() { - // Check "this DateTimeFormat object" per introduction of section 12.3. - var internals = getDateTimeFormatInternals(this, "format"); - - // Step 1. - if (internals.boundFormat === undefined) { - // Step 1.a. - var F = dateTimeFormatFormatToBind; - - // Step 1.b-d. - var bf = callFunction(FunctionBind, F, this); - internals.boundFormat = bf; - } - - // Step 2. - return internals.boundFormat; -} - - -function Intl_DateTimeFormat_formatToParts() { - // Check "this DateTimeFormat object" per introduction of section 12.3. - getDateTimeFormatInternals(this, "formatToParts"); - - // Steps 1.a.i-ii - var date = arguments.length > 0 ? arguments[0] : undefined; - var x = (date === undefined) ? std_Date_now() : ToNumber(date); - - // Step 1.a.iii. - return intl_FormatDateTime(this, x, true); -} - - -/** - * Returns the resolved options for a DateTimeFormat object. - * - * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4. - */ -function Intl_DateTimeFormat_resolvedOptions() { - // Check "this DateTimeFormat object" per introduction of section 12.3. - var internals = getDateTimeFormatInternals(this, "resolvedOptions"); - - var result = { - locale: internals.locale, - calendar: internals.calendar, - numberingSystem: internals.numberingSystem, - timeZone: internals.timeZone - }; - resolveICUPattern(internals.pattern, result); - return result; -} - - -// Table mapping ICU pattern characters back to the corresponding date-time -// components of DateTimeFormat. See -// http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table -var icuPatternCharToComponent = { - E: "weekday", - G: "era", - y: "year", - M: "month", - L: "month", - d: "day", - h: "hour", - H: "hour", - k: "hour", - K: "hour", - m: "minute", - s: "second", - z: "timeZoneName", - v: "timeZoneName", - V: "timeZoneName" -}; - - -/** - * Maps an ICU pattern string to a corresponding set of date-time components - * and their values, and adds properties for these components to the result - * object, which will be returned by the resolvedOptions method. For the - * interpretation of ICU pattern characters, see - * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table - */ -function resolveICUPattern(pattern, result) { - assert(IsObject(result), "resolveICUPattern"); - var i = 0; - while (i < pattern.length) { - var c = pattern[i++]; - if (c === "'") { - while (i < pattern.length && pattern[i] !== "'") - i++; - i++; - } else { - var count = 1; - while (i < pattern.length && pattern[i] === c) { - i++; - count++; - } - var value; - switch (c) { - // "text" cases - case "G": - case "E": - case "z": - case "v": - case "V": - if (count <= 3) - value = "short"; - else if (count === 4) - value = "long"; - else - value = "narrow"; - break; - // "number" cases - case "y": - case "d": - case "h": - case "H": - case "m": - case "s": - case "k": - case "K": - if (count === 2) - value = "2-digit"; - else - value = "numeric"; - break; - // "text & number" cases - case "M": - case "L": - if (count === 1) - value = "numeric"; - else if (count === 2) - value = "2-digit"; - else if (count === 3) - value = "short"; - else if (count === 4) - value = "long"; - else - value = "narrow"; - break; - default: - // skip other pattern characters and literal text - } - if (callFunction(std_Object_hasOwnProperty, icuPatternCharToComponent, c)) - _DefineDataProperty(result, icuPatternCharToComponent[c], value); - if (c === "h" || c === "K") - _DefineDataProperty(result, "hour12", true); - else if (c === "H" || c === "k") - _DefineDataProperty(result, "hour12", false); - } - } -} - diff --git a/js/src/builtin/intl/DateTimeFormat.js b/js/src/builtin/intl/DateTimeFormat.js new file mode 100644 index 0000000000..9c5c907ce3 --- /dev/null +++ b/js/src/builtin/intl/DateTimeFormat.js @@ -0,0 +1,813 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/********** Intl.DateTimeFormat **********/ + + +/** + * Compute an internal properties object from |lazyDateTimeFormatData|. + */ +function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { + assert(IsObject(lazyDateTimeFormatData), "lazy data not an object?"); + + // Lazy DateTimeFormat data has the following structure: + // + // { + // requestedLocales: List of locales, + // + // localeOpt: // *first* opt computed in InitializeDateTimeFormat + // { + // localeMatcher: "lookup" / "best fit", + // + // hour12: true / false, // optional + // } + // + // timeZone: IANA time zone name, + // + // formatOpt: // *second* opt computed in InitializeDateTimeFormat + // { + // // all the properties/values listed in Table 3 + // // (weekday, era, year, month, day, &c.) + // } + // + // formatMatcher: "basic" / "best fit", + // } + // + // Note that lazy data is only installed as a final step of initialization, + // so every DateTimeFormat lazy data object has *all* these properties, + // never a subset of them. + + var internalProps = std_Object_create(null); + + // Compute effective locale. + // Step 8. + var DateTimeFormat = dateTimeFormatInternalProperties; + + // Step 9. + var localeData = DateTimeFormat.localeData; + + // Step 10. + var r = ResolveLocale(callFunction(DateTimeFormat.availableLocales, DateTimeFormat), + lazyDateTimeFormatData.requestedLocales, + lazyDateTimeFormatData.localeOpt, + DateTimeFormat.relevantExtensionKeys, + localeData); + + // Steps 11-13. + internalProps.locale = r.locale; + internalProps.calendar = r.ca; + internalProps.numberingSystem = r.nu; + + // Compute formatting options. + // Step 14. + var dataLocale = r.dataLocale; + + // Steps 15-17. + var tz = lazyDateTimeFormatData.timeZone; + if (tz === undefined) { + // Step 16. + tz = DefaultTimeZone(); + } + internalProps.timeZone = tz; + + // Step 18. + var formatOpt = lazyDateTimeFormatData.formatOpt; + + // Steps 27-28, more or less - see comment after this function. + var pattern = toBestICUPattern(dataLocale, formatOpt); + + // Step 29. + internalProps.pattern = pattern; + + // Step 30. + internalProps.boundFormat = undefined; + + // The caller is responsible for associating |internalProps| with the right + // object using |setInternalProperties|. + return internalProps; +} + + +/** + * Returns an object containing the DateTimeFormat internal properties of |obj|, + * or throws a TypeError if |obj| isn't DateTimeFormat-initialized. + */ +function getDateTimeFormatInternals(obj, methodName) { + var internals = getIntlObjectInternals(obj, "DateTimeFormat", methodName); + assert(internals.type === "DateTimeFormat", "bad type escaped getIntlObjectInternals"); + + // If internal properties have already been computed, use them. + var internalProps = maybeInternalProperties(internals); + if (internalProps) + return internalProps; + + // Otherwise it's time to fully create them. + internalProps = resolveDateTimeFormatInternals(internals.lazyData); + setInternalProperties(internals, internalProps); + return internalProps; +} + +/** + * Components of date and time formats and their values. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +var dateTimeComponentValues = { + 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"] +}; + + +var dateTimeComponents = std_Object_getOwnPropertyNames(dateTimeComponentValues); + + +/** + * Initializes an object as a DateTimeFormat. + * + * This method is complicated a moderate bit by its implementing initialization + * as a *lazy* concept. Everything that must happen now, does -- but we defer + * all the work we can until the object is actually used as a DateTimeFormat. + * This later work occurs in |resolveDateTimeFormatInternals|; steps not noted + * here occur there. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function InitializeDateTimeFormat(dateTimeFormat, locales, options) { + assert(IsObject(dateTimeFormat), "InitializeDateTimeFormat"); + + // Step 1. + if (isInitializedIntlObject(dateTimeFormat)) + ThrowTypeError(JSMSG_INTL_OBJECT_REINITED); + + // Step 2. + var internals = initializeIntlObject(dateTimeFormat); + + // Lazy DateTimeFormat data has the following structure: + // + // { + // requestedLocales: List of locales, + // + // localeOpt: // *first* opt computed in InitializeDateTimeFormat + // { + // localeMatcher: "lookup" / "best fit", + // } + // + // timeZone: IANA time zone name, + // + // formatOpt: // *second* opt computed in InitializeDateTimeFormat + // { + // // all the properties/values listed in Table 3 + // // (weekday, era, year, month, day, &c.) + // + // hour12: true / false // optional + // } + // + // formatMatcher: "basic" / "best fit", + // } + // + // Note that lazy data is only installed as a final step of initialization, + // so every DateTimeFormat lazy data object has *all* these properties, + // never a subset of them. + var lazyDateTimeFormatData = std_Object_create(null); + + // Step 3. + var requestedLocales = CanonicalizeLocaleList(locales); + lazyDateTimeFormatData.requestedLocales = requestedLocales; + + // Step 4. + options = ToDateTimeOptions(options, "any", "date"); + + // Compute options that impact interpretation of locale. + // Step 5. + var localeOpt = new Record(); + lazyDateTimeFormatData.localeOpt = localeOpt; + + // Steps 6-7. + var localeMatcher = + GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], + "best fit"); + localeOpt.localeMatcher = localeMatcher; + + // Steps 15-17. + var tz = options.timeZone; + if (tz !== undefined) { + // Step 15.a. + tz = ToString(tz); + + // Step 15.b. + var timeZone = intl_IsValidTimeZoneName(tz); + if (timeZone === null) + ThrowRangeError(JSMSG_INVALID_TIME_ZONE, tz); + + // Step 15.c. + tz = CanonicalizeTimeZoneName(timeZone); + } + lazyDateTimeFormatData.timeZone = tz; + + // Step 18. + var formatOpt = new Record(); + lazyDateTimeFormatData.formatOpt = formatOpt; + + // Step 19. + var i, prop; + for (i = 0; i < dateTimeComponents.length; i++) { + prop = dateTimeComponents[i]; + var value = GetOption(options, prop, "string", dateTimeComponentValues[prop], undefined); + formatOpt[prop] = value; + } + + // Steps 20-21 provided by ICU - see comment after this function. + + // Step 22. + // + // For some reason (ICU not exposing enough interface?) we drop the + // requested format matcher on the floor after this. In any case, even if + // doing so is justified, we have to do this work here in case it triggers + // getters or similar. (bug 852837) + var formatMatcher = + GetOption(options, "formatMatcher", "string", ["basic", "best fit"], + "best fit"); + + // Steps 23-25 provided by ICU, more or less - see comment after this function. + + // Step 26. + var hr12 = GetOption(options, "hour12", "boolean", undefined, undefined); + + // Pass hr12 on to ICU. + if (hr12 !== undefined) + formatOpt.hour12 = hr12; + + // Step 31. + // + // We've done everything that must be done now: mark the lazy data as fully + // computed and install it. + setLazyData(internals, "DateTimeFormat", lazyDateTimeFormatData); +} + + +// Intl.DateTimeFormat and ICU skeletons and patterns +// ================================================== +// +// Different locales have different ways to display dates using the same +// basic components. For example, en-US might use "Sept. 24, 2012" while +// fr-FR might use "24 Sept. 2012". The intent of Intl.DateTimeFormat is to +// permit production of a format for the locale that best matches the +// set of date-time components and their desired representation as specified +// by the API client. +// +// ICU supports specification of date and time formats in three ways: +// +// 1) A style is just one of the identifiers FULL, LONG, MEDIUM, or SHORT. +// The date-time components included in each style and their representation +// are defined by ICU using CLDR locale data (CLDR is the Unicode +// Consortium's Common Locale Data Repository). +// +// 2) A skeleton is a string specifying which date-time components to include, +// and which representations to use for them. For example, "yyyyMMMMdd" +// specifies a year with at least four digits, a full month name, and a +// two-digit day. It does not specify in which order the components appear, +// how they are separated, the localized strings for textual components +// (such as weekday or month), whether the month is in format or +// stand-alone form¹, or the numbering system used for numeric components. +// All that information is filled in by ICU using CLDR locale data. +// ¹ The format form is the one used in formatted strings that include a +// day; the stand-alone form is used when not including days, e.g., in +// calendar headers. The two forms differ at least in some Slavic languages, +// e.g. Russian: "22 марта 2013 г." vs. "Март 2013". +// +// 3) A pattern is a string specifying which date-time components to include, +// in which order, with which separators, in which grammatical case. For +// example, "EEEE, d MMMM y" specifies the full localized weekday name, +// followed by comma and space, followed by the day, followed by space, +// followed by the full month name in format form, followed by space, +// followed by the full year. It +// still does not specify localized strings for textual components and the +// numbering system - these are determined by ICU using CLDR locale data or +// possibly API parameters. +// +// All actual formatting in ICU is done with patterns; styles and skeletons +// have to be mapped to patterns before processing. +// +// The options of DateTimeFormat most closely correspond to ICU skeletons. This +// implementation therefore, in the toBestICUPattern function, converts +// DateTimeFormat options to ICU skeletons, and then lets ICU map skeletons to +// actual ICU patterns. The pattern may not directly correspond to what the +// skeleton requests, as the mapper (UDateTimePatternGenerator) is constrained +// by the available locale data for the locale. The resulting ICU pattern is +// kept as the DateTimeFormat's [[pattern]] internal property and passed to ICU +// in the format method. +// +// An ICU pattern represents the information of the following DateTimeFormat +// internal properties described in the specification, which therefore don't +// exist separately in the implementation: +// - [[weekday]], [[era]], [[year]], [[month]], [[day]], [[hour]], [[minute]], +// [[second]], [[timeZoneName]] +// - [[hour12]] +// - [[hourNo0]] +// When needed for the resolvedOptions method, the resolveICUPattern function +// maps the instance's ICU pattern back to the specified properties of the +// object returned by resolvedOptions. +// +// ICU date-time skeletons and patterns aren't fully documented in the ICU +// documentation (see http://bugs.icu-project.org/trac/ticket/9627). The best +// documentation at this point is in UTR 35: +// http://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns + + +/** + * Returns an ICU pattern string for the given locale and representing the + * specified options as closely as possible given available locale data. + */ +function toBestICUPattern(locale, options) { + // Create an ICU skeleton representing the specified options. See + // http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table + var skeleton = ""; + switch (options.weekday) { + case "narrow": + skeleton += "EEEEE"; + break; + case "short": + skeleton += "E"; + break; + case "long": + skeleton += "EEEE"; + } + switch (options.era) { + case "narrow": + skeleton += "GGGGG"; + break; + case "short": + skeleton += "G"; + break; + case "long": + skeleton += "GGGG"; + break; + } + switch (options.year) { + case "2-digit": + skeleton += "yy"; + break; + case "numeric": + skeleton += "y"; + break; + } + switch (options.month) { + case "2-digit": + skeleton += "MM"; + break; + case "numeric": + skeleton += "M"; + break; + case "narrow": + skeleton += "MMMMM"; + break; + case "short": + skeleton += "MMM"; + break; + case "long": + skeleton += "MMMM"; + break; + } + switch (options.day) { + case "2-digit": + skeleton += "dd"; + break; + case "numeric": + skeleton += "d"; + break; + } + var hourSkeletonChar = "j"; + if (options.hour12 !== undefined) { + if (options.hour12) + hourSkeletonChar = "h"; + else + hourSkeletonChar = "H"; + } + switch (options.hour) { + case "2-digit": + skeleton += hourSkeletonChar + hourSkeletonChar; + break; + case "numeric": + skeleton += hourSkeletonChar; + break; + } + switch (options.minute) { + case "2-digit": + skeleton += "mm"; + break; + case "numeric": + skeleton += "m"; + break; + } + switch (options.second) { + case "2-digit": + skeleton += "ss"; + break; + case "numeric": + skeleton += "s"; + break; + } + switch (options.timeZoneName) { + case "short": + skeleton += "z"; + break; + case "long": + skeleton += "zzzz"; + break; + } + + // Let ICU convert the ICU skeleton to an ICU pattern for the given locale. + return intl_patternForSkeleton(locale, skeleton); +} + + +/** + * Returns a new options object that includes the provided options (if any) + * and fills in default components if required components are not defined. + * Required can be "date", "time", or "any". + * Defaults can be "date", "time", or "all". + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function ToDateTimeOptions(options, required, defaults) { + assert(typeof required === "string", "ToDateTimeOptions"); + assert(typeof defaults === "string", "ToDateTimeOptions"); + + // Steps 1-3. + if (options === undefined) + options = null; + else + options = ToObject(options); + options = std_Object_create(options); + + // Step 4. + var needDefaults = true; + + // Step 5. + if ((required === "date" || required === "any") && + (options.weekday !== undefined || options.year !== undefined || + options.month !== undefined || options.day !== undefined)) + { + needDefaults = false; + } + + // Step 6. + if ((required === "time" || required === "any") && + (options.hour !== undefined || options.minute !== undefined || + options.second !== undefined)) + { + needDefaults = false; + } + + // Step 7. + if (needDefaults && (defaults === "date" || defaults === "all")) { + // The specification says to call [[DefineOwnProperty]] with false for + // the Throw parameter, while Object.defineProperty uses true. For the + // calls here, the difference doesn't matter because we're adding + // properties to a new object. + _DefineDataProperty(options, "year", "numeric"); + _DefineDataProperty(options, "month", "numeric"); + _DefineDataProperty(options, "day", "numeric"); + } + + // Step 8. + if (needDefaults && (defaults === "time" || defaults === "all")) { + // See comment for step 7. + _DefineDataProperty(options, "hour", "numeric"); + _DefineDataProperty(options, "minute", "numeric"); + _DefineDataProperty(options, "second", "numeric"); + } + + // Step 9. + return options; +} + + +/** + * Compares the date and time components requested by options with the available + * date and time formats in formats, and selects the best match according + * to a specified basic matching algorithm. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function BasicFormatMatcher(options, formats) { + // Steps 1-6. + var removalPenalty = 120, + additionPenalty = 20, + longLessPenalty = 8, + longMorePenalty = 6, + shortLessPenalty = 6, + shortMorePenalty = 3; + + // Table 3. + var properties = ["weekday", "era", "year", "month", "day", + "hour", "minute", "second", "timeZoneName"]; + + // Step 11.c.vi.1. + var values = ["2-digit", "numeric", "narrow", "short", "long"]; + + // Steps 7-8. + var bestScore = -Infinity; + var bestFormat; + + // Steps 9-11. + var i = 0; + var len = formats.length; + while (i < len) { + // Steps 11.a-b. + var format = formats[i]; + var score = 0; + + // Step 11.c. + var formatProp; + for (var j = 0; j < properties.length; j++) { + var property = properties[j]; + + // Step 11.c.i. + var optionsProp = options[property]; + // Step missing from spec. + // https://bugs.ecmascript.org/show_bug.cgi?id=1254 + formatProp = undefined; + + // Steps 11.c.ii-iii. + if (callFunction(std_Object_hasOwnProperty, format, property)) + formatProp = format[property]; + + if (optionsProp === undefined && formatProp !== undefined) { + // Step 11.c.iv. + score -= additionPenalty; + } else if (optionsProp !== undefined && formatProp === undefined) { + // Step 11.c.v. + score -= removalPenalty; + } else { + // Step 11.c.vi. + var optionsPropIndex = callFunction(ArrayIndexOf, values, optionsProp); + var formatPropIndex = callFunction(ArrayIndexOf, values, formatProp); + var delta = std_Math_max(std_Math_min(formatPropIndex - optionsPropIndex, 2), -2); + if (delta === 2) + score -= longMorePenalty; + else if (delta === 1) + score -= shortMorePenalty; + else if (delta === -1) + score -= shortLessPenalty; + else if (delta === -2) + score -= longLessPenalty; + } + } + + // Step 11.d. + if (score > bestScore) { + bestScore = score; + bestFormat = format; + } + + // Step 11.e. + i++; + } + + // Step 12. + return bestFormat; +} + + +/** + * Compares the date and time components requested by options with the available + * date and time formats in formats, and selects the best match according + * to an unspecified best-fit matching algorithm. + * + * Spec: ECMAScript Internationalization API Specification, 12.1.1. + */ +function BestFitFormatMatcher(options, formats) { + // this implementation doesn't have anything better + return BasicFormatMatcher(options, formats); +} + + +/** + * Returns the subset of the given locale list for which this locale list has a + * matching (possibly fallback) locale. Locales appear in the same order in the + * returned list as in the input list. + * + * Spec: ECMAScript Internationalization API Specification, 12.2.2. + */ +function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) { + var options = arguments.length > 1 ? arguments[1] : undefined; + + var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales, + dateTimeFormatInternalProperties); + var requestedLocales = CanonicalizeLocaleList(locales); + return SupportedLocales(availableLocales, requestedLocales, options); +} + + +/** + * DateTimeFormat internal properties. + * + * Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3. + */ +var dateTimeFormatInternalProperties = { + localeData: dateTimeFormatLocaleData, + _availableLocales: null, + availableLocales: function() + { + var locales = this._availableLocales; + if (locales) + return locales; + + locales = intl_DateTimeFormat_availableLocales(); + addSpecialMissingLanguageTags(locales); + return (this._availableLocales = locales); + }, + relevantExtensionKeys: ["ca", "nu"] +}; + + +function dateTimeFormatLocaleData(locale) { + return { + ca: intl_availableCalendars(locale), + nu: getNumberingSystems(locale) + }; +} + + +/** + * Function to be bound and returned by Intl.DateTimeFormat.prototype.format. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.2. + */ +function dateTimeFormatFormatToBind() { + // Steps 1.a.i-ii + var date = arguments.length > 0 ? arguments[0] : undefined; + var x = (date === undefined) ? std_Date_now() : ToNumber(date); + + // Step 1.a.iii. + return intl_FormatDateTime(this, x, false); +} + +/** + * Returns a function bound to this DateTimeFormat that returns a String value + * representing the result of calling ToNumber(date) according to the + * effective locale and the formatting options of this DateTimeFormat. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.2. + */ +function Intl_DateTimeFormat_format_get() { + // Check "this DateTimeFormat object" per introduction of section 12.3. + var internals = getDateTimeFormatInternals(this, "format"); + + // Step 1. + if (internals.boundFormat === undefined) { + // Step 1.a. + var F = dateTimeFormatFormatToBind; + + // Step 1.b-d. + var bf = callFunction(FunctionBind, F, this); + internals.boundFormat = bf; + } + + // Step 2. + return internals.boundFormat; +} + + +function Intl_DateTimeFormat_formatToParts() { + // Check "this DateTimeFormat object" per introduction of section 12.3. + getDateTimeFormatInternals(this, "formatToParts"); + + // Steps 1.a.i-ii + var date = arguments.length > 0 ? arguments[0] : undefined; + var x = (date === undefined) ? std_Date_now() : ToNumber(date); + + // Step 1.a.iii. + return intl_FormatDateTime(this, x, true); +} + + +/** + * Returns the resolved options for a DateTimeFormat object. + * + * Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4. + */ +function Intl_DateTimeFormat_resolvedOptions() { + // Check "this DateTimeFormat object" per introduction of section 12.3. + var internals = getDateTimeFormatInternals(this, "resolvedOptions"); + + var result = { + locale: internals.locale, + calendar: internals.calendar, + numberingSystem: internals.numberingSystem, + timeZone: internals.timeZone + }; + resolveICUPattern(internals.pattern, result); + return result; +} + + +// Table mapping ICU pattern characters back to the corresponding date-time +// components of DateTimeFormat. See +// http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table +var icuPatternCharToComponent = { + E: "weekday", + G: "era", + y: "year", + M: "month", + L: "month", + d: "day", + h: "hour", + H: "hour", + k: "hour", + K: "hour", + m: "minute", + s: "second", + z: "timeZoneName", + v: "timeZoneName", + V: "timeZoneName" +}; + + +/** + * Maps an ICU pattern string to a corresponding set of date-time components + * and their values, and adds properties for these components to the result + * object, which will be returned by the resolvedOptions method. For the + * interpretation of ICU pattern characters, see + * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table + */ +function resolveICUPattern(pattern, result) { + assert(IsObject(result), "resolveICUPattern"); + var i = 0; + while (i < pattern.length) { + var c = pattern[i++]; + if (c === "'") { + while (i < pattern.length && pattern[i] !== "'") + i++; + i++; + } else { + var count = 1; + while (i < pattern.length && pattern[i] === c) { + i++; + count++; + } + var value; + switch (c) { + // "text" cases + case "G": + case "E": + case "z": + case "v": + case "V": + if (count <= 3) + value = "short"; + else if (count === 4) + value = "long"; + else + value = "narrow"; + break; + // "number" cases + case "y": + case "d": + case "h": + case "H": + case "m": + case "s": + case "k": + case "K": + if (count === 2) + value = "2-digit"; + else + value = "numeric"; + break; + // "text & number" cases + case "M": + case "L": + if (count === 1) + value = "numeric"; + else if (count === 2) + value = "2-digit"; + else if (count === 3) + value = "short"; + else if (count === 4) + value = "long"; + else + value = "narrow"; + break; + default: + // skip other pattern characters and literal text + } + if (callFunction(std_Object_hasOwnProperty, icuPatternCharToComponent, c)) + _DefineDataProperty(result, icuPatternCharToComponent[c], value); + if (c === "h" || c === "K") + _DefineDataProperty(result, "hour12", true); + else if (c === "H" || c === "k") + _DefineDataProperty(result, "hour12", false); + } + } +} + diff --git a/js/src/moz.build b/js/src/moz.build index 767a92042d..40407a1985 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -669,6 +669,7 @@ selfhosted.inputs = [ 'builtin/Function.js', 'builtin/Generator.js', 'builtin/Intl.js', + 'builtin/intl/DateTimeFormat.js', 'builtin/intl/IntlObject.js', 'builtin/intl/LangTagMappingsGenerated.js', 'builtin/intl/PluralRules.js',