mirror of
https://github.com/roytam1/basilisk55.git
synced 2026-05-26 14:58:59 +00:00
import from UXP: Issue #2046 - Implement Intl.DateTimeFormat's date-/timeStyle and hourCycle options (4400677a)
This commit is contained in:
@@ -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
|
||||
* <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)
|
||||
@@ -467,6 +594,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()));
|
||||
|
||||
@@ -478,69 +615,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;
|
||||
@@ -553,9 +796,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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user