Skip to content

Commit 6df8246

Browse files
feat(mf2): Support datetime override options (unicode-org/message-format-wg#911)
1 parent 1211464 commit 6df8246

File tree

1 file changed

+65
-26
lines changed

1 file changed

+65
-26
lines changed

mf2/messageformat/src/functions/datetime.ts

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,19 @@ export interface MessageDateTimePart extends MessageExpressionPart {
2424
parts: Intl.DateTimeFormatPart[];
2525
}
2626

27-
const localeOptions = [
28-
'calendar',
29-
'localeMatcher',
30-
'hour12',
31-
'hourCycle',
32-
'numberingSystem',
33-
'timeZone'
34-
];
27+
const styleOptions = new Set(['dateStyle', 'timeStyle']);
28+
const fieldOptions = new Set([
29+
'weekday',
30+
'era',
31+
'year',
32+
'month',
33+
'day',
34+
'hour',
35+
'minute',
36+
'second',
37+
'fractionalSecondDigits',
38+
'timeZoneName'
39+
]);
3540

3641
/**
3742
* `datetime` accepts a Date, number or string as its input
@@ -46,6 +51,8 @@ export const datetime = (
4651
input?: unknown
4752
): MessageDateTime =>
4853
dateTimeImplementation(ctx, input, res => {
54+
let hasStyle = false;
55+
let hasFields = false;
4956
for (const [name, value] of Object.entries(options)) {
5057
if (value === undefined) continue;
5158
try {
@@ -54,23 +61,27 @@ export const datetime = (
5461
break;
5562
case 'fractionalSecondDigits':
5663
res[name] = asPositiveInteger(value);
64+
hasFields = true;
5765
break;
5866
case 'hour12':
5967
res[name] = asBoolean(value);
6068
break;
6169
default:
6270
res[name] = asString(value);
71+
if (!hasStyle && styleOptions.has(name)) hasStyle = true;
72+
if (!hasFields && fieldOptions.has(name)) hasFields = true;
6373
}
6474
} catch {
65-
const msg = `Value ${value} is not valid for :datetime option ${name}`;
75+
const msg = `Value ${value} is not valid for :datetime ${name} option`;
6676
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
6777
}
6878
}
69-
70-
// Set defaults if localeMatcher is the only option
71-
if (Object.keys(res).length <= 1) {
79+
if (!hasStyle && !hasFields) {
7280
res.dateStyle = 'medium';
7381
res.timeStyle = 'short';
82+
} else if (hasStyle && hasFields) {
83+
const msg = 'Style and field options cannot be both set for :datetime';
84+
throw new MessageResolutionError('bad-option', msg, ctx.source);
7485
}
7586
});
7687

@@ -86,16 +97,30 @@ export const date = (
8697
input?: unknown
8798
): MessageDateTime =>
8899
dateTimeImplementation(ctx, input, res => {
89-
const ds = options.style ?? res.dateStyle ?? 'medium';
90100
for (const name of Object.keys(res)) {
91-
if (!localeOptions.includes(name)) delete res[name];
101+
if (styleOptions.has(name) || fieldOptions.has(name)) delete res[name];
92102
}
93-
try {
94-
res.dateStyle = asString(ds);
95-
} catch {
96-
const msg = `Value ${ds} is not valid for :date style option`;
97-
throw new MessageResolutionError('bad-option', msg, ctx.source);
103+
for (const [name, value] of Object.entries(options)) {
104+
if (value === undefined) continue;
105+
try {
106+
switch (name) {
107+
case 'style':
108+
res.dateStyle = asString(value);
109+
break;
110+
case 'hour12':
111+
res[name] = asBoolean(value);
112+
break;
113+
case 'calendar':
114+
case 'numberingSystem':
115+
case 'timeZone':
116+
res[name] = asString(value);
117+
}
118+
} catch {
119+
const msg = `Value ${value} is not valid for :date ${name} option`;
120+
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
121+
}
98122
}
123+
res.dateStyle ??= 'medium';
99124
});
100125

101126
/**
@@ -110,16 +135,30 @@ export const time = (
110135
input?: unknown
111136
): MessageDateTime =>
112137
dateTimeImplementation(ctx, input, res => {
113-
const ts = options.style ?? res.timeStyle ?? 'short';
114138
for (const name of Object.keys(res)) {
115-
if (!localeOptions.includes(name)) delete res[name];
139+
if (styleOptions.has(name) || fieldOptions.has(name)) delete res[name];
116140
}
117-
try {
118-
res.timeStyle = asString(ts);
119-
} catch {
120-
const msg = `Value ${ts} is not valid for :time style option`;
121-
throw new MessageResolutionError('bad-option', msg, ctx.source);
141+
for (const [name, value] of Object.entries(options)) {
142+
if (value === undefined) continue;
143+
try {
144+
switch (name) {
145+
case 'style':
146+
res.timeStyle = asString(value);
147+
break;
148+
case 'hour12':
149+
res[name] = asBoolean(value);
150+
break;
151+
case 'calendar':
152+
case 'numberingSystem':
153+
case 'timeZone':
154+
res[name] = asString(value);
155+
}
156+
} catch {
157+
const msg = `Value ${value} is not valid for :time ${name} option`;
158+
ctx.onError(new MessageResolutionError('bad-option', msg, ctx.source));
159+
}
122160
}
161+
res.timeStyle ??= 'short';
123162
});
124163

125164
function dateTimeImplementation(

0 commit comments

Comments
 (0)