From df34fda785dd5465d6bfcaba4284756d1c12c4bf Mon Sep 17 00:00:00 2001 From: roytam1 Date: Fri, 7 Jul 2023 15:23:39 +0800 Subject: [PATCH] import from UXP: Issue #2046 - Implement Intl.DateTimeFormat's date-/timeStyle and hourCycle options (4400677a) --- js/src/builtin/intl/DateTimeFormat.cpp | 335 ++++++++++++++++++++++--- js/src/builtin/intl/DateTimeFormat.h | 11 +- js/src/builtin/intl/DateTimeFormat.js | 271 +++++++++++--------- js/src/js.msg | 2 + js/src/vm/SelfHosting.cpp | 4 +- 5 files changed, 470 insertions(+), 153 deletions(-) diff --git a/js/src/builtin/intl/DateTimeFormat.cpp b/js/src/builtin/intl/DateTimeFormat.cpp index c5aef3f6c..1fa78463b 100644 --- a/js/src/builtin/intl/DateTimeFormat.cpp +++ b/js/src/builtin/intl/DateTimeFormat.cpp @@ -9,6 +9,7 @@ #include "mozilla/Assertions.h" #include "mozilla/Range.h" +#include "mozilla/Span.h" #include "jscntxt.h" #include "jsfriendapi.h" @@ -447,13 +448,139 @@ js::intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp) { return true; } +enum class HourCycle { + // 12 hour cycle, from 0 to 11. + H11, + + // 12 hour cycle, from 1 to 12. + H12, + + // 24 hour cycle, from 0 to 23. + H23, + + // 24 hour cycle, from 1 to 24. + H24 +}; + +static bool +IsHour12(HourCycle hc) +{ + return hc == HourCycle::H11 || hc == HourCycle::H12; +} + +static char16_t +HourSymbol(HourCycle hc) +{ + switch (hc) { + case HourCycle::H11: + return 'K'; + case HourCycle::H12: + return 'h'; + case HourCycle::H23: + return 'H'; + case HourCycle::H24: + return 'k'; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected hour cycle"); +} + +/** +* Parse a pattern according to the format specified in +* . +*/ +template +class PatternIterator { + CharT* iter_; + const CharT* const end_; + + public: + explicit PatternIterator(mozilla::Span pattern) + : iter_(pattern.data()), end_(pattern.data() + pattern.size()) {} + + CharT* next() { + MOZ_ASSERT(iter_ != nullptr); + + bool inQuote = false; + while (iter_ < end_) { + CharT* cur = iter_++; + if (*cur == '\'') { + inQuote = !inQuote; + } else if (!inQuote) { + return cur; + } + } + + iter_ = nullptr; + return nullptr; + } +}; + +/** +* Return the hour cycle for the given option string. +*/ +static HourCycle +HourCycleFromOption(JSLinearString* str) +{ + if (StringEqualsAscii(str, "h11")) { + return HourCycle::H11; + } + if (StringEqualsAscii(str, "h12")) { + return HourCycle::H12; + } + if (StringEqualsAscii(str, "h23")) { + return HourCycle::H23; + } + MOZ_ASSERT(StringEqualsAscii(str, "h24")); + return HourCycle::H24; +} + +/** +* Return the hour cycle used in the input pattern or Nothing if none was found. +*/ +static mozilla::Maybe +HourCycleFromPattern(mozilla::Span pattern) +{ + PatternIterator iter(pattern); + while (const auto* ptr = iter.next()) { + switch (*ptr) { + case 'K': + return mozilla::Some(HourCycle::H11); + case 'h': + return mozilla::Some(HourCycle::H12); + case 'H': + return mozilla::Some(HourCycle::H23); + case 'k': + return mozilla::Some(HourCycle::H24); + } + } + return mozilla::Nothing(); +} + +/** +* Replaces all hour pattern characters in |pattern| to use the matching hour +* representation for |hourCycle|. +*/ +static void +ReplaceHourSymbol(mozilla::Span pattern, HourCycle hc) +{ + char16_t replacement = HourSymbol(hc); + PatternIterator iter(pattern); + while (auto* ptr = iter.next()) { + char16_t ch = *ptr; + if (ch == 'K' || ch == 'h' || ch == 'H' || ch == 'k') { + *ptr = replacement; + } + } +} + bool js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args.length() == 3); MOZ_ASSERT(args[0].isString()); MOZ_ASSERT(args[1].isString()); + MOZ_ASSERT(args[2].isString() || args[2].isUndefined()); JSAutoByteString locale(cx, args[0].toString()); if (!locale) @@ -467,6 +594,16 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) if (!stableChars.initTwoByte(cx, skeletonFlat)) return false; + mozilla::Maybe hourCycle; + if (args[2].isString()) { + JSLinearString* hourCycleStr = args[2].toString()->ensureLinear(cx); + if (!hourCycleStr) { + return false; + } + + hourCycle.emplace(HourCycleFromOption(hourCycleStr)); + } + mozilla::Range skeletonChars = stableChars.twoByteRange(); uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.begin().get())); @@ -478,69 +615,175 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) } ScopedICUObject toClose(gen); - JSString* str = - CallICU(cx, [gen, &skeletonChars, skeletonLen](UChar* chars, uint32_t size, UErrorCode* status) { - return udatpg_getBestPattern(gen, skeletonChars.begin().get(), skeletonLen, - chars, size, status); + Vector pattern(cx); + + int32_t patternSize = CallICU( + cx, + pattern, + [gen, &skeletonChars](UChar* chars, uint32_t size, UErrorCode* status) { + return udatpg_getBestPattern(gen, skeletonChars.begin().get(), + skeletonChars.length(), chars, size, status); }); - if (!str) + if (patternSize < 0) { return false; + } + + // If the hourCycle option was set, adjust the resolved pattern to use the + // requested hour cycle representation. + if (hourCycle) { + ReplaceHourSymbol(pattern, hourCycle.value()); + } + + JSString* str = NewStringCopyN(cx, pattern.begin(), pattern.length()); + if (!str) { + return false; + } args.rval().setString(str); return true; } +/** + * Find a matching pattern using the requested hour-12 options. + * + * This function is needed to work around the following two issues. + * - https://unicode-org.atlassian.net/browse/ICU-21023 + * - https://unicode-org.atlassian.net/browse/CLDR-13425 + * + * We're currently using a relatively simple workaround, which doesn't give the + * most accurate results. For example: + * + * ``` + * var dtf = new Intl.DateTimeFormat("en", { + * timeZone: "UTC", + * dateStyle: "long", + * timeStyle: "long", + * hourCycle: "h12", + * }); + * print(dtf.format(new Date("2020-01-01T00:00Z"))); + * ``` + * + * Returns the pattern "MMMM d, y 'at' h:mm:ss a z", but when going through + * |udatpg_getSkeleton| and then |udatpg_getBestPattern| to find an equivalent + * pattern for "h23", we'll end up with the pattern "MMMM d, y, HH:mm:ss z", so + * the combinator element " 'at' " was lost in the process. + */ +template +static bool +FindPatternWithHourCycle(JSContext* cx, const char* locale, + Vector& pattern, bool hour12) +{ + UErrorCode status = U_ZERO_ERROR; + UDateTimePatternGenerator* gen = udatpg_open(IcuLocale(locale), &status); + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + ScopedICUObject toClose(gen); + + if (!gen) { + return false; + } + + Vector skeleton(cx); + + int32_t skeletonSize = CallICU( + cx, + skeleton, + [&pattern](UChar* chars, uint32_t size, UErrorCode* status) { + return udatpg_getSkeleton(nullptr, pattern.begin(), pattern.length(), + chars, size, status); + }); + if (skeletonSize < 0) { + return false; + } + + // Input skeletons don't differentiate between "K" and "h" resp. "k" and "H". + ReplaceHourSymbol(skeleton, hour12 ? HourCycle::H12 : HourCycle::H23); + + MOZ_ALWAYS_TRUE(pattern.resize(0)); + + int32_t patternSize = CallICU( + cx, + pattern, + [gen, &skeleton](UChar* chars, uint32_t size, UErrorCode* status) { + return udatpg_getBestPattern(gen, skeleton.begin(), skeleton.length(), + chars, size, status); + }); + if (patternSize < 0) { + return false; + } + + return true; +} + bool js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 4); + MOZ_ASSERT(args.length() == 6); MOZ_ASSERT(args[0].isString()); + MOZ_ASSERT(args[1].isString() || args[1].isUndefined()); + MOZ_ASSERT(args[2].isString() || args[2].isUndefined()); + MOZ_ASSERT(args[3].isString()); + MOZ_ASSERT(args[4].isBoolean() || args[4].isUndefined()); + MOZ_ASSERT(args[5].isString() || args[5].isUndefined()); JSAutoByteString locale(cx, args[0].toString()); if (!locale) return false; + auto toDateFormatStyle = [](JSLinearString* str) { + if (StringEqualsAscii(str, "full")) { + return UDAT_FULL; + } + if (StringEqualsAscii(str, "long")) { + return UDAT_LONG; + } + if (StringEqualsAscii(str, "medium")) { + return UDAT_MEDIUM; + } + MOZ_ASSERT(StringEqualsAscii(str, "short")); + return UDAT_SHORT; + }; + UDateFormatStyle dateStyle = UDAT_NONE; - UDateFormatStyle timeStyle = UDAT_NONE; if (args[1].isString()) { JSLinearString* dateStyleStr = args[1].toString()->ensureLinear(cx); if (!dateStyleStr) return false; - if (StringEqualsAscii(dateStyleStr, "full")) - dateStyle = UDAT_FULL; - else if (StringEqualsAscii(dateStyleStr, "long")) - dateStyle = UDAT_LONG; - else if (StringEqualsAscii(dateStyleStr, "medium")) - dateStyle = UDAT_MEDIUM; - else if (StringEqualsAscii(dateStyleStr, "short")) - dateStyle = UDAT_SHORT; - else - MOZ_ASSERT_UNREACHABLE("unexpected dateStyle"); + dateStyle = toDateFormatStyle(dateStyleStr); } + UDateFormatStyle timeStyle = UDAT_NONE; if (args[2].isString()) { JSLinearString* timeStyleStr = args[2].toString()->ensureLinear(cx); if (!timeStyleStr) return false; - if (StringEqualsAscii(timeStyleStr, "full")) - timeStyle = UDAT_FULL; - else if (StringEqualsAscii(timeStyleStr, "long")) - timeStyle = UDAT_LONG; - else if (StringEqualsAscii(timeStyleStr, "medium")) - timeStyle = UDAT_MEDIUM; - else if (StringEqualsAscii(timeStyleStr, "short")) - timeStyle = UDAT_SHORT; - else - MOZ_ASSERT_UNREACHABLE("unexpected timeStyle"); + timeStyle = toDateFormatStyle(timeStyleStr); } AutoStableStringChars timeZone(cx); if (!timeZone.initTwoByte(cx, args[3].toString())) return false; + mozilla::Maybe hour12; + if (args[4].isBoolean()) { + hour12.emplace(args[4].toBoolean()); + } + + mozilla::Maybe hourCycle; + if (args[5].isString()) { + JSLinearString* hourCycleStr = args[5].toString()->ensureLinear(cx); + if (!hourCycleStr) { + return false; + } + + hourCycle.emplace(HourCycleFromOption(hourCycleStr)); + } + mozilla::Range timeZoneChars = timeZone.twoByteRange(); UErrorCode status = U_ZERO_ERROR; @@ -553,9 +796,39 @@ js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp) } ScopedICUObject toClose(df); - JSString* str = CallICU(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) { - return udat_toPattern(df, false, chars, size, status); - }); + Vector pattern(cx); + + int32_t patternSize = CallICU( + cx, + pattern, + [df](UChar* chars, uint32_t size, UErrorCode* status) { + return udat_toPattern(df, false, chars, size, status); + }); + if (patternSize < 0) { + return false; + } + + // If a specific hour cycle was requested and this hour cycle doesn't match + // the hour cycle used in the resolved pattern, find an equivalent pattern + // with the correct hour cycle. + if (timeStyle != UDAT_NONE && (hour12 || hourCycle)) { + if (auto hcPattern = HourCycleFromPattern(pattern)) { + bool wantHour12 = hour12 ? hour12.value() : IsHour12(hourCycle.value()); + if (wantHour12 != IsHour12(hcPattern.value())) { + if (!FindPatternWithHourCycle(cx, locale.ptr(), pattern, wantHour12)) { + return false; + } + } + } + } + + // If the hourCycle option was set, adjust the resolved pattern to use the + // requested hour cycle representation. + if (hourCycle) { + ReplaceHourSymbol(pattern, hourCycle.value()); + } + + JSString* str = NewStringCopyN(cx, pattern.begin(), pattern.length()); if (!str) return false; args.rval().setString(str); diff --git a/js/src/builtin/intl/DateTimeFormat.h b/js/src/builtin/intl/DateTimeFormat.h index da53d65ea..5a223e871 100644 --- a/js/src/builtin/intl/DateTimeFormat.h +++ b/js/src/builtin/intl/DateTimeFormat.h @@ -119,7 +119,7 @@ intl_defaultTimeZoneOffset(JSContext* cx, unsigned argc, Value* vp); * best-fit date-time format pattern corresponding to skeleton for the * given locale. * - * Usage: pattern = intl_patternForSkeleton(locale, skeleton) + * Usage: pattern = intl_patternForSkeleton(locale, skeleton, hourCycle) */ extern MOZ_MUST_USE bool intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); @@ -128,7 +128,7 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); * Return a pattern in the date-time format pattern language of Unicode * Technical Standard 35, Unicode Locale Data Markup Language, for the * best-fit date-time style for the given locale. - * The function takes four arguments: + * The function takes six arguments: * * locale * BCP47 compliant locale string @@ -138,6 +138,10 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); * A string with values: full or long or medium or short, or `undefined` * timeZone * IANA time zone name + * hour12 + * A boolean to request hour12 representation, or `undefined` + * hourCycle + * A string with values: h11, h12, h23, or h24, or `undefined` * * Date and time style categories map to CLDR time/date standard * format patterns. @@ -148,7 +152,8 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp); * If `undefined` is passed to `dateStyle` or `timeStyle`, the respective * portions of the pattern will not be included in the result. * - * Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone) + * Usage: pattern = intl_patternForStyle(locale, dateStyle, timeStyle, timeZone, + * hour12, hourCycle) */ extern MOZ_MUST_USE bool intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp); diff --git a/js/src/builtin/intl/DateTimeFormat.js b/js/src/builtin/intl/DateTimeFormat.js index 9d1adc868..99c078fec 100644 --- a/js/src/builtin/intl/DateTimeFormat.js +++ b/js/src/builtin/intl/DateTimeFormat.js @@ -39,11 +39,6 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { // // formatMatcher: "basic" / "best fit", // - // mozExtensions: true / false, - // - // - // // If mozExtensions is true: - // // dateStyle: "full" / "long" / "medium" / "short" / undefined, // // timeStyle: "full" / "long" / "medium" / "short" / undefined, @@ -96,31 +91,25 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { // Steps 26-30, more or less - see comment after this function. var pattern; - if (lazyDateTimeFormatData.mozExtensions) { - if (lazyDateTimeFormatData.patternOption !== undefined) { - pattern = lazyDateTimeFormatData.patternOption; + if (lazyDateTimeFormatData.patternOption !== undefined) { + pattern = lazyDateTimeFormatData.patternOption; - internalProps.patternOption = lazyDateTimeFormatData.patternOption; - } else if (lazyDateTimeFormatData.dateStyle || lazyDateTimeFormatData.timeStyle) { - pattern = intl_patternForStyle(dataLocale, - lazyDateTimeFormatData.dateStyle, lazyDateTimeFormatData.timeStyle, - lazyDateTimeFormatData.timeZone); + internalProps.patternOption = lazyDateTimeFormatData.patternOption; + } else if (lazyDateTimeFormatData.dateStyle !== undefined || + lazyDateTimeFormatData.timeStyle !== undefined) { + pattern = intl_patternForStyle(dataLocale, + lazyDateTimeFormatData.dateStyle, + lazyDateTimeFormatData.timeStyle, + lazyDateTimeFormatData.timeZone, + formatOpt.hour12, + formatOpt.hourCycle); - internalProps.dateStyle = lazyDateTimeFormatData.dateStyle; - internalProps.timeStyle = lazyDateTimeFormatData.timeStyle; - } else { - pattern = toBestICUPattern(dataLocale, formatOpt); - } - internalProps.mozExtensions = true; + internalProps.dateStyle = lazyDateTimeFormatData.dateStyle; + internalProps.timeStyle = lazyDateTimeFormatData.timeStyle; } else { - pattern = toBestICUPattern(dataLocale, formatOpt); + pattern = toBestICUPattern(dataLocale, formatOpt); } - // If the hourCycle option was set, adjust the resolved pattern to use the - // requested hour cycle representation. - if (formatOpt.hourCycle !== undefined) - pattern = replaceHourRepresentation(pattern, formatOpt.hourCycle); - // Step 31. internalProps.pattern = pattern; @@ -130,47 +119,6 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) { } -/** - * Replaces all hour pattern characters in |pattern| to use the matching hour - * representation for |hourCycle|. - */ -function replaceHourRepresentation(pattern, hourCycle) { - var hour; - switch (hourCycle) { - case "h11": - hour = "K"; - break; - case "h12": - hour = "h"; - break; - case "h23": - hour = "H"; - break; - case "h24": - hour = "k"; - break; - } - assert(hour !== undefined, "Unexpected hourCycle requested: " + hourCycle); - - // Parse the pattern according to the format specified in - // https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns - // and replace all hour symbols with |hour|. - var resultPattern = ""; - var inQuote = false; - for (var i = 0; i < pattern.length; i++) { - var ch = pattern[i]; - if (ch === "'") { - inQuote = !inQuote; - } else if (!inQuote && (ch === "h" || ch === "H" || ch === "k" || ch === "K")) { - ch = hour; - } - resultPattern += ch; - } - - return resultPattern; -} - - /** * Returns an object containing the DateTimeFormat internal properties of |obj|. */ @@ -449,16 +397,9 @@ function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, m var formatOpt = new Record(); lazyDateTimeFormatData.formatOpt = formatOpt; - lazyDateTimeFormatData.mozExtensions = mozExtensions; - if (mozExtensions) { let pattern = GetOption(options, "pattern", "string", undefined, undefined); lazyDateTimeFormatData.patternOption = pattern; - - let dateStyle = GetOption(options, "dateStyle", "string", ["full", "long", "medium", "short"], undefined); - lazyDateTimeFormatData.dateStyle = dateStyle; - let timeStyle = GetOption(options, "timeStyle", "string", ["full", "long", "medium", "short"], undefined); - lazyDateTimeFormatData.timeStyle = timeStyle; } // Step 22. @@ -482,6 +423,30 @@ function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, m GetOption(options, "formatMatcher", "string", ["basic", "best fit"], "best fit"); + // "DateTimeFormat dateStyle & timeStyle" propsal + // https://github.com/tc39/proposal-intl-datetime-style + var dateStyle = GetOption(options, "dateStyle", "string", ["full", "long", "medium", "short"], + undefined); + lazyDateTimeFormatData.dateStyle = dateStyle; + + var timeStyle = GetOption(options, "timeStyle", "string", ["full", "long", "medium", "short"], + undefined); + lazyDateTimeFormatData.timeStyle = timeStyle; + + if (dateStyle !== undefined || timeStyle !== undefined) { + var optionsList = [ + "weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName", + ]; + + for (var i = 0; i < optionsList.length; i++) { + var option = optionsList[i]; + if (formatOpt[option] !== undefined) { + ThrowTypeError(JSMSG_INVALID_DATETIME_OPTION, option, + dateStyle !== undefined ? "dateStyle" : "timeStyle"); + } + } + } + // Steps 26-28 provided by ICU, more or less - see comment after this function. // Steps 29-30. @@ -698,7 +663,7 @@ function toBestICUPattern(locale, options) { } // Let ICU convert the ICU skeleton to an ICU pattern for the given locale. - return intl_patternForSkeleton(locale, skeleton); + return intl_patternForSkeleton(locale, skeleton, options.hourCycle); } @@ -744,6 +709,20 @@ function ToDateTimeOptions(options, required, defaults) { needDefaults = false; } + // "DateTimeFormat dateStyle & timeStyle" propsal + // https://github.com/tc39/proposal-intl-datetime-style + var dateStyle = options.dateStyle; + var timeStyle = options.timeStyle; + + if (dateStyle !== undefined || timeStyle !== undefined) + needDefaults = false; + + if (required === "date" && timeStyle !== undefined) + ThrowTypeError(JSMSG_INVALID_DATETIME_STYLE, "timeStyle", "toLocaleDateString"); + + if (required === "time" && dateStyle !== undefined) + ThrowTypeError(JSMSG_INVALID_DATETIME_STYLE, "dateStyle", "toLocaleTimeString"); + // Step 6. if (needDefaults && (defaults === "date" || defaults === "all")) { // The specification says to call [[DefineOwnProperty]] with false for @@ -1000,44 +979,34 @@ function Intl_DateTimeFormat_resolvedOptions() { timeZone: internals.timeZone, }; - if (internals.mozExtensions) { - if (internals.patternOption !== undefined) { - result.pattern = internals.pattern; - } else if (internals.dateStyle || internals.timeStyle) { - result.dateStyle = internals.dateStyle; - result.timeStyle = internals.timeStyle; - } + if (internals.patternOption !== undefined) { + _DefineDataProperty(result, "pattern", internals.pattern); } - resolveICUPattern(internals.pattern, result); + var hasDateStyle = internals.dateStyle !== undefined; + var hasTimeStyle = internals.timeStyle !== undefined; + + if (hasDateStyle || hasTimeStyle) { + if (hasTimeStyle) { + // timeStyle (unlike dateStyle) requires resolving the pattern to + // ensure "hourCycle" and "hour12" properties are added to |result|. + resolveICUPattern(internals.pattern, result, /* includeDateTimeFields = */ false); + } + if (hasDateStyle) { + _DefineDataProperty(result, "dateStyle", internals.dateStyle); + } + if (hasTimeStyle) { + _DefineDataProperty(result, "timeStyle", internals.timeStyle); + } + } else { + resolveICUPattern(internals.pattern, result, /* includeDateTimeFields = */ true); + } // Step 6. 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 @@ -1045,8 +1014,12 @@ var icuPatternCharToComponent = { * interpretation of ICU pattern characters, see * http://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table */ -function resolveICUPattern(pattern, result) { +function resolveICUPattern(pattern, result, includeDateTimeFields) { assert(IsObject(result), "resolveICUPattern"); + + var hourCycle, weekday, era, year, month, day, hour, minute, second, + timeZoneName; + var i = 0; while (i < pattern.length) { var c = pattern[i++]; @@ -1106,27 +1079,91 @@ function resolveICUPattern(pattern, result) { default: // skip other pattern characters and literal text } - if (hasOwn(c, icuPatternCharToComponent)) - _DefineDataProperty(result, icuPatternCharToComponent[c], value); + + // Map 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 switch (c) { - case "h": - _DefineDataProperty(result, "hourCycle", "h12"); - _DefineDataProperty(result, "hour12", true); + case "E": + case "c": + weekday = value; break; - case "K": - _DefineDataProperty(result, "hourCycle", "h11"); - _DefineDataProperty(result, "hour12", true); + case "G": + era = value; + break; + case "y": + year = value; + break; + case "M": + case "L": + month = value; + break; + case "d": + day = value; + break; + case "h": + hourCycle = "h12"; + hour = value; break; case "H": - _DefineDataProperty(result, "hourCycle", "h23"); - _DefineDataProperty(result, "hour12", false); + hourCycle = "h23"; + hour = value; break; case "k": - _DefineDataProperty(result, "hourCycle", "h24"); - _DefineDataProperty(result, "hour12", false); + hourCycle = "h24"; + hour = value; + break; + case "K": + hourCycle = "h11"; + hour = value; + break; + case "m": + minute = value; + break; + case "s": + second = value; + break; + case "z": + case "v": + case "V": + timeZoneName = value; break; } } } + if (hourCycle) { + _DefineDataProperty(result, "hourCycle", hourCycle); + _DefineDataProperty(result, "hour12", hourCycle === "h11" || hourCycle === "h12"); + } + if (!includeDateTimeFields) { + return; + } + if (weekday) { + _DefineDataProperty(result, "weekday", weekday); + } + if (era) { + _DefineDataProperty(result, "era", era); + } + if (year) { + _DefineDataProperty(result, "year", year); + } + if (month) { + _DefineDataProperty(result, "month", month); + } + if (day) { + _DefineDataProperty(result, "day", day); + } + if (hour) { + _DefineDataProperty(result, "hour", hour); + } + if (minute) { + _DefineDataProperty(result, "minute", minute); + } + if (second) { + _DefineDataProperty(result, "second", second); + } + if (timeZoneName) { + _DefineDataProperty(result, "timeZoneName", timeZoneName); + } } diff --git a/js/src/js.msg b/js/src/js.msg index c550087e0..0baec2846 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -500,6 +500,8 @@ MSG_DEF(JSMSG_INVALID_LOCALES_ELEMENT, 0, JSEXN_TYPEERR, "invalid element in loc MSG_DEF(JSMSG_INVALID_LOCALE_MATCHER, 1, JSEXN_RANGEERR, "invalid locale matcher in supportedLocalesOf(): {0}") MSG_DEF(JSMSG_INVALID_OPTION_VALUE, 2, JSEXN_RANGEERR, "invalid value {1} for option {0}") MSG_DEF(JSMSG_INVALID_TIME_ZONE, 1, JSEXN_RANGEERR, "invalid time zone in DateTimeFormat(): {0}") +MSG_DEF(JSMSG_INVALID_DATETIME_OPTION, 2, JSEXN_TYPEERR, "can't set option {0} when {1} is used") +MSG_DEF(JSMSG_INVALID_DATETIME_STYLE, 2, JSEXN_TYPEERR, "can't set option {0} in Date.{1}()") MSG_DEF(JSMSG_UNDEFINED_CURRENCY, 0, JSEXN_TYPEERR, "undefined currency in NumberFormat() with currency style") // RegExp diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 43917b185..51519c85e 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2655,8 +2655,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0), JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0), JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0), - JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0), - JS_FN("intl_patternForStyle", intl_patternForStyle, 3,0), + JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 3, 0), + JS_FN("intl_patternForStyle", intl_patternForStyle, 6, 0), JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0), JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0), JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2,0),