Bug 1569103: Add support for "dayPeriod" option to Intl.DateTimeFormat. r=jwalden

Nightly-only for now until the open spec issues are addressed.

Differential Revision: https://phabricator.services.mozilla.com/D39465
This commit is contained in:
André Bargull 2020-05-05 17:38:40 +00:00
parent 9855279a32
commit be41cb6dfb
13 changed files with 356 additions and 13 deletions

View File

@ -825,6 +825,14 @@ static FieldType GetFieldTypeForFormatField(UDateFormatField fieldName) {
return &JSAtomState::unknown;
#endif
case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
#ifdef NIGHTLY_BUILD
return &JSAtomState::dayPeriod;
#else
// Currently restricted to Nightly.
return &JSAtomState::unknown;
#endif
#ifndef U_HIDE_INTERNAL_API
case UDAT_RELATED_YEAR_FIELD:
return &JSAtomState::relatedYear;
@ -843,7 +851,6 @@ static FieldType GetFieldTypeForFormatField(UDateFormatField fieldName) {
case UDAT_TIMEZONE_ISO_FIELD:
case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
#ifndef U_HIDE_INTERNAL_API
case UDAT_TIME_SEPARATOR_FIELD:
#endif

View File

@ -433,6 +433,10 @@ function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, m
formatOpt.month = GetOption(options, "month", "string",
["2-digit", "numeric", "narrow", "short", "long"], undefined);
formatOpt.day = GetOption(options, "day", "string", ["2-digit", "numeric"], undefined);
#ifdef NIGHTLY_BUILD
formatOpt.dayPeriod = GetOption(options, "dayPeriod", "string", ["narrow", "short", "long"],
undefined);
#endif
formatOpt.hour = GetOption(options, "hour", "string", ["2-digit", "numeric"], undefined);
formatOpt.minute = GetOption(options, "minute", "string", ["2-digit", "numeric"], undefined);
formatOpt.second = GetOption(options, "second", "string", ["2-digit", "numeric"], undefined);
@ -644,6 +648,21 @@ function toBestICUPattern(locale, options) {
skeleton += hourSkeletonChar;
break;
}
#ifdef NIGHTLY_BUILD
// ICU requires that "B" is set after the "j" hour skeleton symbol.
// https://unicode-org.atlassian.net/browse/ICU-20731
switch (options.dayPeriod) {
case "narrow":
skeleton += "BBBBB";
break;
case "short":
skeleton += "B";
break;
case "long":
skeleton += "BBBB";
break;
}
#endif
switch (options.minute) {
case "2-digit":
skeleton += "mm";
@ -723,6 +742,10 @@ function ToDateTimeOptions(options, required, defaults) {
// Step 5.
if (required === "time" || required === "any") {
#ifdef NIGHTLY_BUILD
if (options.dayPeriod !== undefined)
needDefaults = false;
#endif
if (options.hour !== undefined)
needDefaults = false;
if (options.minute !== undefined)
@ -934,7 +957,8 @@ function Intl_DateTimeFormat_resolvedOptions() {
function resolveICUPattern(pattern, result) {
assert(IsObject(result), "resolveICUPattern");
var hourCycle, weekday, era, year, month, day, hour, minute, second, fractionalSecondDigits, timeZoneName;
var hourCycle, weekday, era, year, month, day, dayPeriod, hour, minute, second,
fractionalSecondDigits, timeZoneName;
var i = 0;
while (i < pattern.length) {
var c = pattern[i++];
@ -955,6 +979,7 @@ function resolveICUPattern(pattern, result) {
case "G":
case "E":
case "c":
case "B":
case "z":
case "v":
case "V":
@ -1021,6 +1046,9 @@ function resolveICUPattern(pattern, result) {
case "d":
day = value;
break;
case "B":
dayPeriod = value;
break;
case "h":
hourCycle = "h12";
hour = value;
@ -1074,6 +1102,11 @@ function resolveICUPattern(pattern, result) {
if (day) {
_DefineDataProperty(result, "day", day);
}
#ifdef NIGHTLY_BUILD
if (dayPeriod) {
_DefineDataProperty(result, "dayPeriod", dayPeriod);
}
#endif
if (hour) {
_DefineDataProperty(result, "hour", hour);
}

View File

@ -0,0 +1,102 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
const {
Year, Month, Day, DayPeriod, Hour, Minute, Literal
} = DateTimeFormatParts;
// If the locale defaults to a 24-hour-cycle, the "dayPeriod" option is ignored if an "hour" option
// is also present, unless the hour-cycle is manually set to a 12-hour-cycle.
const tests = [
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", hour: "numeric", },
locales: {
en: [Hour("12"), Literal(" "), DayPeriod("noon")],
de: [Hour("12"), Literal(" Uhr")],
},
},
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", hour: "numeric", hour12: true },
locales: {
en: [Hour("12"), Literal(" "), DayPeriod("noon")],
de: [Hour("12"), Literal(" "), DayPeriod("mittags")],
},
},
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", hour: "numeric", hour12: false },
locales: {
en: [Hour("12")],
de: [Hour("12"), Literal(" Uhr")],
},
},
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", hour: "numeric", hourCycle: "h12" },
locales: {
en: [Hour("12"), Literal(" "), DayPeriod("noon")],
de: [Hour("12"), Literal(" "), DayPeriod("mittags")],
},
},
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", hour: "numeric", hourCycle: "h11" },
locales: {
en: [Hour("0"), Literal(" "), DayPeriod("noon")],
de: [Hour("0"), Literal(" "), DayPeriod("mittags")],
},
},
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", hour: "numeric", hourCycle: "h23" },
locales: {
en: [Hour("12")],
de: [Hour("12"), Literal(" Uhr")],
},
},
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", hour: "numeric", hourCycle: "h24" },
locales: {
en: [Hour("12")],
de: [Hour("12"), Literal(" Uhr")],
},
},
// The default hour-cycle is irrelevant when an "hour" option isn't present.
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", day: "numeric", month: "numeric", year: "numeric" },
locales: {
en: [Month("1"), Literal("/"), Day("1"), Literal("/"), Year("2019"), Literal(", "), DayPeriod("noon")],
de: [Day("1"), Literal("."), Month("1"), Literal("."), Year("2019"), Literal(", "), DayPeriod("mittags")],
},
},
// ICU replacement pattern for missing <appendItem> entries in CLDR.
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "short", minute: "numeric" },
locales: {
en: [Minute("0"), Literal(" ├AM/PM: "), DayPeriod("noon"), Literal("┤")],
de: [Minute("0"), Literal(" ├Tageshälfte: "), DayPeriod("mittags"), Literal("┤")],
},
},
];
for (let {date, options, locales} of tests) {
for (let [locale, parts] of Object.entries(locales)) {
let dtf = new Intl.DateTimeFormat(locale, options);
assertEq(dtf.format(date), parts.map(({value}) => value).join(""),
`locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
assertDeepEq(dtf.formatToParts(date), parts,
`locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
}
}
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View File

@ -0,0 +1,160 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
// Tests using various locales to cover all day period types:
// "midnight", "noon", "morning1", "morning2", "afternoon1", "afternoon2",
// "evening1", "evening2", "night1", "night2".
const tests = [
{
// ICU doesn't support "midnight" and instead uses "night1" resp. "night2".
// ICU bug: https://unicode-org.atlassian.net/projects/ICU/issues/ICU-12278
date: new Date("2019-01-01T00:00:00"),
locales: {
en: { narrow: "at night", short: "at night", long: "at night" },
de: { narrow: "nachts", short: "nachts", long: "nachts" },
th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
ja: { narrow: "夜中", short: "夜中", long: "夜中" },
}
},
{
date: new Date("2019-01-01T03:00:00"),
locales: {
en: { narrow: "at night", short: "at night", long: "at night" },
de: { narrow: "nachts", short: "nachts", long: "nachts" },
th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
ja: { narrow: "夜中", short: "夜中", long: "夜中" },
}
},
{
date: new Date("2019-01-01T04:00:00"),
locales: {
en: { narrow: "at night", short: "at night", long: "at night" },
de: { narrow: "nachts", short: "nachts", long: "nachts" },
th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
ja: { narrow: "朝", short: "朝", long: "朝" },
}
},
{
date: new Date("2019-01-01T05:00:00"),
locales: {
en: { narrow: "at night", short: "at night", long: "at night" },
de: { narrow: "morgens", short: "morgens", long: "morgens" },
th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
ja: { narrow: "朝", short: "朝", long: "朝" },
}
},
{
date: new Date("2019-01-01T06:00:00"),
locales: {
en: { narrow: "in the morning", short: "in the morning", long: "in the morning" },
de: { narrow: "morgens", short: "morgens", long: "morgens" },
th: { narrow: "เช้า", short: "ในตอนเช้า", long: "ในตอนเช้า" },
ja: { narrow: "朝", short: "朝", long: "朝" },
}
},
{
date: new Date("2019-01-01T10:00:00"),
locales: {
en: { narrow: "in the morning", short: "in the morning", long: "in the morning" },
de: { narrow: "vorm.", short: "vorm.", long: "vormittags" },
th: { narrow: "เช้า", short: "ในตอนเช้า", long: "ในตอนเช้า" },
ja: { narrow: "朝", short: "朝", long: "朝" },
}
},
{
date: new Date("2019-01-01T12:00:00"),
locales: {
en: { narrow: "n", short: "noon", long: "noon" },
de: { narrow: "mittags", short: "mittags", long: "mittags" },
th: { narrow: "เที่ยง", short: "เที่ยง", long: "เที่ยง" },
ja: { narrow: "正午", short: "正午", long: "正午" },
}
},
{
date: new Date("2019-01-01T13:00:00"),
locales: {
en: { narrow: "in the afternoon", short: "in the afternoon", long: "in the afternoon" },
de: { narrow: "nachm.", short: "nachm.", long: "nachmittags" },
th: { narrow: "บ่าย", short: "บ่าย", long: "บ่าย" },
ja: { narrow: "昼", short: "昼", long: "昼" },
}
},
{
date: new Date("2019-01-01T15:00:00"),
locales: {
en: { narrow: "in the afternoon", short: "in the afternoon", long: "in the afternoon" },
de: { narrow: "nachm.", short: "nachm.", long: "nachmittags" },
th: { narrow: "บ่าย", short: "บ่าย", long: "บ่าย" },
ja: { narrow: "昼", short: "昼", long: "昼" },
}
},
{
date: new Date("2019-01-01T16:00:00"),
locales: {
en: { narrow: "in the afternoon", short: "in the afternoon", long: "in the afternoon" },
de: { narrow: "nachm.", short: "nachm.", long: "nachmittags" },
th: { narrow: "เย็น", short: "ในตอนเย็น", long: "ในตอนเย็น" },
ja: { narrow: "夕方", short: "夕方", long: "夕方" },
}
},
{
date: new Date("2019-01-01T18:00:00"),
locales: {
en: { narrow: "in the evening", short: "in the evening", long: "in the evening" },
de: { narrow: "abends", short: "abends", long: "abends" },
th: { narrow: "ค่ำ", short: "ค่ำ", long: "ค่ำ" },
ja: { narrow: "夕方", short: "夕方", long: "夕方" },
}
},
{
date: new Date("2019-01-01T19:00:00"),
locales: {
en: { narrow: "in the evening", short: "in the evening", long: "in the evening" },
de: { narrow: "abends", short: "abends", long: "abends" },
th: { narrow: "ค่ำ", short: "ค่ำ", long: "ค่ำ" },
ja: { narrow: "夜", short: "夜", long: "夜" },
}
},
{
date: new Date("2019-01-01T21:00:00"),
locales: {
en: { narrow: "at night", short: "at night", long: "at night" },
de: { narrow: "abends", short: "abends", long: "abends" },
th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
ja: { narrow: "夜", short: "夜", long: "夜" },
}
},
{
date: new Date("2019-01-01T22:00:00"),
locales: {
en: { narrow: "at night", short: "at night", long: "at night" },
de: { narrow: "abends", short: "abends", long: "abends" },
th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
ja: { narrow: "夜", short: "夜", long: "夜" },
}
},
{
date: new Date("2019-01-01T23:00:00"),
locales: {
en: { narrow: "at night", short: "at night", long: "at night" },
de: { narrow: "abends", short: "abends", long: "abends" },
th: { narrow: "กลางคืน", short: "กลางคืน", long: "กลางคืน" },
ja: { narrow: "夜中", short: "夜中", long: "夜中" },
}
},
];
for (let {date, locales} of tests) {
for (let [locale, formats] of Object.entries(locales)) {
for (let [dayPeriod, expected] of Object.entries(formats)) {
let dtf = new Intl.DateTimeFormat(locale, {dayPeriod});
assertEq(dtf.format(date), expected,
`locale=${locale}, date=${date}, dayPeriod=${dayPeriod}`);
assertDeepEq(dtf.formatToParts(date), [{type: "dayPeriod", value: expected}]);
}
}
}
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View File

@ -0,0 +1,40 @@
// |reftest| skip-if(!this.hasOwnProperty("Intl")||release_or_beta)
const {
Weekday, DayPeriod, Literal
} = DateTimeFormatParts;
const tests = [
// https://unicode-org.atlassian.net/browse/ICU-20741
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "long", weekday: "long", },
locales: {
"en-001": [Weekday("Tuesday"), Literal(", "), DayPeriod("noon")],
},
},
// https://unicode-org.atlassian.net/browse/ICU-20740
{
date: new Date("2019-01-01T12:00:00"),
options: { dayPeriod: "narrow", weekday: "long", },
locales: {
"bs-Cyrl": [Weekday("уторак"), Literal(" "), DayPeriod("подне")],
},
},
];
for (let {date, options, locales} of tests) {
for (let [locale, parts] of Object.entries(locales)) {
let dtf = new Intl.DateTimeFormat(locale, options);
assertEq(dtf.format(date), parts.map(({value}) => value).join(""),
`locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
assertDeepEq(dtf.formatToParts(date), parts,
`locale=${locale}, date=${date}, options=${JSON.stringify(options)}`);
}
}
if (typeof reportCompare === "function")
reportCompare(0, 0, "ok");

View File

@ -23,17 +23,18 @@ var proxy = new Proxy({
}));
var fractionalSecondDigits = isNightly ? ["fractionalSecondDigits"] : [];
var dayPeriod = isNightly ? ["dayPeriod"] : [];
var constructorAccesses = [
// ToDateTimeOptions(options, "any", "date").
"weekday", "year", "month", "day",
"hour", "minute", "second", ...fractionalSecondDigits,
...dayPeriod, "hour", "minute", "second", ...fractionalSecondDigits,
// InitializeDateTimeFormat
"localeMatcher", "calendar", "numberingSystem", "hour12", "hourCycle", "timeZone",
// Table 5: Components of date and time formats
"weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName",
"weekday", "era", "year", "month", "day", ...dayPeriod, "hour", "minute", "second", "timeZoneName",
// InitializeDateTimeFormat
...fractionalSecondDigits,
@ -51,7 +52,7 @@ new Date().toLocaleString(undefined, proxy);
assertEqArray(log, [
// ToDateTimeOptions(options, "any", "all").
"weekday", "year", "month", "day",
"hour", "minute", "second", ...fractionalSecondDigits,
...dayPeriod, "hour", "minute", "second", ...fractionalSecondDigits,
...constructorAccesses
]);
@ -71,7 +72,7 @@ new Date().toLocaleTimeString(undefined, proxy);
assertEqArray(log, [
// ToDateTimeOptions(options, "time", "time").
"hour", "minute", "second", ...fractionalSecondDigits,
...dayPeriod, "hour", "minute", "second", ...fractionalSecondDigits,
...constructorAccesses
]);

View File

@ -33,7 +33,6 @@ UNSUPPORTED_FEATURES = set([
"export-star-as-namespace-from-module",
"Intl.DateTimeFormat-quarter",
"Intl.DateTimeFormat-datetimestyle",
"Intl.DateTimeFormat-dayPeriod",
"Intl.DateTimeFormat-formatRange",
"Intl.DisplayNames",
"Intl.Segmenter",
@ -47,6 +46,7 @@ FEATURE_CHECK_NEEDED = {
}
RELEASE_OR_BETA = set([
"Intl.DateTimeFormat-fractionalSecondDigits",
"Intl.DateTimeFormat-dayPeriod",
"Promise.any",
"AggregateError",
"logical-assignment-operators",

View File

@ -1,4 +1,4 @@
// |reftest| skip -- Intl.DateTimeFormat-dayPeriod is not supported
// |reftest| skip-if(release_or_beta) -- Intl.DateTimeFormat-dayPeriod is not released yet
// Copyright 2019 Google Inc. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// |reftest| skip -- Intl.DateTimeFormat-dayPeriod is not supported
// |reftest| skip-if(release_or_beta) -- Intl.DateTimeFormat-dayPeriod is not released yet
// Copyright 2019 Google Inc. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// |reftest| skip -- Intl.DateTimeFormat-dayPeriod is not supported
// |reftest| skip-if(release_or_beta) -- Intl.DateTimeFormat-dayPeriod is not released yet
// Copyright 2019 Googe Inc. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// |reftest| skip -- Intl.DateTimeFormat-dayPeriod is not supported
// |reftest| skip-if(release_or_beta) -- Intl.DateTimeFormat-dayPeriod is not released yet
// Copyright 2019 Google Inc. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// |reftest| skip -- Intl.DateTimeFormat-dayPeriod is not supported
// |reftest| skip-if(release_or_beta) -- Intl.DateTimeFormat-dayPeriod is not released yet
// Copyright 2019 Google Inc. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

View File

@ -1,4 +1,4 @@
// |reftest| skip -- Intl.DateTimeFormat-dayPeriod is not supported
// |reftest| skip-if(release_or_beta) -- Intl.DateTimeFormat-dayPeriod is not released yet
// Copyright 2019 Google Inc. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.