@aemforms/af-formatters 0.22.26 → 0.22.30
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 +62 -239
- package/lib/esm/date/DateParser.js +52 -189
- package/lib/esm/date/SkeletonParser.js +39 -100
- package/lib/esm/date/index.js +20 -18
- package/lib/esm/index.js +34 -13
- package/lib/esm/number/NumberParser.js +34 -15
- package/lib/esm/number/SkeletonParser.js +75 -74
- package/lib/esm/number/currencies.js +24 -6
- package/package.json +11 -1
- package/lib/browser/afb-formatters.js +0 -950
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
|
-
|
|
158
|
-
case 'a': // AM, PM
|
|
131
|
+
case 'a':
|
|
159
132
|
result.push(['hour12', true, 1]);
|
|
160
133
|
break;
|
|
161
|
-
case 'b':
|
|
162
|
-
case 'B':
|
|
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
|
-
|
|
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':
|
|
210
|
-
case 'x':
|
|
211
|
-
case 'Z':
|
|
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':
|
|
219
|
-
case 'v':
|
|
220
|
-
case 'V':
|
|
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);
|
|
342
|
-
var myLocaleOffset = (myLocale.day * 24 * 60) + (myLocale.hour * 60) + (myLocale.minute);
|
|
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
|
-
|
|
519
|
-
|
|
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
|
-
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
options.maximumSignificantDigits = len
|
|
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
|
-
|
|
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) {
|