@aemforms/af-formatters 0.22.25 → 0.22.29

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.
@@ -0,0 +1,781 @@
1
+ /*************************************************************************
2
+ * ADOBE CONFIDENTIAL
3
+ * ___________________
4
+ *
5
+ * Copyright 2022 Adobe
6
+ * All Rights Reserved.
7
+ *
8
+ * NOTICE: All information contained herein is, and remains
9
+ * the property of Adobe and its suppliers, if any. The intellectual
10
+ * and technical concepts contained herein are proprietary to Adobe
11
+ * and its suppliers and are protected by all applicable intellectual
12
+ * property laws, including trade secret and copyright laws.
13
+ * Dissemination of this information or reproduction of this material
14
+ * is strictly forbidden unless prior written permission is obtained
15
+ * from Adobe.
16
+
17
+ * Adobe permits you to use and modify this file solely in accordance with
18
+ * the terms of the Adobe license agreement accompanying it.
19
+ *************************************************************************/
20
+
21
+ 'use strict';
22
+
23
+ const DATE_TIME_REGEX =
24
+ /(?:[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}|[zZOvV]{1,5}|[zZOvVxX]{1,3}|S{1,3}|'(?:[^']|'')*')|[^a-zA-Z']+/g;
25
+ const ShorthandStyles$1 = ["full", "long", "medium", "short"];
26
+ function getSkeleton(skeleton, language) {
27
+ if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
28
+ const parsed = parseDateStyle(skeleton, language);
29
+ const result = [];
30
+ const symbols = {
31
+ month : 'M',
32
+ year : 'Y',
33
+ day : 'd'
34
+ };
35
+ parsed.forEach(([type, option, length]) => {
36
+ if (type in symbols) {
37
+ result.push(Array(length).fill(symbols[type]).join(''));
38
+ } else if (type === 'literal') {
39
+ result.push(option);
40
+ }
41
+ });
42
+ return result.join('');
43
+ }
44
+ return skeleton;
45
+ }
46
+ function parseDateStyle(skeleton, language) {
47
+ const options = {};
48
+ const styles = skeleton.split(/\s/).filter(s => s.length);
49
+ options.dateStyle = styles[0];
50
+ if (styles.length > 1) options.timeStyle = styles[1];
51
+ const testDate = new Date(2000, 2, 1, 2, 3, 4);
52
+ const parts = new Intl.DateTimeFormat(language, options).formatToParts(testDate);
53
+ const formattedMarch = parts.find(p => p.type === 'month').value;
54
+ const longMarch = new Intl.DateTimeFormat(language, {month: 'long'}).formatToParts(testDate)[0].value;
55
+ const shortMarch = new Intl.DateTimeFormat(language, {month: 'short'}).formatToParts(testDate)[0].value;
56
+ const result = [];
57
+ parts.forEach(({type, value}) => {
58
+ let option;
59
+ if (type === 'month') {
60
+ option = {
61
+ [formattedMarch]: skeleton === 'medium' ? 'short' : 'long',
62
+ [longMarch]: 'long',
63
+ [shortMarch]: 'short',
64
+ '03': '2-digit',
65
+ '3': 'numeric'
66
+ }[value];
67
+ }
68
+ if (type === 'year') option = {'2000': 'numeric', '00': '2-digit'}[value];
69
+ if (['day', 'hour', 'minute', 'second'].includes(type)) option = value.length === 2 ? '2-digit' : 'numeric';
70
+ if (type === 'literal') option = value;
71
+ if (type === 'dayPeriod') option = 'short';
72
+ result.push([type, option, value.length]);
73
+ });
74
+ return result;
75
+ }
76
+ function parseDateTimeSkeleton(skeleton, language) {
77
+ if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
78
+ return parseDateStyle(skeleton, language);
79
+ }
80
+ const result = [];
81
+ skeleton.replace(DATE_TIME_REGEX, match => {
82
+ const len = match.length;
83
+ switch (match[0]) {
84
+ case 'G':
85
+ result.push(['era', len === 4 ? 'long' : len === 5 ? 'narrow' : 'short', len]);
86
+ break;
87
+ case 'y':
88
+ result.push(['year', len === 2 ? '2-digit' : 'numeric', len]);
89
+ break;
90
+ case 'Y':
91
+ case 'u':
92
+ case 'U':
93
+ case 'r':
94
+ throw new RangeError(
95
+ '`Y/u/U/r` (year) patterns are not supported, use `y` instead'
96
+ );
97
+ case 'q':
98
+ case 'Q':
99
+ throw new RangeError('`q/Q` (quarter) patterns are not supported');
100
+ case 'M':
101
+ case 'L':
102
+ result.push(['month', ['numeric', '2-digit', 'short', 'long', 'narrow'][len - 1], len]);
103
+ break;
104
+ case 'w':
105
+ case 'W':
106
+ throw new RangeError('`w/W` (week) patterns are not supported');
107
+ case 'd':
108
+ result.push(['day', ['numeric', '2-digit'][len - 1], len]);
109
+ break;
110
+ case 'D':
111
+ case 'F':
112
+ case 'g':
113
+ throw new RangeError(
114
+ '`D/F/g` (day) patterns are not supported, use `d` instead'
115
+ );
116
+ case 'E':
117
+ result.push(['weekday', ['short', 'short', 'short', 'long', 'narrow', 'narrow'][len - 1], len]);
118
+ break;
119
+ case 'e':
120
+ if (len < 4) {
121
+ throw new RangeError('`e..eee` (weekday) patterns are not supported');
122
+ }
123
+ result.push(['weekday', ['short', 'long', 'narrow', 'short'][len - 4], len]);
124
+ break;
125
+ case 'c':
126
+ if (len < 3 || len > 5) {
127
+ throw new RangeError('`c, cc, cccccc` (weekday) patterns are not supported');
128
+ }
129
+ result.push(['weekday', ['short', 'long', 'narrow', 'short'][len - 3], len]);
130
+ break;
131
+ case 'a':
132
+ result.push(['hour12', true, 1]);
133
+ break;
134
+ case 'b':
135
+ case 'B':
136
+ throw new RangeError(
137
+ '`b/B` (period) patterns are not supported, use `a` instead'
138
+ );
139
+ case 'h':
140
+ result.push(['hourCycle', 'h12']);
141
+ result.push(['hour', ['numeric', '2-digit'][len - 1], len]);
142
+ break;
143
+ case 'H':
144
+ result.push(['hourCycle', 'h23', 1]);
145
+ result.push(['hour', ['numeric', '2-digit'][len - 1], len]);
146
+ break;
147
+ case 'K':
148
+ result.push(['hourCycle', 'h11', 1]);
149
+ result.push(['hour', ['numeric', '2-digit'][len - 1], len]);
150
+ break;
151
+ case 'k':
152
+ result.push(['hourCycle', 'h24', 1]);
153
+ result.push(['hour', ['numeric', '2-digit'][len - 1], len]);
154
+ break;
155
+ case 'j':
156
+ case 'J':
157
+ case 'C':
158
+ throw new RangeError(
159
+ '`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead'
160
+ );
161
+ case 'm':
162
+ result.push(['minute', ['numeric', '2-digit'][len - 1], len]);
163
+ break;
164
+ case 's':
165
+ result.push(['second', ['numeric', '2-digit'][len - 1], len]);
166
+ break;
167
+ case 'S':
168
+ result.push(['fractionalSecondDigits', len, len]);
169
+ break;
170
+ case 'A':
171
+ throw new RangeError(
172
+ '`S/A` (millisecond) patterns are not supported, use `s` instead'
173
+ );
174
+ case 'O':
175
+ result.push(['timeZoneName', len < 4 ? 'shortOffset' : 'longOffset', len]);
176
+ result.push(['x-timeZoneName', len < 4 ? 'O' : 'OOOO', len]);
177
+ break;
178
+ case 'X':
179
+ case 'x':
180
+ case 'Z':
181
+ result.push(['timeZoneName', 'longOffset', 1]);
182
+ result.push(['x-timeZoneName', match, 1]);
183
+ break;
184
+ case 'z':
185
+ case 'v':
186
+ case 'V':
187
+ throw new RangeError(
188
+ 'z/v/V` (timeZone) patterns are not supported, use `X/x/Z/O` instead'
189
+ );
190
+ case '\'':
191
+ result.push(['literal', match.slice(1, -1).replace(/''/g, '\''), -1]);
192
+ break;
193
+ default:
194
+ result.push(['literal', match, -1]);
195
+ }
196
+ return '';
197
+ });
198
+ return result;
199
+ }
200
+
201
+ const twelveMonths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(m => new Date(2000, m, 1));
202
+ function monthNames(locale, options) {
203
+ return twelveMonths.map(month => {
204
+ const parts = new Intl.DateTimeFormat(locale, options).formatToParts(month);
205
+ const m = parts.find(p => p.type === 'month');
206
+ return m && m.value;
207
+ });
208
+ }
209
+ function digitChars(locale) {
210
+ return new Intl.NumberFormat(locale, {style:'decimal', useGrouping:false})
211
+ .format(9876543210)
212
+ .split('')
213
+ .reverse();
214
+ }
215
+ function calendarName(locale) {
216
+ const parts = new Intl.DateTimeFormat(locale, {era:'short'}).formatToParts(new Date());
217
+ const era = parts.find(p => p.type === 'era')?.value;
218
+ return era === 'هـ' ? 'islamic' : 'gregory';
219
+ }
220
+ function getDayPeriod(language) {
221
+ const morning = new Date(2000, 1, 1, 1, 1, 1);
222
+ const afternoon = new Date(2000, 1, 1, 16, 1, 1);
223
+ const df = new Intl.DateTimeFormat(language, {dateStyle: 'full', timeStyle: 'full'});
224
+ const am = df.formatToParts(morning).find(p => p.type === 'dayPeriod');
225
+ const pm = df.formatToParts(afternoon).find(p => p.type === 'dayPeriod');
226
+ if (!am || !pm) return null;
227
+ return {
228
+ regex: `(${am.value}|${pm.value})`,
229
+ fn: (period, obj) => obj.hour += (period === pm.value) ? 12 : 0
230
+ };
231
+ }
232
+ function offsetMS(dateObj, timeZone) {
233
+ let tzOffset;
234
+ try {
235
+ tzOffset = new Intl.DateTimeFormat('en-US', {timeZone, timeZoneName: 'longOffset'}).format(dateObj);
236
+ } catch(e) {
237
+ return offsetMSFallback(dateObj, timeZone);
238
+ }
239
+ const offset = /GMT([+\-−])?(\d{1,2}):?(\d{0,2})?/.exec(tzOffset);
240
+ if (!offset) return 0;
241
+ const [sign, hours, minutes] = offset.slice(1);
242
+ const nHours = isNaN(parseInt(hours)) ? 0 : parseInt(hours);
243
+ const nMinutes = isNaN(parseInt(minutes)) ? 0 : parseInt(minutes);
244
+ const result = ((nHours * 60) + nMinutes) * 60 * 1000;
245
+ return sign === '-' ? - result : result;
246
+ }
247
+ function getTimezoneOffsetFrom(otherTimezone) {
248
+ var date = new Date();
249
+ function objFromStr(str) {
250
+ var array = str.replace(":", " ").split(" ");
251
+ return {
252
+ day: parseInt(array[0]),
253
+ hour: parseInt(array[1]),
254
+ minute: parseInt(array[2])
255
+ };
256
+ }
257
+ var str = date.toLocaleString('en-US', { timeZone: otherTimezone, day: 'numeric', hour: 'numeric', minute: 'numeric', hourCycle: 'h23' });
258
+ var other = objFromStr(str);
259
+ str = date.toLocaleString('en-US', { day: 'numeric', hour: 'numeric', minute: 'numeric', hourCycle: 'h23' });
260
+ var myLocale = objFromStr(str);
261
+ var otherOffset = (other.day * 24 * 60) + (other.hour * 60) + (other.minute);
262
+ var myLocaleOffset = (myLocale.day * 24 * 60) + (myLocale.hour * 60) + (myLocale.minute);
263
+ return otherOffset - myLocaleOffset - date.getTimezoneOffset();
264
+ }
265
+ function offsetMSFallback(dateObj, timezone) {
266
+ const timezoneOffset = getTimezoneOffsetFrom(timezone);
267
+ return timezoneOffset * 60 * 1000;
268
+ }
269
+ function adjustTimeZone(dateObj, timeZone) {
270
+ if (dateObj === null) return null;
271
+ let baseDate = dateObj.getTime() - dateObj.getTimezoneOffset() * 60 * 1000;
272
+ const offset = offsetMS(dateObj, timeZone);
273
+ offsetMSFallback(dateObj, timeZone);
274
+ baseDate += - offset;
275
+ return new Date(baseDate);
276
+ }
277
+ function fixDigits(formattedParts, parsed) {
278
+ ['hour', 'minute', 'second'].forEach(type => {
279
+ const defn = formattedParts.find(f => f.type === type);
280
+ if (!defn) return;
281
+ const fmt = parsed.find(pair => pair[0] === type)[1];
282
+ if (fmt === '2-digit' && defn.value.length === 1) defn.value = `0${defn.value}`;
283
+ if (fmt === 'numeric' && defn.value.length === 2 && defn.value.charAt(0) === '0') defn.value = defn.value.slice(1);
284
+ });
285
+ }
286
+ function fixYear(formattedParts, parsed) {
287
+ const defn = formattedParts.find(f => f.type === 'year');
288
+ if (!defn) return;
289
+ const chars = parsed.find(pair => pair[0] === 'year')[2];
290
+ while(defn.value.length < chars) {
291
+ defn.value = `0${defn.value}`;
292
+ }
293
+ }
294
+ function formatDateToParts(dateValue, language, skeleton, timeZone) {
295
+ const mappings = key => ({
296
+ hour12: 'dayPeriod',
297
+ fractionalSecondDigits: 'fractionalSecond',
298
+ })[key] || key;
299
+ const allParameters = parseDateTimeSkeleton(skeleton, language);
300
+ allParameters.push(['timeZone', timeZone]);
301
+ const parsed = allParameters.filter(p => !p[0].startsWith('x-'));
302
+ const nonStandard = allParameters.filter(p => p[0].startsWith('x-'));
303
+ const options = Object.fromEntries(parsed);
304
+ delete options.literal;
305
+ const df = new Intl.DateTimeFormat(language, options);
306
+ const formattedParts = df.formatToParts(dateValue);
307
+ fixDigits(formattedParts, allParameters);
308
+ fixYear(formattedParts, parsed);
309
+ return parsed.reduce((result, cur) => {
310
+ if (cur[0] === 'literal') result.push(cur);
311
+ else {
312
+ const v = formattedParts.find(p => p.type === mappings(cur[0]));
313
+ if (v && v.type === 'timeZoneName') {
314
+ const tz = nonStandard.find(p => p[0] === 'x-timeZoneName')[1];
315
+ const category = tz[0];
316
+ if (category === 'Z') {
317
+ if (tz.length < 4) {
318
+ v.value = v.value.replace(/(GMT|:)/g, '');
319
+ if (v.value === '') v.value = '+0000';
320
+ } else if (tz.length === 5) {
321
+ if (v.value === 'GMT') v.value = 'Z';
322
+ else v.value = v.value.replace(/GMT/, '');
323
+ }
324
+ }
325
+ if (category === 'X' || category === 'x') {
326
+ if (tz.length === 1) {
327
+ v.value = v.value.replace(/(GMT|:(00)?)/g, '');
328
+ }
329
+ if (tz.length === 2) {
330
+ v.value = v.value.replace(/(GMT|:)/g, '');
331
+ }
332
+ if (tz.length === 3) {
333
+ v.value = v.value.replace(/GMT/g, '');
334
+ }
335
+ if (category === 'X' && v.value === '') v.value = 'Z';
336
+ } else if (tz === 'O') {
337
+ v.value = v.value.replace(/GMT/g, '').replace(/0(\d+):/, '$1:').replace(/:00/, '');
338
+ if (v.value === '') v.value = '+0';
339
+ }
340
+ }
341
+ if (v) result.push([v.type, v.value]);
342
+ }
343
+ return result;
344
+ }, []);
345
+ }
346
+ function formatDate(dateValue, language, skeleton, timeZone) {
347
+ if (skeleton.startsWith('date|')) {
348
+ skeleton = skeleton.split('|')[1];
349
+ }
350
+ if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
351
+ const options = {timeZone};
352
+ const parts = skeleton.split(/\s/).filter(s => s.length);
353
+ if (ShorthandStyles$1.indexOf(parts[0]) > -1) {
354
+ options.dateStyle = parts[0];
355
+ }
356
+ if (parts.length > 1 && ShorthandStyles$1.indexOf(parts[1]) > -1) {
357
+ options.timeStyle = parts[1];
358
+ }
359
+ return new Intl.DateTimeFormat(language, options).format(dateValue);
360
+ }
361
+ const parts = formatDateToParts(dateValue, language, skeleton, timeZone);
362
+ return parts.map(p => p[1]).join('');
363
+ }
364
+ function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
365
+ if (skeleton.startsWith('date|')) {
366
+ skeleton = skeleton.split('|')[1];
367
+ }
368
+ const lookups = [];
369
+ const regexParts = [];
370
+ const calendar = calendarName(language);
371
+ const digits = digitChars(language);
372
+ const twoDigit = `([${digits[0]}-${digits[9]}]{1,2})`;
373
+ const threeDigit = `([${digits[0]}-${digits[9]}]{1,3})`;
374
+ const fourDigit = `([${digits[0]}-${digits[9]}]{1,4})`;
375
+ let hourCycle = 'h12';
376
+ let _bUseUTC = bUseUTC;
377
+ let _setFullYear = false;
378
+ const isSeparator = str => str.length === 1 && ':-/.'.includes(str);
379
+ const monthNumber = str => getNumber(str) - 1;
380
+ const getNumber = str => str.split('').reduce((total, digit) => (total * 10) + digits.indexOf(digit), 0);
381
+ const yearNumber = templateDigits => str => {
382
+ let year = getNumber(str);
383
+ year = year < 100 && templateDigits === 2 ? year + 2000 : year;
384
+ if (calendar === 'islamic') year = Math.ceil(year * 0.97 + 622);
385
+ if (templateDigits > 2 && year < 100) {
386
+ _setFullYear = true;
387
+ }
388
+ return year;
389
+ };
390
+ const monthLookup = list => month => list.indexOf(month);
391
+ const parsed = parseDateTimeSkeleton(skeleton, language);
392
+ const months = monthNames(language, Object.fromEntries(parsed));
393
+ parsed.forEach(([option, value, len]) => {
394
+ if (option === 'literal') {
395
+ if (isSeparator(value)) regexParts.push(`[^${digits[0]}-${digits[9]}]`);
396
+ else regexParts.push(value);
397
+ } else if (option === 'month' && ['numeric', '2-digit'].includes(value)) {
398
+ regexParts.push(twoDigit);
399
+ lookups.push(['month', monthNumber]);
400
+ } else if (option === 'month' && ['formatted', 'long', 'short', 'narrow'].includes(value)) {
401
+ regexParts.push(`(${months.join('|')})`);
402
+ lookups.push(['month', monthLookup(months)]);
403
+ } else if (['day', 'minute', 'second'].includes(option)) {
404
+ if (option === 'minute' || option === 'second') {
405
+ _bUseUTC = false;
406
+ }
407
+ regexParts.push(twoDigit);
408
+ lookups.push([option, getNumber]);
409
+ } else if (option === 'fractionalSecondDigits') {
410
+ _bUseUTC = false;
411
+ regexParts.push(threeDigit);
412
+ lookups.push([option, (v, obj) => obj.fractionalSecondDigits + getNumber(v)]);
413
+ } else if (option === 'hour') {
414
+ _bUseUTC = false;
415
+ regexParts.push(twoDigit);
416
+ lookups.push([option, (v, obj) => obj.hour + getNumber(v)]);
417
+ } else if (option === 'year') {
418
+ regexParts.push('numeric' === value ? fourDigit : twoDigit);
419
+ lookups.push(['year', yearNumber(len)]);
420
+ } else if (option === 'dayPeriod') {
421
+ _bUseUTC = false;
422
+ const dayPeriod = getDayPeriod(language);
423
+ if (dayPeriod) {
424
+ regexParts.push(dayPeriod.regex);
425
+ lookups.push(['hour', dayPeriod.fn]);
426
+ }
427
+ } else if (option === 'hourCycle') {
428
+ _bUseUTC = false;
429
+ hourCycle = value;
430
+ } else if (option === 'x-timeZoneName') {
431
+ _bUseUTC = false;
432
+ regexParts.push('(?:GMT|UTC|Z)?([+\\-−0-9]{0,3}:?[0-9]{0,2})');
433
+ lookups.push([option, (v, obj) => {
434
+ _bUseUTC = true;
435
+ if (!v) return;
436
+ const timeParts = v.replace(/−/, '-').match(/([+\-\d]{2,3}):?(\d{0,2})/);
437
+ const hours = timeParts[1] * 1;
438
+ obj.hour -= hours;
439
+ const mins = timeParts.length > 2 ? timeParts[2] * 1 : 0;
440
+ obj.minute -= (hours < 0) ? - mins : mins;
441
+ }]);
442
+ } else if (option !== 'timeZoneName') {
443
+ _bUseUTC = false;
444
+ regexParts.push('.+?');
445
+ }
446
+ return regexParts;
447
+ }, []);
448
+ const regex = new RegExp(regexParts.join(''));
449
+ const match = dateString.match(regex);
450
+ if (match === null) return dateString;
451
+ const dateObj = {year: 1972, month: 0, day: 1, hour: 0, minute: 0, second: 0, fractionalSecondDigits: 0};
452
+ match.slice(1).forEach((m, index) => {
453
+ const [element, func] = lookups[index];
454
+ dateObj[element] = func(m, dateObj);
455
+ });
456
+ if (hourCycle === 'h24' && dateObj.hour === 24) dateObj.hour = 0;
457
+ if (hourCycle === 'h12' && dateObj.hour === 12) dateObj.hour = 0;
458
+ if (_bUseUTC) {
459
+ const utcDate = new Date(Date.UTC(
460
+ dateObj.year,
461
+ dateObj.month,
462
+ dateObj.day,
463
+ dateObj.hour,
464
+ dateObj.minute,
465
+ dateObj.second,
466
+ dateObj.fractionalSecondDigits,
467
+ ));
468
+ if (_setFullYear) {
469
+ utcDate.setUTCFullYear(dateObj.year);
470
+ }
471
+ return utcDate;
472
+ }
473
+ const jsDate = new Date(
474
+ dateObj.year,
475
+ dateObj.month,
476
+ dateObj.day,
477
+ dateObj.hour,
478
+ dateObj.minute,
479
+ dateObj.second,
480
+ dateObj.fractionalSecondDigits,
481
+ );
482
+ if (_setFullYear) {
483
+ jsDate.setFullYear(dateObj.year);
484
+ }
485
+ return timeZone == null ? jsDate : adjustTimeZone(jsDate, timeZone);
486
+ }
487
+
488
+ const currencies = {
489
+ 'da-DK': 'DKK',
490
+ 'de-DE': 'EUR',
491
+ 'en-US': 'USD',
492
+ 'en-GB': 'GBP',
493
+ 'es-ES': 'EUR',
494
+ 'fi-FI': 'EUR',
495
+ 'fr-FR': 'EUR',
496
+ 'it-IT': 'EUR',
497
+ 'ja-JP': 'JPY',
498
+ 'nb-NO': 'NOK',
499
+ 'nl-NL': 'EUR',
500
+ 'pt-BR': 'BRL',
501
+ 'sv-SE': 'SEK',
502
+ 'zh-CN': 'CNY',
503
+ 'zh-TW': 'TWD',
504
+ 'ko-KR': 'KRW',
505
+ 'cs-CZ': 'CZK',
506
+ 'pl-PL': 'PLN',
507
+ 'ru-RU': 'RUB',
508
+ 'tr-TR': 'TRY'
509
+ };
510
+ const locales = Object.keys(currencies);
511
+ const getCurrency = function (locale) {
512
+ if (locales.indexOf(locale) > -1) {
513
+ return currencies[locale]
514
+ } else {
515
+ const matchingLocale = locales.find(x => x.startsWith(locale));
516
+ if (matchingLocale) {
517
+ return currencies[matchingLocale]
518
+ }
519
+ }
520
+ return ''
521
+ };
522
+
523
+ const NUMBER_REGEX =
524
+ /(?:[#]+|[@]+(#+)?|[0]+|[,]|[.]|[-]|[+]|[%]|[¤]{1,4}(?:\/([a-zA-Z]{3}))?|[;]|[K]{1,2}|E{1,2}[+]?|'(?:[^']|'')*')|[^a-zA-Z']+/g;
525
+ const supportedUnits = ['acre', 'bit', 'byte', 'celsius', 'centimeter', 'day',
526
+ 'degree', 'fahrenheit', 'fluid-ounce', 'foot', 'gallon', 'gigabit',
527
+ 'gigabyte', 'gram', 'hectare', 'hour', 'inch', 'kilobit', 'kilobyte',
528
+ 'kilogram', 'kilometer', 'liter', 'megabit', 'megabyte', 'meter', 'mile',
529
+ 'mile-scandinavian', 'milliliter', 'millimeter', 'millisecond', 'minute', 'month',
530
+ 'ounce', 'percent', 'petabyte', 'pound', 'second', 'stone', 'terabit', 'terabyte', 'week', 'yard', 'year'].join('|');
531
+ const ShorthandStyles = [/^currency(?:\/([a-zA-Z]{3}))?$/, /^decimal$/, /^integer$/, /^percent$/, new RegExp(`^unit\/(${supportedUnits})$`)];
532
+ function parseNumberSkeleton(skeleton, language) {
533
+ const options = {};
534
+ const order = [];
535
+ let match, index;
536
+ for (index = 0; index < ShorthandStyles.length && match == null; index++) {
537
+ match = ShorthandStyles[index].exec(skeleton);
538
+ }
539
+ if (match) {
540
+ switch(index) {
541
+ case 1:
542
+ options.style = 'currency';
543
+ options.currencyDisplay = 'narrowSymbol';
544
+ if (match[1]) {
545
+ options.currency = match[1];
546
+ } else {
547
+ options.currency = getCurrency(language);
548
+ }
549
+ break;
550
+ case 2:
551
+ new Intl.NumberFormat(language, {}).resolvedOptions();
552
+ options.minimumFractionDigits = options.minimumFractionDigits || 2;
553
+ break;
554
+ case 3:
555
+ options.minimumFractionDigits = 0;
556
+ options.maximumFractionDigits = 0;
557
+ break;
558
+ case 4:
559
+ options.style = 'percent';
560
+ options.maximumFractionDigits = 2;
561
+ break;
562
+ case 5:
563
+ options.style = "unit";
564
+ options.unitDisplay = "long";
565
+ options.unit = match[1];
566
+ break;
567
+ }
568
+ return {
569
+ options,
570
+ order
571
+ }
572
+ }
573
+ options.useGrouping = false;
574
+ options.minimumIntegerDigits = 1;
575
+ options.maximumFractionDigits = 0;
576
+ options.minimumFractionDigits = 0;
577
+ skeleton.replace(NUMBER_REGEX, (match, maxSignificantDigits, currencySymbol, offset) => {
578
+ const len = match.length;
579
+ switch(match[0]) {
580
+ case '#':
581
+ order.push(['digit', len]);
582
+ if (options?.decimal === true) {
583
+ options.maximumFractionDigits = options.minimumFractionDigits + len;
584
+ }
585
+ break;
586
+ case '@':
587
+ if (options?.minimumSignificantDigits) {
588
+ throw "@ symbol should occur together"
589
+ }
590
+ const hashes = maxSignificantDigits || "";
591
+ order.push(['@', len - hashes.length]);
592
+ options.minimumSignificantDigits = len - hashes.length;
593
+ options.maximumSignificantDigits = len;
594
+ order.push(['digit', hashes.length]);
595
+ break;
596
+ case ',':
597
+ if (options?.decimal === true) {
598
+ throw "grouping character not supporting for fractions"
599
+ }
600
+ order.push(['group', 1]);
601
+ options.useGrouping = 'auto';
602
+ break;
603
+ case '.':
604
+ if (options?.decimal) {
605
+ console.error("only one decimal symbol is allowed");
606
+ } else {
607
+ order.push(['decimal', 1]);
608
+ options.decimal = true;
609
+ }
610
+ break;
611
+ case '0':
612
+ order.push('0', len);
613
+ if(options.minimumSignificantDigits || options.maximumSignificantDigits) {
614
+ throw "0 is not supported with @"
615
+ }
616
+ if (options?.decimal === true) {
617
+ options.minimumFractionDigits = len;
618
+ if (!options.maximumFractionDigits) {
619
+ options.maximumFractionDigits = len;
620
+ }
621
+ } else {
622
+ options.minimumIntegerDigits = len;
623
+ }
624
+ break;
625
+ case '-':
626
+ if (offset !== 0) {
627
+ console.error("sign display is always in the beginning");
628
+ }
629
+ options.signDisplay = 'negative';
630
+ order.push(['signDisplay', 1, '-']);
631
+ break;
632
+ case '+':
633
+ if (offset !== 0 && order[order.length - 1][0] === 'E') {
634
+ console.error("sign display is always in the beginning");
635
+ }
636
+ if (offset === 0) {
637
+ options.signDisplay = 'always';
638
+ }
639
+ order.push(['signDisplay', 1, '+']);
640
+ break;
641
+ case '¤':
642
+ if (offset !== 0 && offset !== skeleton.length - 1) {
643
+ console.error("currency display should be either in the beginning or at the end");
644
+ } else {
645
+ options.style = 'currency';
646
+ options.currencyDisplay = ['symbol', 'code', 'name', 'narrowSymbol'][len - 1];
647
+ options.currency = currencySymbol || getCurrency(language);
648
+ order.push(['currency', len]);
649
+ }
650
+ break;
651
+ case '%':
652
+ if (offset !== 0 && offset !== skeleton.length - 1) {
653
+ console.error("percent display should be either in the beginning or at the end");
654
+ } else {
655
+ order.push(['%', 1]);
656
+ options.style = 'percent';
657
+ }
658
+ break;
659
+ case 'E':
660
+ order.push(['E', len]);
661
+ options.style = ['scientific','engineering'](len - 1);
662
+ break;
663
+ default:
664
+ console.error("unknown chars" + match);
665
+ }
666
+ });
667
+ return {
668
+ options,
669
+ order
670
+ };
671
+ }
672
+
673
+ function formatNumber(numberValue, language, skeletn) {
674
+ if (skeletn.startsWith('num|')) {
675
+ skeletn = skel.split('|')[1];
676
+ }
677
+ if (!skeletn) return numberValue
678
+ language = language || "en";
679
+ const {options, order} = parseNumberSkeleton(skeletn, language);
680
+ return new Intl.NumberFormat(language, options).format(numberValue);
681
+ }
682
+ function getMetaInfo(language, skel) {
683
+ const parts = {};
684
+ let options = new Intl.NumberFormat(language, {style:'decimal', useGrouping:false}).formatToParts(9876543210.1);
685
+ parts.digits = options.find(p => p.type === 'integer').value.split('').reverse();
686
+ parts.decimal = options.find(p => p.type === 'decimal').value;
687
+ const gather = type => {
688
+ const find = options.find(p => p.type === type);
689
+ if (find) parts[type] = find.value;
690
+ };
691
+ const parsed = parseNumberSkeleton(skel);
692
+ const nf = new Intl.NumberFormat(language, parsed);
693
+ options = nf.formatToParts(-987654321);
694
+ gather('group');
695
+ gather('minusSign');
696
+ gather('percentSign');
697
+ parts.currency = options.filter(p => p.type === 'currency').map(p => p.value);
698
+ parts.literal = options.filter(p => p.type === 'literal').map(p => p.value);
699
+ options = nf.formatToParts(987654321);
700
+ gather('plusSign');
701
+ gather('exponentSeparator');
702
+ gather('unit');
703
+ return parts;
704
+ }
705
+ function parseNumber(numberString, language, skel) {
706
+ try {
707
+ if (skel.startsWith('num|')) {
708
+ skel = skel.split('|')[1];
709
+ }
710
+ let factor = 1;
711
+ let number = numberString;
712
+ const meta = getMetaInfo(language, skel);
713
+ if (meta.group) number = number.replaceAll(meta.group, '');
714
+ number = number.replace(meta.decimal, '.');
715
+ if (meta.unit) number = number.replaceAll(meta.unit, '');
716
+ if (meta.minusSign && number.includes(meta.minusSign)) {
717
+ number = number.replace(meta.minusSign, '');
718
+ factor *= -1;
719
+ }
720
+ if (meta.percentSign && number.includes(meta.percentSign)) {
721
+ factor = factor/100;
722
+ number = number.replace(meta.percentSign, '');
723
+ }
724
+ meta.currency.forEach(currency => number = number.replace(currency, ''));
725
+ meta.literal.forEach(literal => {
726
+ if (number.includes(literal)) {
727
+ if (literal === '(') factor = factor * -1;
728
+ number = number.replace(literal, '');
729
+ }
730
+ });
731
+ if (meta.plusSign) number = number.replace(meta.plusSign, '');
732
+ if (meta.exponentSeparator) {
733
+ let e;
734
+ [number, e] = number.split(meta.exponentSeparator);
735
+ factor = factor * Math.pow(10, e);
736
+ }
737
+ const result = factor * number;
738
+ return isNaN(result) ? numberString : result;
739
+ } catch (e) {
740
+ console.dir(e);
741
+ return numberString;
742
+ }
743
+ }
744
+
745
+ const getCategory = function (skeleton) {
746
+ const chkCategory = skeleton?.match(/^(?:(num|date)\|)?(.+)/);
747
+ return [chkCategory?.[1], chkCategory?.[2]]
748
+ };
749
+ const format = function (value, locale, skeleton, timezone) {
750
+ const [category, skelton] = getCategory(skeleton);
751
+ switch (category) {
752
+ case 'date':
753
+ if (!(value instanceof Date)) {
754
+ value = new Date(value);
755
+ }
756
+ return formatDate(value, locale, skelton, timezone)
757
+ case 'num':
758
+ return formatNumber(value, locale, skelton)
759
+ default:
760
+ throw `unable to deduce the format. The skeleton should be date|<format> for date formats and num|<format> for numbers`
761
+ }
762
+ };
763
+ const parse = function (value, locale, skeleton, timezone) {
764
+ const [category, skelton] = getCategory(skeleton);
765
+ switch (category) {
766
+ case 'date':
767
+ return parseDate(value, locale, skelton, timezone)
768
+ case 'number':
769
+ return parseNumber(value, locale, skelton)
770
+ default:
771
+ throw `unable to deduce the format. The skeleton should be date|<format> for date formats and num|<format> for numbers`
772
+ }
773
+ };
774
+
775
+ exports.format = format;
776
+ exports.formatDate = formatDate;
777
+ exports.formatNumber = formatNumber;
778
+ exports.parse = parse;
779
+ exports.parseDate = parseDate;
780
+ exports.parseDateSkeleton = getSkeleton;
781
+ exports.parseNumber = parseNumber;