@formatjs/icu-skeleton-parser 1.3.11 → 1.3.12

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