@aemforms/af-formatters 0.22.26 → 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.
package/lib/cjs/index.cjs CHANGED
@@ -1,32 +1,28 @@
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
+
1
21
  'use strict';
2
22
 
3
- /*************************************************************************
4
- * ADOBE CONFIDENTIAL
5
- * ___________________
6
- *
7
- * Copyright 2022 Adobe
8
- * All Rights Reserved.
9
- *
10
- * NOTICE: All information contained herein is, and remains
11
- * the property of Adobe and its suppliers, if any. The intellectual
12
- * and technical concepts contained herein are proprietary to Adobe
13
- * and its suppliers and are protected by all applicable intellectual
14
- * property laws, including trade secret and copyright laws.
15
- * Dissemination of this information or reproduction of this material
16
- * is strictly forbidden unless prior written permission is obtained
17
- * from Adobe.
18
- **************************************************************************/
19
- /**
20
- * https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
21
- * Credit: https://git.corp.adobe.com/dc/dfl/blob/master/src/patterns/parseDateTimeSkeleton.js
22
- * Created a separate library to be used elsewhere as well.
23
- */
24
23
  const DATE_TIME_REGEX =
25
- // eslint-disable-next-line max-len
26
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;
27
-
28
25
  const ShorthandStyles$1 = ["full", "long", "medium", "short"];
29
-
30
26
  function getSkeleton(skeleton, language) {
31
27
  if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
32
28
  const parsed = parseDateStyle(skeleton, language);
@@ -47,24 +43,13 @@ function getSkeleton(skeleton, language) {
47
43
  }
48
44
  return skeleton;
49
45
  }
50
-
51
- /**
52
- *
53
- * @param skeleton shorthand style for the date concatenated with shorthand style of time. The
54
- * Shorthand style for both date and time is one of ['full', 'long', 'medium', 'short'].
55
- * @param language {string} language to parse the date shorthand style
56
- * @returns {[*,string][]}
57
- */
58
46
  function parseDateStyle(skeleton, language) {
59
47
  const options = {};
60
- // the skeleton could have two keywords -- one for date, one for time
61
48
  const styles = skeleton.split(/\s/).filter(s => s.length);
62
49
  options.dateStyle = styles[0];
63
50
  if (styles.length > 1) options.timeStyle = styles[1];
64
-
65
51
  const testDate = new Date(2000, 2, 1, 2, 3, 4);
66
52
  const parts = new Intl.DateTimeFormat(language, options).formatToParts(testDate);
67
- // oddly, the formatted month name can be different from the standalone month name
68
53
  const formattedMarch = parts.find(p => p.type === 'month').value;
69
54
  const longMarch = new Intl.DateTimeFormat(language, {month: 'long'}).formatToParts(testDate)[0].value;
70
55
  const shortMarch = new Intl.DateTimeFormat(language, {month: 'short'}).formatToParts(testDate)[0].value;
@@ -88,11 +73,6 @@ function parseDateStyle(skeleton, language) {
88
73
  });
89
74
  return result;
90
75
  }
91
-
92
- /**
93
- * Parse Date time skeleton into Intl.DateTimeFormatOptions parts
94
- * Ref: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
95
- */
96
76
  function parseDateTimeSkeleton(skeleton, language) {
97
77
  if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
98
78
  return parseDateStyle(skeleton, language);
@@ -101,11 +81,9 @@ function parseDateTimeSkeleton(skeleton, language) {
101
81
  skeleton.replace(DATE_TIME_REGEX, match => {
102
82
  const len = match.length;
103
83
  switch (match[0]) {
104
- // Era
105
84
  case 'G':
106
85
  result.push(['era', len === 4 ? 'long' : len === 5 ? 'narrow' : 'short', len]);
107
86
  break;
108
- // Year
109
87
  case 'y':
110
88
  result.push(['year', len === 2 ? '2-digit' : 'numeric', len]);
111
89
  break;
@@ -116,16 +94,13 @@ function parseDateTimeSkeleton(skeleton, language) {
116
94
  throw new RangeError(
117
95
  '`Y/u/U/r` (year) patterns are not supported, use `y` instead'
118
96
  );
119
- // Quarter
120
97
  case 'q':
121
98
  case 'Q':
122
99
  throw new RangeError('`q/Q` (quarter) patterns are not supported');
123
- // Month
124
100
  case 'M':
125
101
  case 'L':
126
102
  result.push(['month', ['numeric', '2-digit', 'short', 'long', 'narrow'][len - 1], len]);
127
103
  break;
128
- // Week
129
104
  case 'w':
130
105
  case 'W':
131
106
  throw new RangeError('`w/W` (week) patterns are not supported');
@@ -138,7 +113,6 @@ function parseDateTimeSkeleton(skeleton, language) {
138
113
  throw new RangeError(
139
114
  '`D/F/g` (day) patterns are not supported, use `d` instead'
140
115
  );
141
- // Weekday
142
116
  case 'E':
143
117
  result.push(['weekday', ['short', 'short', 'short', 'long', 'narrow', 'narrow'][len - 1], len]);
144
118
  break;
@@ -154,16 +128,14 @@ function parseDateTimeSkeleton(skeleton, language) {
154
128
  }
155
129
  result.push(['weekday', ['short', 'long', 'narrow', 'short'][len - 3], len]);
156
130
  break;
157
- // Period
158
- case 'a': // AM, PM
131
+ case 'a':
159
132
  result.push(['hour12', true, 1]);
160
133
  break;
161
- case 'b': // am, pm, noon, midnight
162
- case 'B': // flexible day periods
134
+ case 'b':
135
+ case 'B':
163
136
  throw new RangeError(
164
137
  '`b/B` (period) patterns are not supported, use `a` instead'
165
138
  );
166
- // Hour
167
139
  case 'h':
168
140
  result.push(['hourCycle', 'h12']);
169
141
  result.push(['hour', ['numeric', '2-digit'][len - 1], len]);
@@ -186,11 +158,9 @@ function parseDateTimeSkeleton(skeleton, language) {
186
158
  throw new RangeError(
187
159
  '`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead'
188
160
  );
189
- // Minute
190
161
  case 'm':
191
162
  result.push(['minute', ['numeric', '2-digit'][len - 1], len]);
192
163
  break;
193
- // Second
194
164
  case 's':
195
165
  result.push(['second', ['numeric', '2-digit'][len - 1], len]);
196
166
  break;
@@ -201,23 +171,19 @@ function parseDateTimeSkeleton(skeleton, language) {
201
171
  throw new RangeError(
202
172
  '`S/A` (millisecond) patterns are not supported, use `s` instead'
203
173
  );
204
- // Zone
205
- case 'O': // timeZone GMT-8 or GMT-08:00
174
+ case 'O':
206
175
  result.push(['timeZoneName', len < 4 ? 'shortOffset' : 'longOffset', len]);
207
176
  result.push(['x-timeZoneName', len < 4 ? 'O' : 'OOOO', len]);
208
177
  break;
209
- case 'X': // 1, 2, 3, 4: The ISO8601 varios formats
210
- case 'x': // 1, 2, 3, 4: The ISO8601 varios formats
211
- case 'Z': // 1..3, 4, 5: The ISO8601 varios formats
212
- // Z, ZZ, ZZZ should produce -0800
213
- // ZZZZ should produce GMT-08:00
214
- // ZZZZZ should produce -8:00 or -07:52:58
178
+ case 'X':
179
+ case 'x':
180
+ case 'Z':
215
181
  result.push(['timeZoneName', 'longOffset', 1]);
216
182
  result.push(['x-timeZoneName', match, 1]);
217
183
  break;
218
- case 'z': // 1..3, 4: specific non-location format
219
- case 'v': // 1, 4: generic non-location format
220
- case 'V': // 1, 2, 3, 4: time zone ID or city
184
+ case 'z':
185
+ case 'v':
186
+ case 'V':
221
187
  throw new RangeError(
222
188
  'z/v/V` (timeZone) patterns are not supported, use `X/x/Z/O` instead'
223
189
  );
@@ -232,31 +198,7 @@ function parseDateTimeSkeleton(skeleton, language) {
232
198
  return result;
233
199
  }
234
200
 
235
- /*************************************************************************
236
- * ADOBE CONFIDENTIAL
237
- * ___________________
238
- *
239
- * Copyright 2022 Adobe
240
- * All Rights Reserved.
241
- *
242
- * NOTICE: All information contained herein is, and remains
243
- * the property of Adobe and its suppliers, if any. The intellectual
244
- * and technical concepts contained herein are proprietary to Adobe
245
- * and its suppliers and are protected by all applicable intellectual
246
- * property laws, including trade secret and copyright laws.
247
- * Dissemination of this information or reproduction of this material
248
- * is strictly forbidden unless prior written permission is obtained
249
- * from Adobe.
250
- **************************************************************************/
251
-
252
- // get the localized month names resulting from a given pattern
253
201
  const twelveMonths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(m => new Date(2000, m, 1));
254
-
255
- /**
256
- * returns the name of all the months for a given locale and given Date Format Settings
257
- * @param locale {string}
258
- * @param options {string} instance of Intl.DateTimeFormatOptions
259
- */
260
202
  function monthNames(locale, options) {
261
203
  return twelveMonths.map(month => {
262
204
  const parts = new Intl.DateTimeFormat(locale, options).formatToParts(month);
@@ -264,32 +206,17 @@ function monthNames(locale, options) {
264
206
  return m && m.value;
265
207
  });
266
208
  }
267
-
268
- /**
269
- * return an array of digits used by a given locale
270
- * @param locale {string}
271
- */
272
209
  function digitChars(locale) {
273
210
  return new Intl.NumberFormat(locale, {style:'decimal', useGrouping:false})
274
211
  .format(9876543210)
275
212
  .split('')
276
213
  .reverse();
277
214
  }
278
-
279
- /**
280
- * returns the calendar name used in a given locale
281
- * @param locale {string}
282
- */
283
215
  function calendarName(locale) {
284
216
  const parts = new Intl.DateTimeFormat(locale, {era:'short'}).formatToParts(new Date());
285
217
  const era = parts.find(p => p.type === 'era')?.value;
286
218
  return era === 'هـ' ? 'islamic' : 'gregory';
287
219
  }
288
-
289
- /**
290
- * returns the representation of the time of day for a given language
291
- * @param language {string}
292
- */
293
220
  function getDayPeriod(language) {
294
221
  const morning = new Date(2000, 1, 1, 1, 1, 1);
295
222
  const afternoon = new Date(2000, 1, 1, 16, 1, 1);
@@ -302,12 +229,6 @@ function getDayPeriod(language) {
302
229
  fn: (period, obj) => obj.hour += (period === pm.value) ? 12 : 0
303
230
  };
304
231
  }
305
-
306
- /**
307
- * get the offset in MS, given a date and timezone
308
- * @param dateObj {Date}
309
- * @param timeZone {string}
310
- */
311
232
  function offsetMS(dateObj, timeZone) {
312
233
  let tzOffset;
313
234
  try {
@@ -323,7 +244,6 @@ function offsetMS(dateObj, timeZone) {
323
244
  const result = ((nHours * 60) + nMinutes) * 60 * 1000;
324
245
  return sign === '-' ? - result : result;
325
246
  }
326
-
327
247
  function getTimezoneOffsetFrom(otherTimezone) {
328
248
  var date = new Date();
329
249
  function objFromStr(str) {
@@ -338,43 +258,22 @@ function getTimezoneOffsetFrom(otherTimezone) {
338
258
  var other = objFromStr(str);
339
259
  str = date.toLocaleString('en-US', { day: 'numeric', hour: 'numeric', minute: 'numeric', hourCycle: 'h23' });
340
260
  var myLocale = objFromStr(str);
341
- var otherOffset = (other.day * 24 * 60) + (other.hour * 60) + (other.minute); // utc date + otherTimezoneDifference
342
- var myLocaleOffset = (myLocale.day * 24 * 60) + (myLocale.hour * 60) + (myLocale.minute); // utc date + myTimeZoneDifference
343
- // (utc date + otherZoneDifference) - (utc date + myZoneDifference) - (-1 * myTimeZoneDifference)
261
+ var otherOffset = (other.day * 24 * 60) + (other.hour * 60) + (other.minute);
262
+ var myLocaleOffset = (myLocale.day * 24 * 60) + (myLocale.hour * 60) + (myLocale.minute);
344
263
  return otherOffset - myLocaleOffset - date.getTimezoneOffset();
345
264
  }
346
-
347
265
  function offsetMSFallback(dateObj, timezone) {
348
- //const defaultOffset = dateObj.getTimezoneOffset();
349
266
  const timezoneOffset = getTimezoneOffsetFrom(timezone);
350
267
  return timezoneOffset * 60 * 1000;
351
268
  }
352
-
353
- /**
354
- * adjust from the default JavaScript timezone to the default timezone
355
- * @param dateObj {Date}
356
- * @param timeZone {string}
357
- */
358
269
  function adjustTimeZone(dateObj, timeZone) {
359
270
  if (dateObj === null) return null;
360
- // const defaultOffset = new Intl.DateTimeFormat('en-US', { timeZoneName: 'longOffset'}).format(dateObj);
361
271
  let baseDate = dateObj.getTime() - dateObj.getTimezoneOffset() * 60 * 1000;
362
272
  const offset = offsetMS(dateObj, timeZone);
363
273
  offsetMSFallback(dateObj, timeZone);
364
274
  baseDate += - offset;
365
-
366
-
367
- // get the offset for the default JS environment
368
- // return days since the epoch
369
275
  return new Date(baseDate);
370
276
  }
371
-
372
- /**
373
- * in some cases, DateTimeFormat doesn't respect the 'numeric' vs. '2-digit' setting
374
- * for time values. The function corrects that
375
- * @param formattedParts instance of Intl.DateTimeFormatPart[]
376
- * @param parsed
377
- */
378
277
  function fixDigits(formattedParts, parsed) {
379
278
  ['hour', 'minute', 'second'].forEach(type => {
380
279
  const defn = formattedParts.find(f => f.type === type);
@@ -384,55 +283,29 @@ function fixDigits(formattedParts, parsed) {
384
283
  if (fmt === 'numeric' && defn.value.length === 2 && defn.value.charAt(0) === '0') defn.value = defn.value.slice(1);
385
284
  });
386
285
  }
387
-
388
286
  function fixYear(formattedParts, parsed) {
389
- // two digit years are handled differently in DateTimeFormat. 00 becomes 1900
390
- // providing a two digit year 0010 gets formatted to 10 and when parsed becomes 1910
391
- // Hence we need to pad the year with 0 as required by the skeleton and mentioned in
392
- // unicode. https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-year
393
287
  const defn = formattedParts.find(f => f.type === 'year');
394
288
  if (!defn) return;
395
- // eslint-disable-next-line no-unused-vars
396
289
  const chars = parsed.find(pair => pair[0] === 'year')[2];
397
290
  while(defn.value.length < chars) {
398
291
  defn.value = `0${defn.value}`;
399
292
  }
400
293
  }
401
-
402
- /**
403
- *
404
- * @param dateValue {Date}
405
- * @param language {string}
406
- * @param skeleton {string}
407
- * @param timeZone {string}
408
- * @returns {T}
409
- */
410
294
  function formatDateToParts(dateValue, language, skeleton, timeZone) {
411
- // DateTimeFormat renames some of the options in its formatted output
412
- //@ts-ignore
413
295
  const mappings = key => ({
414
296
  hour12: 'dayPeriod',
415
297
  fractionalSecondDigits: 'fractionalSecond',
416
298
  })[key] || key;
417
-
418
- // produces an array of name/value pairs of skeleton parts
419
299
  const allParameters = parseDateTimeSkeleton(skeleton, language);
420
300
  allParameters.push(['timeZone', timeZone]);
421
-
422
301
  const parsed = allParameters.filter(p => !p[0].startsWith('x-'));
423
302
  const nonStandard = allParameters.filter(p => p[0].startsWith('x-'));
424
- // reduce to a set of options that can be used to format
425
303
  const options = Object.fromEntries(parsed);
426
304
  delete options.literal;
427
-
428
305
  const df = new Intl.DateTimeFormat(language, options);
429
- // formattedParts will have all the pieces we need for our date -- but not in the correct order
430
306
  const formattedParts = df.formatToParts(dateValue);
431
-
432
307
  fixDigits(formattedParts, allParameters);
433
308
  fixYear(formattedParts, parsed);
434
- // iterate through the original parsed components and use its ordering and literals,
435
- // and add the formatted pieces
436
309
  return parsed.reduce((result, cur) => {
437
310
  if (cur[0] === 'literal') result.push(cur);
438
311
  else {
@@ -442,37 +315,25 @@ function formatDateToParts(dateValue, language, skeleton, timeZone) {
442
315
  const category = tz[0];
443
316
  if (category === 'Z') {
444
317
  if (tz.length < 4) {
445
- // handle 'Z', 'ZZ', 'ZZZ' Time Zone: ISO8601 basic hms? / RFC 822
446
318
  v.value = v.value.replace(/(GMT|:)/g, '');
447
319
  if (v.value === '') v.value = '+0000';
448
320
  } else if (tz.length === 5) {
449
- // 'ZZZZZ' Time Zone: ISO8601 extended hms?
450
321
  if (v.value === 'GMT') v.value = 'Z';
451
322
  else v.value = v.value.replace(/GMT/, '');
452
323
  }
453
324
  }
454
325
  if (category === 'X' || category === 'x') {
455
326
  if (tz.length === 1) {
456
- // 'X' ISO8601 basic hm?, with Z for 0
457
- // -08, +0530, Z
458
- // 'x' ISO8601 basic hm?, without Z for 0
459
327
  v.value = v.value.replace(/(GMT|:(00)?)/g, '');
460
328
  }
461
329
  if (tz.length === 2) {
462
- // 'XX' ISO8601 basic hm, with Z
463
- // -0800, Z
464
- // 'xx' ISO8601 basic hm, without Z
465
330
  v.value = v.value.replace(/(GMT|:)/g, '');
466
331
  }
467
332
  if (tz.length === 3) {
468
- // 'XXX' ISO8601 extended hm, with Z
469
- // -08:00, Z
470
- // 'xxx' ISO8601 extended hm, without Z
471
333
  v.value = v.value.replace(/GMT/g, '');
472
334
  }
473
335
  if (category === 'X' && v.value === '') v.value = 'Z';
474
336
  } else if (tz === 'O') {
475
- // eliminate 'GMT', leading and trailing zeros
476
337
  v.value = v.value.replace(/GMT/g, '').replace(/0(\d+):/, '$1:').replace(/:00/, '');
477
338
  if (v.value === '') v.value = '+0';
478
339
  }
@@ -482,18 +343,12 @@ function formatDateToParts(dateValue, language, skeleton, timeZone) {
482
343
  return result;
483
344
  }, []);
484
345
  }
485
-
486
- /**
487
- *
488
- * @param dateValue {Date}
489
- * @param language {string}
490
- * @param skeleton {string}
491
- * @param timeZone {string}
492
- */
493
346
  function formatDate(dateValue, language, skeleton, timeZone) {
347
+ if (skeleton.startsWith('date|')) {
348
+ skeleton = skeleton.split('|')[1];
349
+ }
494
350
  if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
495
351
  const options = {timeZone};
496
- // the skeleton could have two keywords -- one for date, one for time
497
352
  const parts = skeleton.split(/\s/).filter(s => s.length);
498
353
  if (ShorthandStyles$1.indexOf(parts[0]) > -1) {
499
354
  options.dateStyle = parts[0];
@@ -506,17 +361,10 @@ function formatDate(dateValue, language, skeleton, timeZone) {
506
361
  const parts = formatDateToParts(dateValue, language, skeleton, timeZone);
507
362
  return parts.map(p => p[1]).join('');
508
363
  }
509
-
510
- /**
511
- *
512
- * @param dateString {string}
513
- * @param language {string}
514
- * @param skeleton {string}
515
- * @param timeZone {string}
516
- */
517
364
  function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
518
- // start by getting all the localized parts of a date/time picture:
519
- // digits, calendar name
365
+ if (skeleton.startsWith('date|')) {
366
+ skeleton = skeleton.split('|')[1];
367
+ }
520
368
  const lookups = [];
521
369
  const regexParts = [];
522
370
  const calendar = calendarName(language);
@@ -527,13 +375,11 @@ function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
527
375
  let hourCycle = 'h12';
528
376
  let _bUseUTC = bUseUTC;
529
377
  let _setFullYear = false;
530
- // functions to process the results of the regex match
531
378
  const isSeparator = str => str.length === 1 && ':-/.'.includes(str);
532
379
  const monthNumber = str => getNumber(str) - 1;
533
380
  const getNumber = str => str.split('').reduce((total, digit) => (total * 10) + digits.indexOf(digit), 0);
534
381
  const yearNumber = templateDigits => str => {
535
382
  let year = getNumber(str);
536
- //todo: align with AF
537
383
  year = year < 100 && templateDigits === 2 ? year + 2000 : year;
538
384
  if (calendar === 'islamic') year = Math.ceil(year * 0.97 + 622);
539
385
  if (templateDigits > 2 && year < 100) {
@@ -542,40 +388,28 @@ function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
542
388
  return year;
543
389
  };
544
390
  const monthLookup = list => month => list.indexOf(month);
545
-
546
391
  const parsed = parseDateTimeSkeleton(skeleton, language);
547
392
  const months = monthNames(language, Object.fromEntries(parsed));
548
- // build up a regex expression that identifies each option in the skeleton
549
- // We build two parallel structures:
550
- // 1. the regex expression that will extract parts of the date/time
551
- // 2. a lookup array that will convert the matched results into date/time values
552
393
  parsed.forEach(([option, value, len]) => {
553
- // use a generic regex pattern for all single-character separator literals.
554
- // Then we'll be forgiving when it comes to separators: / vs - vs : etc
555
394
  if (option === 'literal') {
556
395
  if (isSeparator(value)) regexParts.push(`[^${digits[0]}-${digits[9]}]`);
557
396
  else regexParts.push(value);
558
-
559
397
  } else if (option === 'month' && ['numeric', '2-digit'].includes(value)) {
560
398
  regexParts.push(twoDigit);
561
399
  lookups.push(['month', monthNumber]);
562
-
563
400
  } else if (option === 'month' && ['formatted', 'long', 'short', 'narrow'].includes(value)) {
564
401
  regexParts.push(`(${months.join('|')})`);
565
402
  lookups.push(['month', monthLookup(months)]);
566
-
567
403
  } else if (['day', 'minute', 'second'].includes(option)) {
568
404
  if (option === 'minute' || option === 'second') {
569
405
  _bUseUTC = false;
570
406
  }
571
407
  regexParts.push(twoDigit);
572
408
  lookups.push([option, getNumber]);
573
-
574
409
  } else if (option === 'fractionalSecondDigits') {
575
410
  _bUseUTC = false;
576
411
  regexParts.push(threeDigit);
577
412
  lookups.push([option, (v, obj) => obj.fractionalSecondDigits + getNumber(v)]);
578
-
579
413
  } else if (option === 'hour') {
580
414
  _bUseUTC = false;
581
415
  regexParts.push(twoDigit);
@@ -590,19 +424,15 @@ function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
590
424
  regexParts.push(dayPeriod.regex);
591
425
  lookups.push(['hour', dayPeriod.fn]);
592
426
  }
593
- // Any other part that we don't need, we'll just add a non-greedy consumption
594
427
  } else if (option === 'hourCycle') {
595
428
  _bUseUTC = false;
596
429
  hourCycle = value;
597
430
  } else if (option === 'x-timeZoneName') {
598
431
  _bUseUTC = false;
599
- // we handle only the GMT offset picture
600
432
  regexParts.push('(?:GMT|UTC|Z)?([+\\-−0-9]{0,3}:?[0-9]{0,2})');
601
433
  lookups.push([option, (v, obj) => {
602
434
  _bUseUTC = true;
603
- // v could be undefined if we're on GMT time
604
435
  if (!v) return;
605
- // replace the unicode minus, then extract hours [and minutes]
606
436
  const timeParts = v.replace(/−/, '-').match(/([+\-\d]{2,3}):?(\d{0,2})/);
607
437
  const hours = timeParts[1] * 1;
608
438
  obj.hour -= hours;
@@ -613,14 +443,11 @@ function parseDate(dateString, language, skeleton, timeZone, bUseUTC = false) {
613
443
  _bUseUTC = false;
614
444
  regexParts.push('.+?');
615
445
  }
616
-
617
446
  return regexParts;
618
447
  }, []);
619
448
  const regex = new RegExp(regexParts.join(''));
620
449
  const match = dateString.match(regex);
621
450
  if (match === null) return dateString;
622
-
623
- // now loop through all the matched pieces and build up an object we'll use to create a Date object
624
451
  const dateObj = {year: 1972, month: 0, day: 1, hour: 0, minute: 0, second: 0, fractionalSecondDigits: 0};
625
452
  match.slice(1).forEach((m, index) => {
626
453
  const [element, func] = lookups[index];
@@ -680,9 +507,7 @@ const currencies = {
680
507
  'ru-RU': 'RUB',
681
508
  'tr-TR': 'TRY'
682
509
  };
683
-
684
510
  const locales = Object.keys(currencies);
685
-
686
511
  const getCurrency = function (locale) {
687
512
  if (locales.indexOf(locale) > -1) {
688
513
  return currencies[locale]
@@ -696,8 +521,7 @@ const getCurrency = function (locale) {
696
521
  };
697
522
 
698
523
  const NUMBER_REGEX =
699
- // eslint-disable-next-line max-len
700
- /(?:[#]+|[@]+(?:#+)?|[0]+|[,]|[.]|[-]|[+]|[%]|[¤]{1,4}(?:\/([a-zA-Z]{3}))?|[;]|[K]{1,2}|E{1,2}[+]?|'(?:[^']|'')*')|[^a-zA-Z']+/g;
524
+ /(?:[#]+|[@]+(#+)?|[0]+|[,]|[.]|[-]|[+]|[%]|[¤]{1,4}(?:\/([a-zA-Z]{3}))?|[;]|[K]{1,2}|E{1,2}[+]?|'(?:[^']|'')*')|[^a-zA-Z']+/g;
701
525
  const supportedUnits = ['acre', 'bit', 'byte', 'celsius', 'centimeter', 'day',
702
526
  'degree', 'fahrenheit', 'fluid-ounce', 'foot', 'gallon', 'gigabit',
703
527
  'gigabyte', 'gram', 'hectare', 'hour', 'inch', 'kilobit', 'kilobyte',
@@ -705,8 +529,6 @@ const supportedUnits = ['acre', 'bit', 'byte', 'celsius', 'centimeter', 'day',
705
529
  'mile-scandinavian', 'milliliter', 'millimeter', 'millisecond', 'minute', 'month',
706
530
  'ounce', 'percent', 'petabyte', 'pound', 'second', 'stone', 'terabit', 'terabyte', 'week', 'yard', 'year'].join('|');
707
531
  const ShorthandStyles = [/^currency(?:\/([a-zA-Z]{3}))?$/, /^decimal$/, /^integer$/, /^percent$/, new RegExp(`^unit\/(${supportedUnits})$`)];
708
-
709
-
710
532
  function parseNumberSkeleton(skeleton, language) {
711
533
  const options = {};
712
534
  const order = [];
@@ -735,6 +557,7 @@ function parseNumberSkeleton(skeleton, language) {
735
557
  break;
736
558
  case 4:
737
559
  options.style = 'percent';
560
+ options.maximumFractionDigits = 2;
738
561
  break;
739
562
  case 5:
740
563
  options.style = "unit";
@@ -751,7 +574,7 @@ function parseNumberSkeleton(skeleton, language) {
751
574
  options.minimumIntegerDigits = 1;
752
575
  options.maximumFractionDigits = 0;
753
576
  options.minimumFractionDigits = 0;
754
- skeleton.replace(NUMBER_REGEX, (match, offset) => {
577
+ skeleton.replace(NUMBER_REGEX, (match, maxSignificantDigits, currencySymbol, offset) => {
755
578
  const len = match.length;
756
579
  switch(match[0]) {
757
580
  case '#':
@@ -764,10 +587,10 @@ function parseNumberSkeleton(skeleton, language) {
764
587
  if (options?.minimumSignificantDigits) {
765
588
  throw "@ symbol should occur together"
766
589
  }
767
- order.push(['@', len]);
768
- options.minimumSignificantDigits = len;
769
- const hashes = match.match(/#+/) || "";
770
- options.maximumSignificantDigits = len + hashes.length;
590
+ const hashes = maxSignificantDigits || "";
591
+ order.push(['@', len - hashes.length]);
592
+ options.minimumSignificantDigits = len - hashes.length;
593
+ options.maximumSignificantDigits = len;
771
594
  order.push(['digit', hashes.length]);
772
595
  break;
773
596
  case ',':
@@ -792,6 +615,9 @@ function parseNumberSkeleton(skeleton, language) {
792
615
  }
793
616
  if (options?.decimal === true) {
794
617
  options.minimumFractionDigits = len;
618
+ if (!options.maximumFractionDigits) {
619
+ options.maximumFractionDigits = len;
620
+ }
795
621
  } else {
796
622
  options.minimumIntegerDigits = len;
797
623
  }
@@ -815,18 +641,20 @@ function parseNumberSkeleton(skeleton, language) {
815
641
  case '¤':
816
642
  if (offset !== 0 && offset !== skeleton.length - 1) {
817
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]);
818
649
  }
819
- options.style = 'currency';
820
- options.currencyDisplay = ['symbol', 'code', 'name', 'narrowSymbol'][len -1];
821
- options.currency = getCurrency(language);
822
- order.push(['currency', len]);
823
650
  break;
824
651
  case '%':
825
652
  if (offset !== 0 && offset !== skeleton.length - 1) {
826
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';
827
657
  }
828
- order.push(['%', 1]);
829
- options.style = 'percent';
830
658
  break;
831
659
  case 'E':
832
660
  order.push(['E', len]);
@@ -843,34 +671,30 @@ function parseNumberSkeleton(skeleton, language) {
843
671
  }
844
672
 
845
673
  function formatNumber(numberValue, language, skeletn) {
674
+ if (skeletn.startsWith('num|')) {
675
+ skeletn = skel.split('|')[1];
676
+ }
846
677
  if (!skeletn) return numberValue
847
678
  language = language || "en";
848
679
  const {options, order} = parseNumberSkeleton(skeletn, language);
849
680
  return new Intl.NumberFormat(language, options).format(numberValue);
850
681
  }
851
-
852
682
  function getMetaInfo(language, skel) {
853
683
  const parts = {};
854
- // gather digits and radix symbol
855
684
  let options = new Intl.NumberFormat(language, {style:'decimal', useGrouping:false}).formatToParts(9876543210.1);
856
685
  parts.digits = options.find(p => p.type === 'integer').value.split('').reverse();
857
686
  parts.decimal = options.find(p => p.type === 'decimal').value;
858
-
859
- // extract type values from the parts
860
687
  const gather = type => {
861
688
  const find = options.find(p => p.type === type);
862
689
  if (find) parts[type] = find.value;
863
690
  };
864
- // now gather the localized parts that correspond to the provided skeleton.
865
691
  const parsed = parseNumberSkeleton(skel);
866
692
  const nf = new Intl.NumberFormat(language, parsed);
867
693
  options = nf.formatToParts(-987654321);
868
694
  gather('group');
869
695
  gather('minusSign');
870
696
  gather('percentSign');
871
- // it's possible to have multiple currency representations in a single value
872
697
  parts.currency = options.filter(p => p.type === 'currency').map(p => p.value);
873
- // collect all literals. Most likely a literal is an accounting bracket
874
698
  parts.literal = options.filter(p => p.type === 'literal').map(p => p.value);
875
699
  options = nf.formatToParts(987654321);
876
700
  gather('plusSign');
@@ -878,10 +702,11 @@ function getMetaInfo(language, skel) {
878
702
  gather('unit');
879
703
  return parts;
880
704
  }
881
-
882
705
  function parseNumber(numberString, language, skel) {
883
706
  try {
884
- // factor will be updated to reflect: negative, percent, exponent etc.
707
+ if (skel.startsWith('num|')) {
708
+ skel = skel.split('|')[1];
709
+ }
885
710
  let factor = 1;
886
711
  let number = numberString;
887
712
  const meta = getMetaInfo(language, skel);
@@ -921,7 +746,6 @@ const getCategory = function (skeleton) {
921
746
  const chkCategory = skeleton?.match(/^(?:(num|date)\|)?(.+)/);
922
747
  return [chkCategory?.[1], chkCategory?.[2]]
923
748
  };
924
-
925
749
  const format = function (value, locale, skeleton, timezone) {
926
750
  const [category, skelton] = getCategory(skeleton);
927
751
  switch (category) {
@@ -936,7 +760,6 @@ const format = function (value, locale, skeleton, timezone) {
936
760
  throw `unable to deduce the format. The skeleton should be date|<format> for date formats and num|<format> for numbers`
937
761
  }
938
762
  };
939
-
940
763
  const parse = function (value, locale, skeleton, timezone) {
941
764
  const [category, skelton] = getCategory(skeleton);
942
765
  switch (category) {