diff options
author | Martok <martok@martoks-place.de> | 2023-06-18 19:58:48 +0200 |
---|---|---|
committer | Martok <martok@martoks-place.de> | 2023-06-30 00:01:36 +0200 |
commit | 569f23eb6aebd9366cd1373416df61ba3326620b (patch) | |
tree | 70888ec0bc5e3c53c6b24f8eda57f68f68ea1686 /js/src | |
parent | af47a256b5cf2b81e4c3bf8f36682f8b9f31be42 (diff) | |
download | uxp-569f23eb6aebd9366cd1373416df61ba3326620b.tar.gz |
Issue #2046 - Implement Intl.DateTimeFormat's date-/timeStyle and hourCycle options
- remove mozExtensions flag and expose to client code
Based-on: m-c 1557718
Diffstat (limited to 'js/src')
-rw-r--r-- | js/src/builtin/intl/DateTimeFormat.cpp | 335 | ||||
-rw-r--r-- | js/src/builtin/intl/DateTimeFormat.h | 11 | ||||
-rw-r--r-- | js/src/builtin/intl/DateTimeFormat.js | 275 | ||||
-rw-r--r-- | js/src/js.msg | 2 | ||||
-rw-r--r-- | js/src/vm/SelfHosting.cpp | 4 |
5 files changed, 472 insertions, 155 deletions
diff --git a/js/src/builtin/intl/DateTimeFormat.cpp b/js/src/builtin/intl/DateTimeFormat.cpp index 0dd724bf2e..dd9df42192 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"
@@ -453,13 +454,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
+* <https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns>.
+*/
+template <typename CharT>
+class PatternIterator {
+ CharT* iter_;
+ const CharT* const end_;
+
+ public:
+ explicit PatternIterator(mozilla::Span<CharT> 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<HourCycle>
+HourCycleFromPattern(mozilla::Span<const char16_t> pattern)
+{
+ PatternIterator<const char16_t> 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<char16_t> pattern, HourCycle hc)
+{
+ char16_t replacement = HourSymbol(hc);
+ PatternIterator<char16_t> 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)
@@ -473,6 +600,16 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) if (!stableChars.initTwoByte(cx, skeletonFlat))
return false;
+ mozilla::Maybe<HourCycle> hourCycle;
+ if (args[2].isString()) {
+ JSLinearString* hourCycleStr = args[2].toString()->ensureLinear(cx);
+ if (!hourCycleStr) {
+ return false;
+ }
+
+ hourCycle.emplace(HourCycleFromOption(hourCycleStr));
+ }
+
mozilla::Range<const char16_t> skeletonChars = stableChars.twoByteRange();
uint32_t skeletonLen = u_strlen(Char16ToUChar(skeletonChars.begin().get()));
@@ -484,69 +621,175 @@ js::intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp) }
ScopedICUObject<UDateTimePatternGenerator, udatpg_close> 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<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> 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<CanGC>(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 <size_t N>
+static bool
+FindPatternWithHourCycle(JSContext* cx, const char* locale,
+ Vector<char16_t, N>& 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<UDateTimePatternGenerator, udatpg_close> toClose(gen);
+
+ if (!gen) {
+ return false;
+ }
+
+ Vector<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> 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<bool> hour12;
+ if (args[4].isBoolean()) {
+ hour12.emplace(args[4].toBoolean());
+ }
+
+ mozilla::Maybe<HourCycle> hourCycle;
+ if (args[5].isString()) {
+ JSLinearString* hourCycleStr = args[5].toString()->ensureLinear(cx);
+ if (!hourCycleStr) {
+ return false;
+ }
+
+ hourCycle.emplace(HourCycleFromOption(hourCycleStr));
+ }
+
mozilla::Range<const char16_t> timeZoneChars = timeZone.twoByteRange();
UErrorCode status = U_ZERO_ERROR;
@@ -559,9 +802,39 @@ js::intl_patternForStyle(JSContext* cx, unsigned argc, Value* vp) }
ScopedICUObject<UDateFormat, udat_close> toClose(df);
- JSString* str = CallICU(cx, [df](UChar* chars, uint32_t size, UErrorCode* status) {
- return udat_toPattern(df, false, chars, size, status);
- });
+ Vector<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> 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<CanGC>(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 da53d65eaa..5a223e8716 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 9d1adc8687..99c078fec4 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;
-
- internalProps.patternOption = lazyDateTimeFormatData.patternOption;
- } else if (lazyDateTimeFormatData.dateStyle || lazyDateTimeFormatData.timeStyle) {
- pattern = intl_patternForStyle(dataLocale,
- lazyDateTimeFormatData.dateStyle, lazyDateTimeFormatData.timeStyle,
- lazyDateTimeFormatData.timeZone);
-
- internalProps.dateStyle = lazyDateTimeFormatData.dateStyle;
- internalProps.timeStyle = lazyDateTimeFormatData.timeStyle;
- } else {
- pattern = toBestICUPattern(dataLocale, formatOpt);
- }
- internalProps.mozExtensions = true;
+ if (lazyDateTimeFormatData.patternOption !== undefined) {
+ pattern = lazyDateTimeFormatData.patternOption;
+
+ 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);
+ 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;
@@ -131,47 +120,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|.
*/
function getDateTimeFormatInternals(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 a2a1e3f3d2..242d81a5c8 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -497,6 +497,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 6446cbb4be..357e151bde 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -2479,8 +2479,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), |