UNPKG

14.9 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.parseNumberSkeleton = exports.parseDateTimeSkeleton = void 0;
4var tslib_1 = require("tslib");
5/**
6 * https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
7 * Credit: https://github.com/caridy/intl-datetimeformat-pattern/blob/master/index.js
8 * with some tweaks
9 */
10var DATE_TIME_REGEX = /(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;
11/**
12 * Parse Date time skeleton into Intl.DateTimeFormatOptions
13 * Ref: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
14 * @public
15 * @param skeleton skeleton string
16 */
17function parseDateTimeSkeleton(skeleton) {
18 var result = {};
19 skeleton.replace(DATE_TIME_REGEX, function (match) {
20 var len = match.length;
21 switch (match[0]) {
22 // Era
23 case 'G':
24 result.era = len === 4 ? 'long' : len === 5 ? 'narrow' : 'short';
25 break;
26 // Year
27 case 'y':
28 result.year = len === 2 ? '2-digit' : 'numeric';
29 break;
30 case 'Y':
31 case 'u':
32 case 'U':
33 case 'r':
34 throw new RangeError('`Y/u/U/r` (year) patterns are not supported, use `y` instead');
35 // Quarter
36 case 'q':
37 case 'Q':
38 throw new RangeError('`q/Q` (quarter) patterns are not supported');
39 // Month
40 case 'M':
41 case 'L':
42 result.month = ['numeric', '2-digit', 'short', 'long', 'narrow'][len - 1];
43 break;
44 // Week
45 case 'w':
46 case 'W':
47 throw new RangeError('`w/W` (week) patterns are not supported');
48 case 'd':
49 result.day = ['numeric', '2-digit'][len - 1];
50 break;
51 case 'D':
52 case 'F':
53 case 'g':
54 throw new RangeError('`D/F/g` (day) patterns are not supported, use `d` instead');
55 // Weekday
56 case 'E':
57 result.weekday = len === 4 ? 'short' : len === 5 ? 'narrow' : 'short';
58 break;
59 case 'e':
60 if (len < 4) {
61 throw new RangeError('`e..eee` (weekday) patterns are not supported');
62 }
63 result.weekday = ['short', 'long', 'narrow', 'short'][len - 4];
64 break;
65 case 'c':
66 if (len < 4) {
67 throw new RangeError('`c..ccc` (weekday) patterns are not supported');
68 }
69 result.weekday = ['short', 'long', 'narrow', 'short'][len - 4];
70 break;
71 // Period
72 case 'a': // AM, PM
73 result.hour12 = true;
74 break;
75 case 'b': // am, pm, noon, midnight
76 case 'B': // flexible day periods
77 throw new RangeError('`b/B` (period) patterns are not supported, use `a` instead');
78 // Hour
79 case 'h':
80 result.hourCycle = 'h12';
81 result.hour = ['numeric', '2-digit'][len - 1];
82 break;
83 case 'H':
84 result.hourCycle = 'h23';
85 result.hour = ['numeric', '2-digit'][len - 1];
86 break;
87 case 'K':
88 result.hourCycle = 'h11';
89 result.hour = ['numeric', '2-digit'][len - 1];
90 break;
91 case 'k':
92 result.hourCycle = 'h24';
93 result.hour = ['numeric', '2-digit'][len - 1];
94 break;
95 case 'j':
96 case 'J':
97 case 'C':
98 throw new RangeError('`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead');
99 // Minute
100 case 'm':
101 result.minute = ['numeric', '2-digit'][len - 1];
102 break;
103 // Second
104 case 's':
105 result.second = ['numeric', '2-digit'][len - 1];
106 break;
107 case 'S':
108 case 'A':
109 throw new RangeError('`S/A` (second) patterns are not supported, use `s` instead');
110 // Zone
111 case 'z': // 1..3, 4: specific non-location format
112 result.timeZoneName = len < 4 ? 'short' : 'long';
113 break;
114 case 'Z': // 1..3, 4, 5: The ISO8601 varios formats
115 case 'O': // 1, 4: miliseconds in day short, long
116 case 'v': // 1, 4: generic non-location format
117 case 'V': // 1, 2, 3, 4: time zone ID or city
118 case 'X': // 1, 2, 3, 4: The ISO8601 varios formats
119 case 'x': // 1, 2, 3, 4: The ISO8601 varios formats
120 throw new RangeError('`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead');
121 }
122 return '';
123 });
124 return result;
125}
126exports.parseDateTimeSkeleton = parseDateTimeSkeleton;
127function icuUnitToEcma(unit) {
128 return unit.replace(/^(.*?)-/, '');
129}
130var FRACTION_PRECISION_REGEX = /^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g;
131var SIGNIFICANT_PRECISION_REGEX = /^(@+)?(\+|#+)?$/g;
132var INTEGER_WIDTH_REGEX = /(\*)(0+)|(#+)(0+)|(0+)/g;
133var CONCISE_INTEGER_WIDTH_REGEX = /^(0+)$/;
134function parseSignificantPrecision(str) {
135 var result = {};
136 str.replace(SIGNIFICANT_PRECISION_REGEX, function (_, g1, g2) {
137 // @@@ case
138 if (typeof g2 !== 'string') {
139 result.minimumSignificantDigits = g1.length;
140 result.maximumSignificantDigits = g1.length;
141 }
142 // @@@+ case
143 else if (g2 === '+') {
144 result.minimumSignificantDigits = g1.length;
145 }
146 // .### case
147 else if (g1[0] === '#') {
148 result.maximumSignificantDigits = g1.length;
149 }
150 // .@@## or .@@@ case
151 else {
152 result.minimumSignificantDigits = g1.length;
153 result.maximumSignificantDigits =
154 g1.length + (typeof g2 === 'string' ? g2.length : 0);
155 }
156 return '';
157 });
158 return result;
159}
160function parseSign(str) {
161 switch (str) {
162 case 'sign-auto':
163 return {
164 signDisplay: 'auto',
165 };
166 case 'sign-accounting':
167 case '()':
168 return {
169 currencySign: 'accounting',
170 };
171 case 'sign-always':
172 case '+!':
173 return {
174 signDisplay: 'always',
175 };
176 case 'sign-accounting-always':
177 case '()!':
178 return {
179 signDisplay: 'always',
180 currencySign: 'accounting',
181 };
182 case 'sign-except-zero':
183 case '+?':
184 return {
185 signDisplay: 'exceptZero',
186 };
187 case 'sign-accounting-except-zero':
188 case '()?':
189 return {
190 signDisplay: 'exceptZero',
191 currencySign: 'accounting',
192 };
193 case 'sign-never':
194 case '+_':
195 return {
196 signDisplay: 'never',
197 };
198 }
199}
200function parseConciseScientificAndEngineeringStem(stem) {
201 // Engineering
202 var result;
203 if (stem[0] === 'E' && stem[1] === 'E') {
204 result = {
205 notation: 'engineering',
206 };
207 stem = stem.slice(2);
208 }
209 else if (stem[0] === 'E') {
210 result = {
211 notation: 'scientific',
212 };
213 stem = stem.slice(1);
214 }
215 if (result) {
216 var signDisplay = stem.slice(0, 2);
217 if (signDisplay === '+!') {
218 result.signDisplay = 'always';
219 stem = stem.slice(2);
220 }
221 else if (signDisplay === '+?') {
222 result.signDisplay = 'exceptZero';
223 stem = stem.slice(2);
224 }
225 if (!CONCISE_INTEGER_WIDTH_REGEX.test(stem)) {
226 throw new Error('Malformed concise eng/scientific notation');
227 }
228 result.minimumIntegerDigits = stem.length;
229 }
230 return result;
231}
232function parseNotationOptions(opt) {
233 var result = {};
234 var signOpts = parseSign(opt);
235 if (signOpts) {
236 return signOpts;
237 }
238 return result;
239}
240/**
241 * https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#skeleton-stems-and-options
242 */
243function parseNumberSkeleton(tokens) {
244 var result = {};
245 for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
246 var token = tokens_1[_i];
247 switch (token.stem) {
248 case 'percent':
249 case '%':
250 result.style = 'percent';
251 continue;
252 case '%x100':
253 result.style = 'percent';
254 result.scale = 100;
255 continue;
256 case 'currency':
257 result.style = 'currency';
258 result.currency = token.options[0];
259 continue;
260 case 'group-off':
261 case ',_':
262 result.useGrouping = false;
263 continue;
264 case 'precision-integer':
265 case '.':
266 result.maximumFractionDigits = 0;
267 continue;
268 case 'measure-unit':
269 case 'unit':
270 result.style = 'unit';
271 result.unit = icuUnitToEcma(token.options[0]);
272 continue;
273 case 'compact-short':
274 case 'K':
275 result.notation = 'compact';
276 result.compactDisplay = 'short';
277 continue;
278 case 'compact-long':
279 case 'KK':
280 result.notation = 'compact';
281 result.compactDisplay = 'long';
282 continue;
283 case 'scientific':
284 result = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, result), { notation: 'scientific' }), token.options.reduce(function (all, opt) { return (tslib_1.__assign(tslib_1.__assign({}, all), parseNotationOptions(opt))); }, {}));
285 continue;
286 case 'engineering':
287 result = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, result), { notation: 'engineering' }), token.options.reduce(function (all, opt) { return (tslib_1.__assign(tslib_1.__assign({}, all), parseNotationOptions(opt))); }, {}));
288 continue;
289 case 'notation-simple':
290 result.notation = 'standard';
291 continue;
292 // https://github.com/unicode-org/icu/blob/master/icu4c/source/i18n/unicode/unumberformatter.h
293 case 'unit-width-narrow':
294 result.currencyDisplay = 'narrowSymbol';
295 result.unitDisplay = 'narrow';
296 continue;
297 case 'unit-width-short':
298 result.currencyDisplay = 'code';
299 result.unitDisplay = 'short';
300 continue;
301 case 'unit-width-full-name':
302 result.currencyDisplay = 'name';
303 result.unitDisplay = 'long';
304 continue;
305 case 'unit-width-iso-code':
306 result.currencyDisplay = 'symbol';
307 continue;
308 case 'scale':
309 result.scale = parseFloat(token.options[0]);
310 continue;
311 // https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#integer-width
312 case 'integer-width':
313 if (token.options.length > 1) {
314 throw new RangeError('integer-width stems only accept a single optional option');
315 }
316 token.options[0].replace(INTEGER_WIDTH_REGEX, function (_, g1, g2, g3, g4, g5) {
317 if (g1) {
318 result.minimumIntegerDigits = g2.length;
319 }
320 else if (g3 && g4) {
321 throw new Error('We currently do not support maximum integer digits');
322 }
323 else if (g5) {
324 throw new Error('We currently do not support exact integer digits');
325 }
326 return '';
327 });
328 continue;
329 }
330 // https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#integer-width
331 if (CONCISE_INTEGER_WIDTH_REGEX.test(token.stem)) {
332 result.minimumIntegerDigits = token.stem.length;
333 continue;
334 }
335 if (FRACTION_PRECISION_REGEX.test(token.stem)) {
336 // Precision
337 // https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#fraction-precision
338 // precision-integer case
339 if (token.options.length > 1) {
340 throw new RangeError('Fraction-precision stems only accept a single optional option');
341 }
342 token.stem.replace(FRACTION_PRECISION_REGEX, function (_, g1, g2, g3, g4, g5) {
343 // .000* case (before ICU67 it was .000+)
344 if (g2 === '*') {
345 result.minimumFractionDigits = g1.length;
346 }
347 // .### case
348 else if (g3 && g3[0] === '#') {
349 result.maximumFractionDigits = g3.length;
350 }
351 // .00## case
352 else if (g4 && g5) {
353 result.minimumFractionDigits = g4.length;
354 result.maximumFractionDigits = g4.length + g5.length;
355 }
356 else {
357 result.minimumFractionDigits = g1.length;
358 result.maximumFractionDigits = g1.length;
359 }
360 return '';
361 });
362 if (token.options.length) {
363 result = tslib_1.__assign(tslib_1.__assign({}, result), parseSignificantPrecision(token.options[0]));
364 }
365 continue;
366 }
367 // https://unicode-org.github.io/icu/userguide/format_parse/numbers/skeletons.html#significant-digits-precision
368 if (SIGNIFICANT_PRECISION_REGEX.test(token.stem)) {
369 result = tslib_1.__assign(tslib_1.__assign({}, result), parseSignificantPrecision(token.stem));
370 continue;
371 }
372 var signOpts = parseSign(token.stem);
373 if (signOpts) {
374 result = tslib_1.__assign(tslib_1.__assign({}, result), signOpts);
375 }
376 var conciseScientificAndEngineeringOpts = parseConciseScientificAndEngineeringStem(token.stem);
377 if (conciseScientificAndEngineeringOpts) {
378 result = tslib_1.__assign(tslib_1.__assign({}, result), conciseScientificAndEngineeringOpts);
379 }
380 }
381 return result;
382}
383exports.parseNumberSkeleton = parseNumberSkeleton;