messageformat
Version:
Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill
191 lines (190 loc) • 6.24 kB
JavaScript
import { getLocaleDir } from "../dir-utils.js";
import { MessageResolutionError } from "../errors.js";
import { asBoolean, asPositiveInteger, asString } from "./utils.js";
const styleOptions = new Set(['dateStyle', 'timeStyle']);
const fieldOptions = new Set([
'weekday',
'era',
'year',
'month',
'day',
'hour',
'minute',
'second',
'fractionalSecondDigits',
'timeZoneName'
]);
/**
* `datetime` accepts a Date, number or string as its input
* and formats it with the same options as
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat | Intl.DateTimeFormat}.
*
* @beta
*/
export const datetime = (ctx, options, operand) => dateTimeImplementation(ctx, operand, res => {
let hasStyle = false;
let hasFields = false;
for (const [name, value] of Object.entries(options)) {
if (value === undefined)
continue;
try {
switch (name) {
case 'locale':
break;
case 'fractionalSecondDigits':
res[name] = asPositiveInteger(value);
hasFields = true;
break;
case 'hour12':
res[name] = asBoolean(value);
break;
default:
res[name] = asString(value);
if (!hasStyle && styleOptions.has(name))
hasStyle = true;
if (!hasFields && fieldOptions.has(name))
hasFields = true;
}
}
catch {
const msg = `Value ${value} is not valid for :datetime ${name} option`;
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
}
}
if (!hasStyle && !hasFields) {
res.dateStyle = 'medium';
res.timeStyle = 'short';
}
else if (hasStyle && hasFields) {
const msg = 'Style and field options cannot be both set for :datetime';
throw new MessageResolutionError('bad-option', msg, ctx.source);
}
});
/**
* `date` accepts a Date, number or string as its input
* and formats it according to a single "style" option.
*
* @beta
*/
export const date = (ctx, options, operand) => dateTimeImplementation(ctx, operand, res => {
for (const name of Object.keys(res)) {
if (styleOptions.has(name) || fieldOptions.has(name))
delete res[name];
}
for (const [name, value] of Object.entries(options)) {
if (value === undefined)
continue;
try {
switch (name) {
case 'style':
res.dateStyle = asString(value);
break;
case 'hour12':
res[name] = asBoolean(value);
break;
case 'calendar':
case 'timeZone':
res[name] = asString(value);
}
}
catch {
const msg = `Value ${value} is not valid for :date ${name} option`;
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
}
}
res.dateStyle ??= 'medium';
});
/**
* `time` accepts a Date, number or string as its input
* and formats it according to a single "style" option.
*
* @beta
*/
export const time = (ctx, options, operand) => dateTimeImplementation(ctx, operand, res => {
for (const name of Object.keys(res)) {
if (styleOptions.has(name) || fieldOptions.has(name))
delete res[name];
}
for (const [name, value] of Object.entries(options)) {
if (value === undefined)
continue;
try {
switch (name) {
case 'style':
res.timeStyle = asString(value);
break;
case 'hour12':
res[name] = asBoolean(value);
break;
case 'calendar':
case 'timeZone':
res[name] = asString(value);
}
}
catch {
const msg = `Value ${value} is not valid for :time ${name} option`;
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
}
}
res.timeStyle ??= 'short';
});
function dateTimeImplementation(ctx, input, parseOptions) {
const { localeMatcher, locales, source } = ctx;
const opt = { localeMatcher };
if (input && typeof input === 'object') {
if (input && 'options' in input)
Object.assign(opt, input.options);
if (!(input instanceof Date) && typeof input.valueOf === 'function') {
input = input.valueOf();
}
}
let value;
switch (typeof input) {
case 'number':
case 'string':
value = new Date(input);
break;
case 'object':
value = input;
break;
}
if (!(value instanceof Date) || isNaN(value.getTime())) {
const msg = 'Input is not a date';
throw new MessageResolutionError('bad-operand', msg, source);
}
parseOptions(opt);
const date = value;
let locale;
let dir = ctx.dir;
let dtf;
let str;
return {
type: 'datetime',
source,
get dir() {
if (dir == null) {
locale ??= Intl.DateTimeFormat.supportedLocalesOf(locales, opt)[0];
dir = getLocaleDir(locale);
}
return dir;
},
get options() {
return { ...opt };
},
toParts() {
dtf ??= new Intl.DateTimeFormat(locales, opt);
const parts = dtf.formatToParts(date);
locale ??= dtf.resolvedOptions().locale;
dir ??= getLocaleDir(locale);
return dir === 'ltr' || dir === 'rtl'
? [{ type: 'datetime', source, dir, locale, parts }]
: [{ type: 'datetime', source, locale, parts }];
},
toString() {
dtf ??= new Intl.DateTimeFormat(locales, opt);
str ??= dtf.format(date);
return str;
},
valueOf: () => date
};
}