lightning-base-components 1.15.3-alpha → 1.15.4-alpha

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,1810 @@
1
+ import commonDigits from '@salesforce/i18n/common.digits';
2
+ import commonCalendarData from '@salesforce/i18n/common.calendarData';
3
+ import defaultCalendar from '@salesforce/i18n/defaultCalendar';
4
+ import defaultNumberingSystem from '@salesforce/i18n/defaultNumberingSystem';
5
+ import calendarData from '@salesforce/i18n/calendarData';
6
+ import decimalSeparator from '@salesforce/i18n/number.decimalSeparator';
7
+ import groupingSeparator from '@salesforce/i18n/number.groupingSeparator';
8
+ import percentSign from '@salesforce/i18n/number.percentSign';
9
+ import plusSign from '@salesforce/i18n/number.plusSign';
10
+ import minusSign from '@salesforce/i18n/number.minusSign';
11
+ import exponentialSign from '@salesforce/i18n/number.exponentialSign';
12
+ import superscriptingExponentSign from '@salesforce/i18n/number.superscriptingExponentSign';
13
+ import perMilleSign from '@salesforce/i18n/number.perMilleSign';
14
+ import infinity from '@salesforce/i18n/number.infinity';
15
+ import nan from '@salesforce/i18n/number.nan';
16
+ import currencySymbol from '@salesforce/i18n/number.currencySymbol';
17
+
18
+ /**
19
+ * Given an object instance, generate an object with the same properties, with property name ordered alphabetically, and undefined properties removed.
20
+ *
21
+ * @param value Object to process
22
+ * @returns Generated object with properties only
23
+ */
24
+ function extractProperties(value) {
25
+ // Return primitive value as is
26
+ const valueType = typeof value;
27
+ if (valueType === 'string' ||
28
+ valueType === 'number' ||
29
+ valueType === 'boolean' ||
30
+ valueType === 'undefined') {
31
+ return value;
32
+ }
33
+ if (valueType === 'object') {
34
+ // Array is an object
35
+ if (Array.isArray(value)) {
36
+ const newValue = [];
37
+ // Keep sort order
38
+ for (const arrayValue of value) {
39
+ newValue.push(extractProperties(arrayValue));
40
+ }
41
+ return newValue;
42
+ }
43
+ else {
44
+ const newValue = {};
45
+ // Normalize property order by sorting it before assignment
46
+ const propNames = Object.getOwnPropertyNames(value).sort();
47
+ for (const propName of propNames) {
48
+ const propValue = extractProperties(value[propName]);
49
+ // Normalize property value by assigning only defined values to properties
50
+ if (propValue !== undefined) {
51
+ newValue[propName] = propValue;
52
+ }
53
+ }
54
+ return newValue;
55
+ }
56
+ }
57
+ return undefined;
58
+ }
59
+ /**
60
+ * A Map-like object with normalized key
61
+ */
62
+ class Cache {
63
+ /**
64
+ * Constructs an empty instance.
65
+ *
66
+ * @constructor
67
+ */
68
+ constructor() {
69
+ /** Internal map object */
70
+ this.map = new Map();
71
+ }
72
+ /**
73
+ * Checks whether this cache contains provided key.
74
+ *
75
+ * @param key Cache key
76
+ * @returns 'true' if cache has this key
77
+ */
78
+ has(key) {
79
+ const newKey = this.normalize(key);
80
+ return this.map.has(newKey);
81
+ }
82
+ /**
83
+ * Returns the cached value for provided key, or undefined if key is unavailable
84
+ *
85
+ * @param key Cache key
86
+ * @returns Cache value
87
+ */
88
+ get(key) {
89
+ const newKey = this.normalize(key);
90
+ return this.map.get(newKey);
91
+ }
92
+ /**
93
+ * Sets provided value to provided cache key. Current cached value will be overwritten if cache key exists.
94
+ *
95
+ * @param key Cache key. Examples: {prop: 'prop value', nested: { nestedProp: 'nestedProp value' }}
96
+ * @param value Cache value
97
+ * @returns Current cache instance
98
+ */
99
+ set(key, value) {
100
+ const newKey = this.normalize(key);
101
+ this.map.set(newKey, value);
102
+ return this;
103
+ }
104
+ /**
105
+ * Removes all cached values.
106
+ *
107
+ * @returns void
108
+ */
109
+ clear() {
110
+ return this.map.clear();
111
+ }
112
+ /**
113
+ * Given a cache key, normalize the key to a string.
114
+ *
115
+ * @param key Cache key
116
+ * @returns Normalized cached key string
117
+ */
118
+ normalize(key) {
119
+ const newKey = extractProperties(key);
120
+ return JSON.stringify(newKey);
121
+ }
122
+ }
123
+
124
+ const dateTimeFormatCache = new Map();
125
+ /**
126
+ * Clears the cache
127
+ */
128
+ function clearDateTimeFormatCache() {
129
+ dateTimeFormatCache.clear();
130
+ }
131
+ /**
132
+ * Instantiate or returns a cached value of {@link Intl.DateTimeFormat}, instantiated with given options.
133
+ *
134
+ * @param locale {string} Locale
135
+ * @param options {Intl.DateTimeFormatOptions} Formatter options
136
+ * @returns {Intl.DateTimeFormat} Formatter instance
137
+ */
138
+ function getDateTimeFormat(locale = 'en-US', options = {}) {
139
+ if (!dateTimeFormatCache.has(locale)) {
140
+ dateTimeFormatCache.set(locale, new Cache());
141
+ }
142
+ if (!dateTimeFormatCache.get(locale).has(options)) {
143
+ const instance = new Intl.DateTimeFormat(locale, options);
144
+ dateTimeFormatCache.get(locale).set(options, instance);
145
+ }
146
+ return dateTimeFormatCache.get(locale).get(options);
147
+ }
148
+
149
+ /**
150
+ * Escapes a string to use in Javascript regular expression
151
+ *
152
+ * @param value String value to include in a regex
153
+ * @returns {string} Escaped value
154
+ */
155
+ function escapeRegex(value) {
156
+ return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
157
+ }
158
+ /** Map of calendar name used on Intl to its CLDR counterpart */
159
+ const intlCalendarMap = {
160
+ gregory: 'gregorian',
161
+ };
162
+ /**
163
+ * Resolves Intl calendar name to CLDR calendar name
164
+ *
165
+ * @param calendar {string} Calendar name for Intl
166
+ * @returns {string} Calendar name for CLDR
167
+ */
168
+ function intlCalendarToCldr(calendar) {
169
+ return Object.prototype.hasOwnProperty.call(intlCalendarMap, calendar)
170
+ ? intlCalendarMap[calendar]
171
+ : calendar;
172
+ }
173
+ const eraDateRegex = /^([-]?[0-9]+)-([0-9]{1,2})-([0-9]{1,2})$/;
174
+ /**
175
+ * Parses the date string on CLDR era start/end data to year/month/day property object.
176
+ *
177
+ * @private
178
+ * @param value {string} Era date value to parse
179
+ * @returns Resolved date value
180
+ */
181
+ function parseEraDate(value) {
182
+ if (value && eraDateRegex.test(value)) {
183
+ const matches = value.match(eraDateRegex);
184
+ return {
185
+ year: parseInt(matches[0], 10),
186
+ month: parseInt(matches[0], 10),
187
+ day: parseInt(matches[0], 10),
188
+ };
189
+ }
190
+ return undefined;
191
+ }
192
+ // function compareDate(l: { year: number, month: number, day: number }, r: { year: number, month: number, day: number }): number {
193
+ // if (l.year < r.year || (l.year === r.year && l.month < r.month) || (l.year === r.year && l.month === r.month && l.day < r.day)) return -1;
194
+ // if (l.year > r.year || (l.year === r.year && l.month > r.month) || (l.year === r.year && l.month === r.month && l.day > r.day)) return 1;
195
+ // return 0;
196
+ // }
197
+ /**
198
+ * Resolves a date with year that is indexed to a specific era to its gregorian year value. e.g. Reiwa 3, January 1st for Japanese imperial calendar will resolve to 2021
199
+ *
200
+ * @param eraIndex {number} Era index
201
+ * @param year {number} Year in era
202
+ * @param month {number} Month in era
203
+ * @param day {number} Day in era
204
+ * @param calendarType {string} Calendar type. e.g. "gregorian"
205
+ * @param common {LocaleCommonDataCalendarData} Locale common calendar data
206
+ * @returns {number} Positive gregorian year for AD, negative gregorian year for BC
207
+ */
208
+ function getGregorianYear(eraIndex, year, month, day, calendarType = 'gregorian', calendarData) {
209
+ if (calendarType === 'gregorian') {
210
+ // Fast path for gregorian
211
+ // eraIndex === 0 if BC. A negative BC is a positive AD, vice versa
212
+ return eraIndex === 0 ? -year : year;
213
+ }
214
+ else if (calendarData && calendarData.calendarSystem === 'solar' && calendarData.eras) {
215
+ // non-gregorian solar calendars
216
+ const eras = Object.keys(calendarData.eras)
217
+ .sort()
218
+ .map((k) => calendarData.eras[k]);
219
+ if (eraIndex >= eras.length || eraIndex < 0) {
220
+ // NOTE: Should we start with era that match today, rather than last era if invalid?
221
+ eraIndex = eras.length - 1;
222
+ }
223
+ const era = eras[eraIndex];
224
+ const yearAdjusted = year > 0 ? year - 1 : year < 0 ? year + 1 : 0;
225
+ // We can only have either _start or _end on era
226
+ if (era._end) {
227
+ // This can only be the first era (eras[0]) that counts down (gregorian, coptic, ethiopic)
228
+ const eraEnd = parseEraDate(era._end);
229
+ if (!eraEnd)
230
+ return undefined; // Report to Unicode
231
+ return eraEnd.year - yearAdjusted;
232
+ }
233
+ else if (era._start) {
234
+ const eraStart = parseEraDate(era._start);
235
+ if (!eraStart)
236
+ return undefined; // Report to Unicode
237
+ return eraStart.year + yearAdjusted;
238
+ }
239
+ }
240
+ else if (calendarData && calendarData.calendarSystem !== 'solar' && calendarData.eras) {
241
+ // TODO: non-gregorian non-solar calendars
242
+ return year;
243
+ }
244
+ return undefined;
245
+ }
246
+ /**
247
+ * Extracts localized digits on a string and convert it to an array of integers
248
+ *
249
+ * @param value {string} String value
250
+ * @param localeDigits {string[]} Array of localized digits. e.g. ["0",...,"9"] for "latn"
251
+ * @returns {number[]} Array of integers from string. e.g. [1,2,3] for "123" for "latn"
252
+ */
253
+ function stringToDigits(value, localeDigits) {
254
+ const digits = [];
255
+ [...value].forEach((char) => {
256
+ const digit = localeDigits.indexOf(char);
257
+ if (digit >= 0) {
258
+ digits.push(digit);
259
+ }
260
+ });
261
+ return digits.reverse();
262
+ }
263
+ /**
264
+ * Extracts localized digits on a string and convert it to an integer
265
+ *
266
+ * @param value {string} String value
267
+ * @param digits {string} Localized digits. e.g. "0123456789" for "latn"
268
+ * @returns {number} Integer value. e.g. 123 for "123" for "latn"
269
+ */
270
+ function parseDigits(value, digits) {
271
+ return stringToDigits(value, Array.from(digits)).reduce((int, cur, idx) => (int += cur * 10 ** idx), 0);
272
+ }
273
+ /**
274
+ * @private
275
+ */
276
+ function getDigitsRegexPatternInternal(digits, extras = '') {
277
+ // Optimze for sequential codepoints
278
+ if (digits.length > 1 &&
279
+ digits.reduce((sum, digit, idx, arr) => idx === 0
280
+ ? true
281
+ : sum && digit.codePointAt(0) - arr[idx - 1].codePointAt(0) === 1, false)) {
282
+ return '[' + [digits[0], digits[digits.length - 1]].join('-') + escapeRegex(extras) + ']';
283
+ }
284
+ return '[' + digits.join('') + escapeRegex(extras) + ']';
285
+ }
286
+ /**
287
+ * Generates an optimized regular expression for given localized digits.
288
+ * We would want a regular expression of [0-9] instead of [0123456789],
289
+ * or [\u{660}-\u{669}] instead of [\u{660}\u{661}\u{662}\u{663}\u{664}\u{665}\u{666}\u{667}\u{668}\u{669}] for arabic.
290
+ *
291
+ * @param digits {string} Localized digit string. e.g. "0123456789" for "latn"
292
+ * @param extras {string} Extra characters to include in regular expression range
293
+ * @returns {string} Optimized regular expression for given digits
294
+ */
295
+ function getDigitsRegexPattern(digits, extras = '') {
296
+ return getDigitsRegexPatternInternal(Array.from(digits), extras);
297
+ }
298
+ /**
299
+ * This formats to "MM/dd/yyyy, hh:mm" in English (United States) specifically
300
+ */
301
+ const UTC_FORMAT = new Intl.DateTimeFormat('en-US', {
302
+ timeZone: 'UTC',
303
+ hourCycle: 'h23',
304
+ year: 'numeric',
305
+ month: 'numeric',
306
+ day: 'numeric',
307
+ hour: 'numeric',
308
+ minute: 'numeric',
309
+ });
310
+ /**
311
+ * This matches for "MM/dd/yyyy, hh:mm"
312
+ * Group 0: month
313
+ * Group 1: date
314
+ * Group 2: year
315
+ * Group 3: hour
316
+ * Group 4: minutes
317
+ */
318
+ const EN_US_DATE_REGEXP = /(\d+).(\d+).(\d+),?\s+(\d+).(\d+)(.(\d+))?/;
319
+ const EN_US_DATE_INDEX_DAY = 1;
320
+ const EN_US_DATE_INDEX_HOUR = 3;
321
+ const EN_US_DATE_INDEX_MINUTE = 4;
322
+ const TIMEZONE_DATEFORMATS = new Map();
323
+ /**
324
+ * Parses date string in specific "MM/dd/yyyy, hh:mm" format to its respected parts.
325
+ *
326
+ * @param value Date string value in "MM/dd/yyyy, hh:mm" format
327
+ * @returns Array of parsed strings based on groups defined at EN_US_DATE_REGEXP
328
+ */
329
+ function parseEnUsDate(value) {
330
+ const dateString = value.replace(/[\u200E\u200F]/g, ''); // Cleanse
331
+ return [].slice.call(EN_US_DATE_REGEXP.exec(dateString), 1).map(Math.floor);
332
+ }
333
+ /**
334
+ * Given two date parts parsed by parseEnUsDate, return the difference in minutes.
335
+ *
336
+ * @param date1 Date parts
337
+ * @param date2 Date parts
338
+ * @returns Difference in minutes
339
+ */
340
+ function diffMinutes(date1, date2) {
341
+ let day = date1[EN_US_DATE_INDEX_DAY] - date2[EN_US_DATE_INDEX_DAY];
342
+ const hour = date1[EN_US_DATE_INDEX_HOUR] - date2[EN_US_DATE_INDEX_HOUR];
343
+ const min = date1[EN_US_DATE_INDEX_MINUTE] - date2[EN_US_DATE_INDEX_MINUTE];
344
+ if (day > 15) {
345
+ day = -1;
346
+ }
347
+ if (day < -15) {
348
+ day = 1;
349
+ }
350
+ return 60 * (24 * day + hour) + min;
351
+ }
352
+ /**
353
+ * Return timezone offset of given date in minutes
354
+ *
355
+ * @param timeZoneId Time zone
356
+ * @param date Date object
357
+ * @returns Number of time offset in minutes
358
+ */
359
+ function getTimeZoneOffset(timeZone, date) {
360
+ if (!TIMEZONE_DATEFORMATS.has(timeZone)) {
361
+ // This must be the same locale (English (United States)) and options as UTC_FORMAT, minus the timeZone difference
362
+ TIMEZONE_DATEFORMATS.set(timeZone, new Intl.DateTimeFormat('en-US', {
363
+ timeZone: timeZone,
364
+ hourCycle: 'h23',
365
+ year: 'numeric',
366
+ month: 'numeric',
367
+ day: 'numeric',
368
+ hour: 'numeric',
369
+ minute: 'numeric',
370
+ }));
371
+ }
372
+ const dateFormat = TIMEZONE_DATEFORMATS.get(timeZone);
373
+ return diffMinutes(parseEnUsDate(UTC_FORMAT.format(date)), parseEnUsDate(dateFormat.format(date)));
374
+ }
375
+ /**
376
+ * Validates week
377
+ *
378
+ * @param week {Number} Week number
379
+ * @returns {boolean} true if the week is valid
380
+ */
381
+ function isValidWeek(week) {
382
+ return (Number.isInteger(week) && week >= 1 /*week starts at 1 */ && week <= 53); /* at most we only have 53 weeks */
383
+ }
384
+ /**
385
+ * Validates day of week
386
+ *
387
+ * @param dayOfWeek {Number} dayOfWeek number
388
+ * @returns {boolean} true if the day of week is valid
389
+ */
390
+ function isValidDayOfWeek(dayOfWeek) {
391
+ return (Number.isInteger(dayOfWeek) &&
392
+ dayOfWeek >= 1 /* day of week starts at 1 */ &&
393
+ dayOfWeek <= 7); /* day of week ends at 7 */
394
+ }
395
+ /**
396
+ * Validates month
397
+ *
398
+ * @param month {Number} Month number
399
+ * @returns {boolean} true if the month is valid
400
+ */
401
+ function isValidMonth(month) {
402
+ return is31DayMonth(month) || is30DayMonth(month) || month === 2;
403
+ }
404
+ /**
405
+ * Check if the month has 31 days
406
+ *
407
+ * @param month {Number} Month number
408
+ * @returns {boolean} true if the month has 31 days
409
+ */
410
+ function is31DayMonth(month) {
411
+ return (month === 1 ||
412
+ month === 3 ||
413
+ month === 5 ||
414
+ month === 7 ||
415
+ month === 8 ||
416
+ month === 10 ||
417
+ month === 12);
418
+ }
419
+ /**
420
+ * Check if the month has 30 days
421
+ *
422
+ * @param month {Number} Month number
423
+ * @returns {boolean} true if the month has 30 days
424
+ */
425
+ function is30DayMonth(month) {
426
+ return month === 4 || month === 6 || month === 9 || month === 11;
427
+ }
428
+ /**
429
+ * Check if ordinal is valid
430
+ *
431
+ * @param ordinal {Number} Ordinal number
432
+ * @param isLeapYear {boolean}
433
+ * @returns {boolean} true if the ordinal is valid
434
+ */
435
+ function isValidOrdinal(ordinal, isLeapYear) {
436
+ return (Number.isInteger(ordinal) &&
437
+ ordinal >= 1 /* ordinal day starts at 1 */ &&
438
+ ((isLeapYear && ordinal <= 366) /* at most we only have 366 days for leap year */ ||
439
+ (!isLeapYear && ordinal <= 365))); /* at most we only have 366 days for leap year */
440
+ }
441
+ /**
442
+ * Validate date
443
+ *
444
+ * @param month {Number} Month number
445
+ * @param day {Number} Day number
446
+ * @param isLeapYear {boolean} Indicates whether its a leap year
447
+ * @returns {boolean} true if the date is valid
448
+ */
449
+ function isValidDate(month, day, isLeapYear) {
450
+ return (isValidMonth(month) &&
451
+ Number.isInteger(day) &&
452
+ day >= 1 /* 0 is not a date */ &&
453
+ ((day <= 30 && is30DayMonth(month)) /* validation for 30 day months */ ||
454
+ (day <= 31 && is31DayMonth(month)) /* validation for 31 day months */ ||
455
+ (isLeapYear && month === 2 && day <= 29) /* february, leap year */ ||
456
+ (!isLeapYear && month === 2 && day <= 28))); /* february, non leap year */
457
+ }
458
+
459
+ /**
460
+ * Calendar holds broken down data of an instance in time to calendar parts (year, month, day, etc). This helps in calendar arithmetic (e.g. change time zones, add days, etc)
461
+ */
462
+ class Calendar {
463
+ /**
464
+ * @constructor
465
+ * @param timeZone {string} Time zone of this instance
466
+ */
467
+ constructor(timeZone) {
468
+ this.year = 0;
469
+ this.month = 1;
470
+ this.day = 1;
471
+ this.hour = 0;
472
+ this.minute = 0;
473
+ this.second = 0;
474
+ this.millisecond = 0;
475
+ this.timeZone = timeZone;
476
+ }
477
+ /**
478
+ * Clears the data on this calendar instance
479
+ */
480
+ clear() {
481
+ this.year = 0;
482
+ this.month = 1;
483
+ this.day = 1;
484
+ this.hour = 0;
485
+ this.minute = 0;
486
+ this.second = 0;
487
+ this.millisecond = 0;
488
+ }
489
+ /**
490
+ * Converts this calendar instance to an equivalent Javascript Date instance
491
+ *
492
+ * @param smallYearOffset {number} Offset year to use if this calendar year is a two-digit year
493
+ * @returns {Date} Date instance
494
+ */
495
+ getDate(smallYearOffset = 2000) {
496
+ // Adjust year if two digits and below
497
+ if (this.year < 100 && this.year >= 0) {
498
+ this.year += smallYearOffset;
499
+ }
500
+ // Date is constructed with date & time in local (browser) time zone
501
+ const localDate = new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.millisecond);
502
+ if (this.timeZone) {
503
+ const localOffset = localDate.getTimezoneOffset();
504
+ const timeZoneOffset = getTimeZoneOffset(this.timeZone, localDate);
505
+ const offset = localOffset - timeZoneOffset;
506
+ // Return local date if offset equals, else adjust millisecond and return adjusted date
507
+ if (Math.floor(offset) !== 0) {
508
+ const adjustedDate = new Date(localDate.getTime() - offset * 60 * 1000);
509
+ return adjustedDate;
510
+ }
511
+ }
512
+ return localDate;
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Tokenize string value of CLDR date time format string to array of tokens.
518
+ *
519
+ * Ref: https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
520
+ *
521
+ * @param value CLDR date time format pattern
522
+ * @returns Array of tokens
523
+ */
524
+ function tokenizeDateTimePattern(value) {
525
+ // Notes regarding single quote escape:
526
+ // <quote>
527
+ // Literal text, which is output as-is when formatting, and must closely match when parsing. Literal text can include:
528
+ // * Any characters other than A..Z and a..z, including spaces and punctuation.
529
+ // * Any text between single vertical quotes ('xxxx'), which may include A..Z and a..z as literal text.
530
+ // * Two adjacent single vertical quotes (''), which represent a literal single quote, either inside or outside quoted text.
531
+ // </quote>
532
+ // Ref: https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns
533
+ // e.g.
534
+ // '' -> [text(')]
535
+ // 'a' -> [text(a)]
536
+ // 'ymd' -> [text(ymd)]
537
+ // ''a -> [text('), token(a)]
538
+ // ''' -> [text(')]
539
+ // '''' -> [text('')]
540
+ // 'a''b' -> [text(a'b)]
541
+ // ' '' ' -> [text( ' )]
542
+ const tokenValues = [...'GyYQqM<wWdDFEecabBhHKkmsSAxXOvVXxZz']; // DateTimePatternToken.type
543
+ const tokens = [];
544
+ let isEscapedText = false;
545
+ let isPrevSingleQuote = false;
546
+ [...value].forEach((char) => {
547
+ // tokenValue is defined if it is a known token character.
548
+ const tokenValue = tokenValues.indexOf(char) >= 0 ? char : undefined;
549
+ let isCharText = false;
550
+ // Set single quoted mode
551
+ if ((tokenValue !== undefined || char !== "'") && isPrevSingleQuote) {
552
+ isEscapedText = !isEscapedText;
553
+ }
554
+ // Process
555
+ if (tokenValue !== undefined && !isEscapedText) {
556
+ if (tokens.length > 0 && tokens[tokens.length - 1].type === tokenValue) {
557
+ // Current character is same as previous token. extending token length
558
+ let tokenLength = tokens[tokens.length - 1].length;
559
+ tokenLength = tokenLength === undefined ? 1 : tokenLength + 1;
560
+ tokens[tokens.length - 1].length = tokenLength;
561
+ }
562
+ else if (tokenValue !== undefined) {
563
+ // Current character is a known token
564
+ tokens.push({
565
+ // @ts-ignore
566
+ type: tokenValue,
567
+ length: 1,
568
+ });
569
+ }
570
+ isPrevSingleQuote = false;
571
+ }
572
+ else if (char === "'" && !isPrevSingleQuote) {
573
+ isPrevSingleQuote = true;
574
+ }
575
+ else if (char === "'" && isPrevSingleQuote) {
576
+ // Treat two single quotes as one single quote text
577
+ isCharText = true;
578
+ isPrevSingleQuote = false; // Consume current single quote
579
+ }
580
+ else {
581
+ // Treat others as text
582
+ isCharText = true;
583
+ isPrevSingleQuote = false; // Consumed current single quote
584
+ }
585
+ if (isCharText) {
586
+ // Add as text token
587
+ // Extend last token if it is a text token
588
+ if (tokens.length > 0 && tokens[tokens.length - 1].type === undefined) {
589
+ // Previous token is text, extending token text
590
+ let tokenText = tokens[tokens.length - 1].text;
591
+ tokenText = tokenText === undefined ? char : tokenText + char;
592
+ tokens[tokens.length - 1].text = tokenText;
593
+ }
594
+ else {
595
+ // Previous token is not text, creating new text token
596
+ tokens.push({
597
+ text: char,
598
+ });
599
+ }
600
+ }
601
+ });
602
+ return tokens;
603
+ }
604
+ /**
605
+ * Gets symbols for given CLDR date time format token from CLDR locale data.
606
+ *
607
+ * @param token CLDR date time format token
608
+ * @param calendarData Locale data for calendar
609
+ * @returns Map of symbols from locale data for given token. e.g. {'0':'january','1':'february',...}
610
+ */
611
+ function getSymbols(token, calendarData) {
612
+ switch (token) {
613
+ case 'a':
614
+ case 'aa':
615
+ case 'aaa':
616
+ return calendarData.dayPeriods.format.abbreviated;
617
+ case 'aaaa':
618
+ return calendarData.dayPeriods.format.wide;
619
+ case 'aaaaa':
620
+ return calendarData.dayPeriods.format.narrow;
621
+ case 'G':
622
+ return calendarData.eras.eraAbbr;
623
+ case 'GGGG':
624
+ return calendarData.eras.eraNames;
625
+ case 'GGGGG':
626
+ return calendarData.eras.eraNarrow;
627
+ case 'MMM':
628
+ return calendarData.months.format.abbreviated;
629
+ case 'MMMM':
630
+ return calendarData.months.format.wide;
631
+ case 'MMMMM':
632
+ return calendarData.months.format.narrow;
633
+ case 'EEEE':
634
+ return calendarData.days.format.wide;
635
+ case 'EEEEE':
636
+ return calendarData.days.format.narrow;
637
+ case 'EEEEEE':
638
+ return calendarData.days.format.abbreviated;
639
+ default:
640
+ return {};
641
+ }
642
+ }
643
+ /**
644
+ * Gets the token type for CLDR date time format token, whether it is numeric, specific digit numeric, or string
645
+ *
646
+ * @param value CLDR date time format token
647
+ * @returns -1 if token is not numeric, 0 if token is of arbitrary digits, >= 1 if token is of specific digit
648
+ */
649
+ function getTokenDigits(token) {
650
+ switch (token.type) {
651
+ case 'y': // Year
652
+ case 'd': // DayOfMonth
653
+ case 'K': // Hour0011
654
+ case 'H': // Hour0023
655
+ case 'h': // Hour0112
656
+ case 'k': // Hour0124
657
+ case 'm': // Minute
658
+ case 's': // Second
659
+ case 'S': // SecondFractional
660
+ case 'A': // MilliSecond
661
+ // eslint-disable-next-line no-fallthrough
662
+ case 'Q': // Quarter
663
+ return token.length === 2 ? 2 : 0;
664
+ case 'M': // Month
665
+ case 'e': // WeekDayLocal
666
+ case 'c': // WeekDayLocalStandalone
667
+ switch (token.length) {
668
+ case 1:
669
+ return 0;
670
+ case 2:
671
+ return 2;
672
+ default:
673
+ return -1;
674
+ }
675
+ }
676
+ return -1;
677
+ }
678
+ /** Cache regex */
679
+ const regexps = new Map();
680
+ /**
681
+ * Generates the expression object that contains regex and tokens used on the regex groups based on given pattern and locale data.
682
+ *
683
+ * @param pattern CLDR date time format pattern
684
+ * @param digits Used digits
685
+ * @param calData Locale data for calendar
686
+ * @returns DateTimePattenExpression
687
+ */
688
+ function getExpression$1(pattern, digits, calData) {
689
+ const tokens = tokenizeDateTimePattern(pattern);
690
+ const groups = [];
691
+ const digitsRange = getDigitsRegexPattern(digits);
692
+ let exprValue = '^[\\s]*'; // trim leading whitespace
693
+ for (let i = 0; i < tokens.length; i++) {
694
+ const token = tokens[i];
695
+ if (token.text !== undefined && token.text !== '') {
696
+ // text
697
+ exprValue += '(' + escapeRegex(token.text) + ')';
698
+ groups.push(token);
699
+ }
700
+ else if (token.type !== undefined) {
701
+ const tokenString = token.type.repeat(token.length);
702
+ const digits = getTokenDigits(token);
703
+ if (digits === -1) {
704
+ if (tokenString === 'z') {
705
+ // time zone is free text w/o whitespace
706
+ exprValue += '([^\\s]+)';
707
+ }
708
+ else {
709
+ // non-numeric symbols
710
+ const symbols = getSymbols(tokenString, calData);
711
+ exprValue +=
712
+ '(' +
713
+ Object.keys(symbols)
714
+ .map((k) => escapeRegex(symbols[k]))
715
+ .join('|') +
716
+ ')';
717
+ }
718
+ groups.push(token);
719
+ }
720
+ else if (digits === 0) {
721
+ // arbitrary digits
722
+ exprValue += '(' + digitsRange + '+)';
723
+ groups.push(token);
724
+ }
725
+ else if (digits > 0) {
726
+ // exact digits
727
+ exprValue += '(' + digitsRange + '{1,' + digits + '})';
728
+ groups.push(token);
729
+ }
730
+ }
731
+ }
732
+ exprValue += '[\\s]*$'; // trim trailing whitespace
733
+ if (!regexps.has(exprValue)) {
734
+ regexps.set(exprValue, new RegExp(exprValue));
735
+ }
736
+ return { regexp: regexps.get(exprValue), groups };
737
+ }
738
+ /**
739
+ * Utility to return the first key of object property by its value.
740
+ *
741
+ * @param parent Target object
742
+ * @param value Property value to find
743
+ * @returns Property key
744
+ */
745
+ function getKeyByValue(parent, value) {
746
+ for (const key in parent) {
747
+ if (Object.prototype.hasOwnProperty.call(parent, key) && parent[key] === value) {
748
+ return key;
749
+ }
750
+ }
751
+ return undefined;
752
+ }
753
+ /**
754
+ * Utility to return the CLDR locale data key from locale data by its value.
755
+ * e.g. symbol='February', token={type:'M',length:'MMM'} returns '1'
756
+ *
757
+ * @param symbol Value to search
758
+ * @param token CLDR date time format token to search
759
+ * @param data Locale data for calendar
760
+ * @returns CLDR key name
761
+ */
762
+ function getKeyFromSymbol(symbol, token, data) {
763
+ const tokenString = token.type.repeat(token.length);
764
+ switch (token.type) {
765
+ case 'G': // Era
766
+ switch (tokenString) {
767
+ case 'G':
768
+ return getKeyByValue(data.eras.eraNames, symbol);
769
+ case 'GGGG':
770
+ return getKeyByValue(data.eras.eraNarrow, symbol);
771
+ case 'GGGGG':
772
+ return getKeyByValue(data.eras.eraAbbr, symbol);
773
+ }
774
+ break;
775
+ case 'M': // Month
776
+ case 'L': // MonthStandalone
777
+ switch (tokenString) {
778
+ case 'MMM': // abbreviated/short
779
+ return getKeyByValue(data.months.format.abbreviated, symbol);
780
+ case 'MMMM': // wide/long
781
+ return getKeyByValue(data.months.format.wide, symbol);
782
+ case 'MMMMM': // narrow
783
+ return getKeyByValue(data.months.format.narrow, symbol);
784
+ case 'LLL': // abbreviated/short
785
+ return getKeyByValue(data.months['stand-alone'].short, symbol);
786
+ case 'LLLL': // wide/long
787
+ return getKeyByValue(data.months['stand-alone'].wide, symbol);
788
+ case 'LLLLL': // narrow
789
+ return getKeyByValue(data.months['stand-alone'].narrow, symbol);
790
+ }
791
+ break;
792
+ case 'a': // PeriodAmPm
793
+ case 'b': // PeriodAmPmNoonMidnight
794
+ case 'B': // PeriodFlexible
795
+ if (token.length >= 1 && token.length <= 3)
796
+ return getKeyByValue(data.dayPeriods.format.abbreviated, symbol);
797
+ if (token.length === 4)
798
+ return getKeyByValue(data.dayPeriods.format.wide, symbol);
799
+ if (token.length === 5)
800
+ return getKeyByValue(data.dayPeriods.format.narrow, symbol);
801
+ break;
802
+ case 'E': // WeekDay:
803
+ case 'e': // WeekDayLocal // Same keys as WeekDay. But index of monday changes per locale
804
+ case 'c': // WeekDayLocalStandalone: // Same keys as WeekDay. But index of monday changes per locale
805
+ switch (tokenString) {
806
+ // abbreviated
807
+ case 'E':
808
+ case 'EE':
809
+ case 'EEE':
810
+ case 'eee':
811
+ case 'ccc':
812
+ return getKeyByValue(data.days.format.abbreviated, symbol);
813
+ // wide/long
814
+ case 'EEEE':
815
+ case 'eeee':
816
+ case 'cccc':
817
+ return getKeyByValue(data.days.format.wide, symbol);
818
+ // narrow
819
+ case 'EEEEE':
820
+ case 'eeeee':
821
+ case 'ccccc':
822
+ return getKeyByValue(data.days.format.abbreviated, symbol);
823
+ // short
824
+ case 'EEEEEE':
825
+ case 'eeeeee':
826
+ case 'cccccc':
827
+ return getKeyByValue(data.days.format.short, symbol);
828
+ }
829
+ break;
830
+ }
831
+ return undefined;
832
+ }
833
+
834
+ /*
835
+ * CLDR locale specific types
836
+ */
837
+ /**
838
+ * Keys used on LocaleDataDatesCalendar for months
839
+ */
840
+ const MONTHS_KEYS = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
841
+
842
+ /**
843
+ * Date/time parser implementation based on CLDR locale data
844
+ *
845
+ * @author aazizy
846
+ */
847
+ class DateTimeParseImpl {
848
+ /**
849
+ * @constructor
850
+ * @param options {DateTimeParseOptions} Options
851
+ */
852
+ constructor(options) {
853
+ // Build data
854
+ const data = {
855
+ commonDigits,
856
+ commonCalendarData,
857
+ defaultCalendar,
858
+ defaultNumberingSystem,
859
+ calendarData,
860
+ };
861
+ // Resolve options
862
+ this.calendarType = options.calendar
863
+ ? intlCalendarToCldr(options.calendar)
864
+ : data.defaultCalendar;
865
+ const numSys = options.numberingSystem
866
+ ? options.numberingSystem
867
+ : data.defaultNumberingSystem;
868
+ const timeZone = options.timeZone ? options.timeZone : undefined;
869
+ options.numberingSystem = numSys;
870
+ options.timeZone = timeZone;
871
+ this.options = options;
872
+ this.digits = data.commonDigits[numSys];
873
+ this.commonCalendarData = data.commonCalendarData[this.calendarType];
874
+ this.calendarData = data.calendarData[this.calendarType];
875
+ // Generating expression here becuse we want to do this only once.
876
+ this.expr = getExpression$1(this.options.pattern, this.digits, this.calendarData);
877
+ }
878
+ /**
879
+ * {@inheritdoc}
880
+ */
881
+ parse(value) {
882
+ const matches = value.match(this.expr.regexp);
883
+ if (!matches || matches.length <= this.expr.groups.length) {
884
+ throw new Error("Invalid date: '" + value + "' for pattern: '" + this.options.pattern + "'");
885
+ }
886
+ const cal = new Calendar(this.options.timeZone);
887
+ cal.clear();
888
+ let era;
889
+ let dayPeriod;
890
+ this.expr.groups.forEach((group, groupIndex) => {
891
+ if (group.type === undefined) {
892
+ // Skip text (separators, space etc)
893
+ return;
894
+ }
895
+ const match = matches[groupIndex + 1];
896
+ if (getTokenDigits(group) >= 0) {
897
+ // Parse digits
898
+ // This is lenient parsing right now.
899
+ // We should throw exception for strict parsing, if digits are not in range.
900
+ // e.g.
901
+ // 'K' should validate against 0 <= cal.hour && cal.hour <= 11
902
+ // 'h' should validate against 1 <= cal.hour && cal.hour <= 12
903
+ switch (group.type) {
904
+ case 'y': // Year
905
+ cal.year = parseDigits(match, this.digits);
906
+ break;
907
+ case 'M': // Month
908
+ cal.month = parseDigits(match, this.digits);
909
+ break;
910
+ case 'd': // DayOfMonth
911
+ cal.day = parseDigits(match, this.digits);
912
+ break;
913
+ case 'K': // Hour0011
914
+ cal.hour = parseDigits(match, this.digits);
915
+ break;
916
+ case 'H': // Hour0023
917
+ cal.hour = parseDigits(match, this.digits);
918
+ break;
919
+ case 'h': // Hour0112
920
+ cal.hour = parseDigits(match, this.digits);
921
+ break;
922
+ case 'k': // Hour0124
923
+ cal.hour = parseDigits(match, this.digits);
924
+ break;
925
+ case 'm': // Minute
926
+ cal.minute = parseDigits(match, this.digits);
927
+ break;
928
+ case 's': // Second
929
+ cal.second = parseDigits(match, this.digits);
930
+ break;
931
+ case 'A': // MilliSecond
932
+ cal.millisecond = parseDigits(match, this.digits);
933
+ break;
934
+ }
935
+ }
936
+ else {
937
+ // Parse symbols
938
+ const key = getKeyFromSymbol(match, group, this.calendarData);
939
+ if (key) {
940
+ switch (group.type) {
941
+ case 'G': // Era
942
+ era = parseInt(key, 10);
943
+ break;
944
+ case 'M': // Month
945
+ case 'L': // MonthStandalone
946
+ cal.month = MONTHS_KEYS.indexOf(key) + 1; // Calendar month is one indexed
947
+ break;
948
+ case 'a': // PeriodAmPm
949
+ case 'b': // PeriodAmPmNoonMidnight
950
+ case 'B': // PeriodFlexible
951
+ dayPeriod = key;
952
+ break;
953
+ }
954
+ }
955
+ }
956
+ });
957
+ // Adjust year
958
+ if (era !== undefined && era >= 0) {
959
+ const adjustedYear = getGregorianYear(era, cal.year, cal.month, cal.day, this.calendarType, this.commonCalendarData);
960
+ if (adjustedYear !== undefined)
961
+ cal.year = adjustedYear;
962
+ }
963
+ // Adjust hours based period
964
+ if (dayPeriod !== undefined) {
965
+ switch (dayPeriod) {
966
+ case 'am':
967
+ case 'morning1':
968
+ case 'morning2':
969
+ // NOOP
970
+ break;
971
+ case 'pm':
972
+ case 'afternoon1':
973
+ case 'afternoon2':
974
+ case 'evening1':
975
+ case 'night1':
976
+ if (cal.hour >= 0 && cal.hour < 12)
977
+ cal.hour += 12;
978
+ break;
979
+ case 'midnight':
980
+ if (cal.hour === 12)
981
+ cal.hour = 0;
982
+ break;
983
+ }
984
+ }
985
+ return cal.getDate();
986
+ }
987
+ }
988
+
989
+ const cldrParserCache = new Cache();
990
+ /**
991
+ * Clears the cache
992
+ */
993
+ function clearDateTimeCLDRParserCache() {
994
+ cldrParserCache.clear();
995
+ }
996
+ /**
997
+ * Instantiate or returns a cached value of a CLDR-based date time parser, instantiated with given a parser options.
998
+ *
999
+ * @param options {DateTimeParseOptions} Parser options
1000
+ * @returns {DateTimeParse} Parser instance
1001
+ */
1002
+ function getDateTimeCLDRParser(options) {
1003
+ if (!cldrParserCache.has(options)) {
1004
+ cldrParserCache.set(options, new DateTimeParseImpl(options));
1005
+ }
1006
+ return cldrParserCache.get(options);
1007
+ }
1008
+
1009
+ const intlRelativeTimeFormatCache = new Map();
1010
+ /**
1011
+ * Clears the cache
1012
+ */
1013
+ function clearRelativeTimeFormatCache() {
1014
+ intlRelativeTimeFormatCache.clear();
1015
+ }
1016
+ /**
1017
+ * Instantiate or returns a cached value of {@see Intl.RelativeTimeFormat}, instantiated with given options.
1018
+ *
1019
+ * @param options {Intl.RelativeTimeFormatOptions} Formatter options
1020
+ * @returns {Intl.RelativeTimeFormat} Formatter instance
1021
+ */
1022
+ function getRelativeTimeFormat(locale = 'en-US', options = {}) {
1023
+ if (!intlRelativeTimeFormatCache.has(locale)) {
1024
+ intlRelativeTimeFormatCache.set(locale, new Cache());
1025
+ }
1026
+ if (!intlRelativeTimeFormatCache.get(locale).has(options)) {
1027
+ intlRelativeTimeFormatCache
1028
+ .get(locale)
1029
+ .set(options, new Intl.RelativeTimeFormat(locale, options));
1030
+ }
1031
+ return intlRelativeTimeFormatCache.get(locale).get(options);
1032
+ }
1033
+
1034
+ const numberFormatCache = new Map();
1035
+ /**
1036
+ * Clears the cache
1037
+ */
1038
+ function clearNumberFormatCache() {
1039
+ numberFormatCache.clear();
1040
+ }
1041
+ /**
1042
+ * Instantiate or returns a cached value of {@see Intl.NumberFormat}, instantiated with given options.
1043
+ *
1044
+ * @param locale {string} Locale
1045
+ * @param options {Intl.NumberFornatOptions} Formatter options
1046
+ * @returns {Intl.NumberFormat} Formatter instance
1047
+ */
1048
+ function getNumberFormat(locale = 'en-US', options = {}) {
1049
+ if (!numberFormatCache.has(locale)) {
1050
+ numberFormatCache.set(locale, new Cache());
1051
+ }
1052
+ if (!numberFormatCache.get(locale).has(options)) {
1053
+ numberFormatCache.get(locale).set(options, new Intl.NumberFormat(locale, options));
1054
+ }
1055
+ return numberFormatCache.get(locale).get(options);
1056
+ }
1057
+
1058
+ /**
1059
+ * Calls getExpression and returns either only a positive expression, or a positive and negative expression if the pattern specifies different patterns for negative values
1060
+ *
1061
+ * @param pattern {string} Number pattern as used on CLDR
1062
+ * @param isNegative {boolean} If this pattern is for negative value. Defaults to false
1063
+ * @param digits {string} String of 10 characters used as digits. e.g. "0123456789" for latm numbering system
1064
+ * @param symbols {LocaleDataNumberSymbol} Symbols used in number pattern. See LocaleDataNumberSymbol
1065
+ * @param currencySymbol {string} Currency symbol used. e.g. "$"
1066
+ * @returns {NumberParseExpressions} A map value of positive and/or negative expression
1067
+ */
1068
+ function getExpressions(pattern, digits, symbols, currencySymbol, lenient) {
1069
+ const patterns = pattern.split(';');
1070
+ if (patterns.length >= 2) {
1071
+ return {
1072
+ positive: getExpression(patterns[0], false, digits, symbols, currencySymbol, lenient),
1073
+ negative: getExpression(patterns[1], true, digits, symbols, currencySymbol, lenient),
1074
+ };
1075
+ }
1076
+ return {
1077
+ positive: getExpression(pattern, false, digits, symbols, currencySymbol, lenient),
1078
+ };
1079
+ }
1080
+ /**
1081
+ * Given a pattern and locale data, generates the regular expression and its matching group definitions, to be used when parsing a number string value.
1082
+ *
1083
+ * @param pattern {string} Number pattern as used on CLDR
1084
+ * @param isNegative {boolean} If this pattern is for negative value. Defaults to false
1085
+ * @param digits {string} String of 10 characters used as digits. e.g. "0123456789" for latm numbering system
1086
+ * @param symbols {LocaleDataNumberSymbol} Symbols used in number pattern. See LocaleDataNumberSymbol
1087
+ * @param currencySymbol {string} Currency symbol used. e.g. "$"
1088
+ * @returns {NumberParseExpression} Generate expression and its matching group definition for parsing given number pattern
1089
+ */
1090
+ function getExpression(pattern, isNegative = false, digits, symbols, currencySymbol, lenient) {
1091
+ const groups = [];
1092
+ // Parse pattern
1093
+ let fractionalZeros = 0; // TODO: Used when min/max digits is implemented
1094
+ let integerZeros = 0; // TODO: Used when min/max digits is implemented
1095
+ // const exponentialDigits = 0; // TODO: support exponential
1096
+ // const exponentialLeadingZeros = 0; // TODO: support exponential
1097
+ const integerSeparatorIntervals = []; // Normal: [3], lakh/crore: [3,2]
1098
+ // let fractionalSeparatorRepeat = -1; // Normal: 0 // NOTE: This is used for parsing
1099
+ const exponentialPosition = pattern.indexOf('E');
1100
+ // TODO: parse exponent
1101
+ const decimalPattern = exponentialPosition > 0 ? pattern.slice(0, exponentialPosition) : pattern.slice(0);
1102
+ const decimalPosition = decimalPattern.indexOf('.');
1103
+ const integerPattern = decimalPosition >= 0 ? decimalPattern.slice(0, decimalPosition) : decimalPattern.slice(0);
1104
+ let integerSeparatorInterval = 0;
1105
+ let integerDigitsStarted = false;
1106
+ let hasPlusSign = false;
1107
+ let hasMinusSign = false;
1108
+ // Read right to left
1109
+ const integerPatternChars = [];
1110
+ [...integerPattern].forEach((char) => integerPatternChars.push(char));
1111
+ integerPatternChars.reverse();
1112
+ [...integerPatternChars].forEach((char) => {
1113
+ switch (char) {
1114
+ case '.':
1115
+ // Ignore decimal sign
1116
+ break;
1117
+ case '+':
1118
+ hasPlusSign = true;
1119
+ groups.push({ token: 'plusSign' });
1120
+ break;
1121
+ case '-':
1122
+ hasMinusSign = true;
1123
+ groups.push({ token: 'minusSign' });
1124
+ break;
1125
+ case '%':
1126
+ groups.push({ token: 'percentSign' });
1127
+ break;
1128
+ case '¤':
1129
+ groups.push({ token: 'currencySign' });
1130
+ break;
1131
+ case '0':
1132
+ integerZeros++;
1133
+ // Continue as digits (#)
1134
+ // eslint-disable-next-line no-fallthrough
1135
+ case '#':
1136
+ // this.integerDigits++;
1137
+ integerSeparatorInterval++;
1138
+ if (!integerDigitsStarted) {
1139
+ groups.push({ token: 'integer' });
1140
+ integerDigitsStarted = true;
1141
+ }
1142
+ break;
1143
+ case ',':
1144
+ integerSeparatorIntervals.push(integerSeparatorInterval);
1145
+ // integerSeparatorRepeat = integerSeparatorIntervals.length - 1; // repeat the last interval
1146
+ integerSeparatorInterval = 0;
1147
+ break;
1148
+ default:
1149
+ groups.push({ token: 'string', value: char });
1150
+ }
1151
+ });
1152
+ groups.reverse();
1153
+ if (decimalPosition >= 0) {
1154
+ groups.push({ token: 'decimalSign' });
1155
+ const fractionalPattern = decimalPattern.slice(decimalPosition);
1156
+ let fractionalDigitsStarted = false;
1157
+ // Read left to right
1158
+ [...fractionalPattern].forEach((char) => {
1159
+ switch (char) {
1160
+ case '.':
1161
+ // Ignore decimal sign
1162
+ break;
1163
+ case '+':
1164
+ hasPlusSign = true;
1165
+ groups.push({ token: 'plusSign' });
1166
+ break;
1167
+ case '-':
1168
+ hasMinusSign = true;
1169
+ groups.push({ token: 'minusSign' });
1170
+ break;
1171
+ case '%':
1172
+ groups.push({ token: 'percentSign' });
1173
+ break;
1174
+ case '¤':
1175
+ groups.push({ token: 'currencySign' });
1176
+ break;
1177
+ case '0':
1178
+ fractionalZeros++;
1179
+ // Continue as digits (#)
1180
+ // eslint-disable-next-line no-fallthrough
1181
+ case '#':
1182
+ if (!fractionalDigitsStarted) {
1183
+ groups.push({ token: 'fraction' });
1184
+ fractionalDigitsStarted = true;
1185
+ }
1186
+ break;
1187
+ case ',':
1188
+ break;
1189
+ default:
1190
+ groups.push({ token: 'string', value: char });
1191
+ }
1192
+ });
1193
+ }
1194
+ // Generate expression
1195
+ let exprValue = '';
1196
+ for (let i = 0; i < groups.length; i++) {
1197
+ const group = groups[i];
1198
+ switch (group.token) {
1199
+ case 'integer':
1200
+ // Integer part must always has at least one digit (The least is 0)
1201
+ exprValue +=
1202
+ '(' +
1203
+ getDigitsRegexPattern(digits, integerSeparatorIntervals.length > 0 ? symbols.group : '') +
1204
+ '+)';
1205
+ break;
1206
+ case 'fraction':
1207
+ // Fractional part is optional if lenient, or min fractional digits is 0
1208
+ exprValue +=
1209
+ '(' +
1210
+ getDigitsRegexPattern(digits, integerSeparatorIntervals.length > 0 ? symbols.group : '') +
1211
+ '+)';
1212
+ if (lenient || (decimalPosition > 0 && fractionalZeros <= 0)) {
1213
+ exprValue += '?';
1214
+ }
1215
+ break;
1216
+ case 'decimalSign':
1217
+ // Decimal sign is optional if lenient, or if min fractional digits is 0 (fractional is optional)
1218
+ exprValue += '(' + escapeRegex(symbols.decimal) + ')';
1219
+ if (lenient || (decimalPosition > 0 && fractionalZeros <= 0)) {
1220
+ exprValue += '?';
1221
+ }
1222
+ break;
1223
+ case 'plusSign':
1224
+ // Optional
1225
+ exprValue += '(' + escapeRegex(symbols.plusSign) + ')?';
1226
+ break;
1227
+ case 'minusSign':
1228
+ // Optional
1229
+ exprValue += '(' + escapeRegex(symbols.minusSign) + ')?';
1230
+ break;
1231
+ case 'percentSign':
1232
+ // Optional
1233
+ exprValue += '(' + escapeRegex(symbols.percentSign) + ')?';
1234
+ break;
1235
+ case 'currencySign':
1236
+ // Optional
1237
+ exprValue += '(' + escapeRegex(currencySymbol) + ')?';
1238
+ break;
1239
+ case 'string':
1240
+ if (group.value !== undefined) {
1241
+ // Group to \s if value is all whitespace
1242
+ if (/^\s+$/.test(group.value)) {
1243
+ exprValue += '(\\s+)' + (lenient ? '?' : '');
1244
+ }
1245
+ else {
1246
+ exprValue += '(' + escapeRegex(group.value) + ')';
1247
+ }
1248
+ }
1249
+ break;
1250
+ }
1251
+ }
1252
+ // Prepend optional plus/minus sign if not available in pattern
1253
+ if (!isNegative) {
1254
+ if (!hasPlusSign && !hasMinusSign) {
1255
+ exprValue =
1256
+ '(' +
1257
+ escapeRegex(symbols.plusSign) +
1258
+ '|' +
1259
+ escapeRegex(symbols.minusSign) +
1260
+ ')?' +
1261
+ exprValue;
1262
+ groups.unshift({ token: 'plusOrMinusSign' });
1263
+ }
1264
+ else if (!hasPlusSign) {
1265
+ exprValue = '(' + escapeRegex(symbols.plusSign) + ')?' + exprValue;
1266
+ groups.unshift({ token: 'plusSign' });
1267
+ }
1268
+ else if (!hasMinusSign) {
1269
+ exprValue = '(' + escapeRegex(symbols.minusSign) + ')?' + exprValue;
1270
+ groups.unshift({ token: 'minusSign' });
1271
+ }
1272
+ }
1273
+ const expr = new RegExp(exprValue);
1274
+ return {
1275
+ expr,
1276
+ groups,
1277
+ isNegative,
1278
+ minIntegerDigits: integerZeros,
1279
+ minFractionalDigits: fractionalZeros,
1280
+ };
1281
+ }
1282
+ /**
1283
+ *
1284
+ * @param value {string} String value to parse
1285
+ * @param useNegativeExpr {boolean} Use negativeExpr to parse this value instead of the default positiveExpr
1286
+ * @param positiveExpr {NumberParseExpression} Expression to use to parse this value for positive values
1287
+ * @param negativeExpr {NumberParseExpression} Expression to use to parse this value for negative values
1288
+ * @param digits {string} String of 10 characters used as digits. e.g. "0123456789" for latm numbering system
1289
+ * @param symbols {LocaleDataNumberSymbol} Symbols used in number pattern
1290
+ */
1291
+ function parseNumber(value, useNegativeExpr = false, positiveExpr, negativeExpr, digits, symbols) {
1292
+ // Parse with negative expression first if available and return if value is valid
1293
+ if (!useNegativeExpr && negativeExpr) {
1294
+ const result = parseNumber(value, true, positiveExpr, negativeExpr, digits, symbols);
1295
+ if (!isNaN(result))
1296
+ return result;
1297
+ }
1298
+ // Parse using provided negative expression if requested
1299
+ const expr = useNegativeExpr && negativeExpr ? negativeExpr : positiveExpr;
1300
+ const matches = value.match(expr.expr);
1301
+ if (!matches) {
1302
+ return NaN;
1303
+ }
1304
+ let hasMinusSign = false;
1305
+ let hasPlusSign = false;
1306
+ let isPercentPattern = false;
1307
+ let integerPart = '';
1308
+ let fractionPart = '';
1309
+ const groupRegex = new RegExp(escapeRegex(symbols.group), 'g');
1310
+ for (let i = 0; i < expr.groups.length; i++) {
1311
+ const group = expr.groups[i];
1312
+ isPercentPattern = isPercentPattern || group.token === 'percentSign';
1313
+ const match = matches[i + 1];
1314
+ if (!match)
1315
+ continue;
1316
+ switch (group.token) {
1317
+ case 'integer':
1318
+ integerPart += parseDigits(match.replace(groupRegex, ''), digits);
1319
+ break;
1320
+ case 'fraction':
1321
+ fractionPart += parseDigits(match.replace(groupRegex, ''), digits);
1322
+ break;
1323
+ case 'plusSign':
1324
+ hasPlusSign = true;
1325
+ break;
1326
+ case 'minusSign':
1327
+ hasMinusSign = true;
1328
+ break;
1329
+ case 'plusOrMinusSign':
1330
+ switch (match) {
1331
+ case symbols.plusSign:
1332
+ hasPlusSign = true;
1333
+ break;
1334
+ case symbols.minusSign:
1335
+ hasMinusSign = true;
1336
+ break;
1337
+ }
1338
+ break;
1339
+ }
1340
+ }
1341
+ // Build number
1342
+ if (hasMinusSign && hasPlusSign) {
1343
+ throw new Error('String has both plus and minus sign');
1344
+ }
1345
+ let result = NaN;
1346
+ // Remove leading zeros on integer
1347
+ integerPart.replace(/\b0+\B/, '');
1348
+ if (isPercentPattern) {
1349
+ // Shift 2 digits to the right (divide by 100) if pattern contains percent sign
1350
+ fractionPart = integerPart.slice(-2).padStart(2, '0') + fractionPart;
1351
+ integerPart = integerPart.slice(0, -2).padStart(1, '0');
1352
+ let num = (hasMinusSign ? '-' : '') + integerPart;
1353
+ if (fractionPart !== '') {
1354
+ num += '.' + fractionPart;
1355
+ }
1356
+ result = Number.parseFloat(num);
1357
+ }
1358
+ else {
1359
+ let num = (hasMinusSign ? '-' : '') + integerPart;
1360
+ if (expr.minFractionalDigits === undefined ||
1361
+ (expr.minFractionalDigits !== undefined &&
1362
+ expr.minFractionalDigits <= 0 &&
1363
+ fractionPart === '')) {
1364
+ // Treat as integer if fraction is optional
1365
+ result = Number.parseInt(num);
1366
+ }
1367
+ else {
1368
+ if (fractionPart !== '') {
1369
+ num += '.' + fractionPart;
1370
+ }
1371
+ result = Number.parseFloat(num);
1372
+ }
1373
+ }
1374
+ // Negate result if negative expression is used
1375
+ return useNegativeExpr && negativeExpr ? -result : result;
1376
+ }
1377
+ /**
1378
+ * Number parser implementation
1379
+ *
1380
+ * @author aazizy
1381
+ */
1382
+ class NumberParseImpl {
1383
+ /**
1384
+ * @constructor
1385
+ * @param options {NumberParseOptions} Options used for this parser instance
1386
+ */
1387
+ constructor(options) {
1388
+ // Build data
1389
+ const data = {
1390
+ commonDigits,
1391
+ defaultNumberingSystem,
1392
+ currencySymbol,
1393
+ symbols: {
1394
+ decimal: decimalSeparator,
1395
+ group: groupingSeparator,
1396
+ percentSign: percentSign,
1397
+ plusSign: plusSign,
1398
+ minusSign: minusSign,
1399
+ exponential: exponentialSign,
1400
+ superScriptingExponent: superscriptingExponentSign,
1401
+ perMille: perMilleSign,
1402
+ infinity: infinity,
1403
+ nan: nan,
1404
+ },
1405
+ };
1406
+ // Resolve options
1407
+ options.numberingSystem = options.numberingSystem
1408
+ ? options.numberingSystem
1409
+ : data.defaultNumberingSystem;
1410
+ this.options = options;
1411
+ this.digits = data.commonDigits[options.numberingSystem];
1412
+ this.symbols = data.symbols;
1413
+ this.currencySymbol = data.currencySymbol;
1414
+ this.lenient = !(options.lenient === false); // Default is true
1415
+ // Generating expression here becuse we want to do this only once.
1416
+ const exprs = getExpressions(this.options.pattern, this.digits, this.symbols, this.currencySymbol, this.lenient);
1417
+ this.positiveExpr = exprs.positive;
1418
+ this.negativeExpr = exprs.negative;
1419
+ }
1420
+ /**
1421
+ * @inheritdoc
1422
+ */
1423
+ parse(value) {
1424
+ return parseNumber(value, true, this.positiveExpr, this.negativeExpr, this.digits, this.symbols);
1425
+ }
1426
+ }
1427
+
1428
+ const NumberParseCache = new Cache();
1429
+ /**
1430
+ * Clears the cache
1431
+ */
1432
+ function clearNumberParserCache() {
1433
+ NumberParseCache.clear();
1434
+ }
1435
+ /**
1436
+ * Instantiate or returns a cached value of {@see NumberParse}, instantiated with given options.
1437
+ *
1438
+ * @param options {NumberParseOptions} Parser options
1439
+ * @returns {NumberParse} Parser instance
1440
+ */
1441
+ function getNumberParser(options) {
1442
+ if (!NumberParseCache.has(options)) {
1443
+ NumberParseCache.set(options, new NumberParseImpl(options));
1444
+ }
1445
+ return NumberParseCache.get(options);
1446
+ }
1447
+
1448
+ /**
1449
+ * Regular expression for parsing ISO-8601 datetime string.
1450
+ *
1451
+ * RegExp is thread safe so we can reuse this to match across instances.
1452
+ *
1453
+ * Please group indices accordingly if expression is updated.
1454
+ *
1455
+ * Ref https://en.wikipedia.org/wiki/ISO_8601
1456
+ */
1457
+ const EXPR = /^[\s]*(((-|\+)?([0-9]{4})[-]?|--)(([0-9]{2})|([0-9]{2})[-]?([0-9]{2})|(W([0-9]{2})([-]?([0-9]))?)|[-]?([0-9]{3})))(T([0-9]{2})([:]?([0-9]{2})([:]?([0-9]{2})(\.([0-9]{3}))?)?)?(Z|(-|\+)([0-9]{2})([:]?([0-9]{2})?)?)?)?[\s]*$/;
1458
+ /** (full string) ((-|+)yyyy-|--)(MM|MM-dd|Www-E|DDD)'T'hh:mm:ss.SSS('Z'|(-|+)xx:yy) */
1459
+ /** (date part) ((-|+)yyyy-|--)(MM|MM-dd|Www-E|DDD) */
1460
+ const INDEX_DATE = 1;
1461
+ /** (year part) (-|+)yyyy-|--) */
1462
+ const INDEX_YEAR_OR_NONE = INDEX_DATE + 1;
1463
+ /** (-|+)? */
1464
+ const INDEX_YEAR_SIGN = INDEX_YEAR_OR_NONE + 1;
1465
+ /** yyyy */
1466
+ const INDEX_YEAR = INDEX_YEAR_OR_NONE + 2;
1467
+ /** (MM|MM-dd|Www-E|MM-dd) */
1468
+ const INDEX_MONTH_MONTHDATE_WEEK_ORDINAL = INDEX_YEAR + 1;
1469
+ /** MM */
1470
+ const INDEX_MONTH = INDEX_MONTH_MONTHDATE_WEEK_ORDINAL + 1;
1471
+ /** MM-dd */
1472
+ const INDEX_MONTHDATE = INDEX_MONTH_MONTHDATE_WEEK_ORDINAL + 2;
1473
+ /** (week part) Www-E */
1474
+ const INDEX_WEEK = INDEX_MONTH_MONTHDATE_WEEK_ORDINAL + 4;
1475
+ /** (ordinal) DDD */
1476
+ const INDEX_ORDINAL = INDEX_MONTH_MONTHDATE_WEEK_ORDINAL + 8;
1477
+ /** (time part) 'T'hh:mm:ss.SSS('Z'|(-|+)xx:yy) */
1478
+ const INDEX_TIME = INDEX_MONTH_MONTHDATE_WEEK_ORDINAL + 9;
1479
+ /** :mm:ss.SSS */
1480
+ const INDEX_MINUTE = INDEX_TIME + 2;
1481
+ /** :ss.SSS */
1482
+ const INDEX_SECOND = INDEX_TIME + 4;
1483
+ /** .SSS, (millisecond must be prefixed by a period, even on abbreviated version) */
1484
+ const INDEX_MILLISECOND = INDEX_TIME + 6;
1485
+ /** (timezone part) 'Z'|(-|+)xx:yy */
1486
+ const INDEX_TZ = INDEX_TIME + 8;
1487
+ /** :yy */
1488
+ const INDEX_TZ_MINUTE = INDEX_TIME + 11;
1489
+ const TIME_ONLY_EXPR = /^[\s]*(([0-9]{2})([:]?([0-9]{2})([:]?([0-9]{2})(\.([0-9]{3}))?)?)?(Z|(-|\+)([0-9]{2})([:]?([0-9]{2})?)?)?)[\s]*$/;
1490
+ const TIME_ONLY_INDEX_TIME = 1;
1491
+ const TIME_ONLY_INDEX_MINUTE = TIME_ONLY_INDEX_TIME + 2;
1492
+ const TIME_ONLY_INDEX_SECOND = TIME_ONLY_INDEX_TIME + 4;
1493
+ const TIME_ONLY_INDEX_MILLISECOND = TIME_ONLY_INDEX_TIME + 6;
1494
+ const TIME_ONLY_INDEX_TZ = TIME_ONLY_INDEX_TIME + 8;
1495
+ const TIME_ONLY_INDEX_TZ_MINUTE = TIME_ONLY_INDEX_TIME + 11;
1496
+ /**
1497
+ * Parse an ISO-8601 datetime string representation to a Date object, or throw on unparseable value.
1498
+ *
1499
+ * This parser adheres to ISO-8601:2019, where the time separator 'T' is required, and full date part is required, and minute/second/millisecond is optional.
1500
+ *
1501
+ * Time defaults to 00:00:00.000Z if not specified.
1502
+ *
1503
+ * Timezone defaults to UTC if not specified.
1504
+ *
1505
+ * This parser tries to comply with ISO 8601:2004, which implies not supporting some of previously defined representations, such as YYMMDD, --MMDD
1506
+ *
1507
+ * Provides a more consistent cross-platform parsing for ISO-8601 (only) date strings
1508
+ * but behavior is different on each implementation and not recommended, as noted here:
1509
+ *
1510
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#Timestamp_string
1511
+ *
1512
+ * @param value {string} Value to parse
1513
+ * @returns {Date} Date instance
1514
+ */
1515
+ function parseDateTimeIsoString(value) {
1516
+ if (value && EXPR.test(value)) {
1517
+ return parseDateTimeString(value);
1518
+ }
1519
+ else if (value && TIME_ONLY_EXPR.test(value)) {
1520
+ return parseTimeOnlyString(value);
1521
+ }
1522
+ throw new Error("Unparseable date '" + value + "'");
1523
+ }
1524
+ function parseTimeOnlyString(value) {
1525
+ const matches = value.match(TIME_ONLY_EXPR);
1526
+ if (matches) {
1527
+ // Time parts
1528
+ let hour = 0;
1529
+ let minute = 0;
1530
+ let second = 0;
1531
+ let millisecond = 0;
1532
+ if (matches[TIME_ONLY_INDEX_TIME] !== undefined) {
1533
+ hour = Number(matches[TIME_ONLY_INDEX_TIME + 1]); // hh : hour part is required
1534
+ if (matches[TIME_ONLY_INDEX_MINUTE]) {
1535
+ minute = Number(matches[TIME_ONLY_INDEX_MINUTE + 1]); // mm : minute part is optional
1536
+ }
1537
+ if (matches[TIME_ONLY_INDEX_SECOND] !== undefined) {
1538
+ second = Number(matches[TIME_ONLY_INDEX_SECOND + 1]); // ss : second part is optional
1539
+ }
1540
+ if (matches[TIME_ONLY_INDEX_MILLISECOND] !== undefined) {
1541
+ millisecond = Number(matches[TIME_ONLY_INDEX_MILLISECOND + 1]); // SSS : millisecond part is optional
1542
+ // period on millisecond is required on both basic and extended format
1543
+ }
1544
+ // matches[INDEX_TZ] examples: 'Z', '+09:00', '-1100', '+09', '-11'
1545
+ if (matches[TIME_ONLY_INDEX_TZ] !== undefined) {
1546
+ // Only adjust offset if this is not UTC
1547
+ if (matches[TIME_ONLY_INDEX_TZ] !== 'Z') {
1548
+ let offset = Number(matches[TIME_ONLY_INDEX_TZ + 2]) * 60; // offset hour part is required for non 'Z' offset
1549
+ if (matches[TIME_ONLY_INDEX_TZ_MINUTE] !== undefined) {
1550
+ // offset minute part is optional
1551
+ offset += Number(matches[TIME_ONLY_INDEX_TZ_MINUTE + 1]);
1552
+ }
1553
+ if (matches[TIME_ONLY_INDEX_TZ + 1] === '+') {
1554
+ // plus/minus is required for non 'Z' offset
1555
+ // If offset is positive, substract offset from minutes
1556
+ // e.g. 11am in +09:00 is 2am in UTC (11:00 - 09:00)
1557
+ offset = -offset;
1558
+ }
1559
+ minute += offset;
1560
+ }
1561
+ }
1562
+ }
1563
+ const date = new Date();
1564
+ // Date is instantiated with runtime timezone offset.
1565
+ // We use Date.UTC to generate a timestamp and use that to instantiate the Date instance.
1566
+ const timestamp = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), hour, minute, second, millisecond);
1567
+ const parsedDate = new Date(timestamp);
1568
+ return parsedDate;
1569
+ }
1570
+ throw new Error("Unparseable date '" + value + "'");
1571
+ }
1572
+ function parseDateTimeString(value) {
1573
+ const matches = value.match(EXPR);
1574
+ if (matches) {
1575
+ // Date/time parts in UTC
1576
+ /** This will stay undefined for --MM-dd and --MMdd representation */
1577
+ let year;
1578
+ let isLeapYear;
1579
+ /** Only a full date can be combined with time */
1580
+ let isFullDate = true;
1581
+ let isDateExtendedFormat = false;
1582
+ if (matches[INDEX_YEAR]) {
1583
+ year = Number(matches[INDEX_YEAR]); // yyyy : year part
1584
+ if (matches[INDEX_YEAR_SIGN] && matches[INDEX_YEAR_SIGN] === '-') {
1585
+ year = -year;
1586
+ }
1587
+ isDateExtendedFormat =
1588
+ isDateExtendedFormat || matches[INDEX_YEAR_OR_NONE].endsWith('-');
1589
+ }
1590
+ let month = 1; // Defaults to Jan 1
1591
+ let day = 1; // Defaults to Jan 1
1592
+ if (year !== undefined && matches[INDEX_MONTH_MONTHDATE_WEEK_ORDINAL]) {
1593
+ isLeapYear = new Date(Date.UTC(year, 1, 29, 0, 0, 0, 0)).getUTCDate() === 29;
1594
+ isDateExtendedFormat =
1595
+ isDateExtendedFormat ||
1596
+ matches[INDEX_MONTH_MONTHDATE_WEEK_ORDINAL].indexOf('-') >= 0;
1597
+ if (matches[INDEX_MONTH]) {
1598
+ // Only extended form YYYY-MM is allowed, not YYYYMM
1599
+ if (!matches[INDEX_YEAR_OR_NONE].endsWith('-')) {
1600
+ throw new Error("Unparseable date '" + value + "'");
1601
+ }
1602
+ // Calendar date, with date omitted
1603
+ month = Number(matches[INDEX_MONTH]); // MM : month part
1604
+ day = 1;
1605
+ isFullDate = false;
1606
+ }
1607
+ else if (matches[INDEX_MONTHDATE]) {
1608
+ // Calendar date
1609
+ month = Number(matches[INDEX_MONTHDATE]); // MM : month part
1610
+ day = Number(matches[INDEX_MONTHDATE + 1]); // dd : date part
1611
+ }
1612
+ else if (matches[INDEX_WEEK]) {
1613
+ const week = Number(matches[INDEX_WEEK + 1]); // ww : week part
1614
+ let dayOfWeek = 1; // 1: monday, 7: sunday
1615
+ if (matches[INDEX_WEEK + 2]) {
1616
+ dayOfWeek = Number(matches[INDEX_WEEK + 3]); // E : day of week part is optional
1617
+ }
1618
+ else {
1619
+ isFullDate = false;
1620
+ }
1621
+ // Validate week/dayOfWeek
1622
+ if (!isValidWeek(week) || !isValidDayOfWeek(dayOfWeek)) {
1623
+ throw new Error("Unparseable date '" + value + "'");
1624
+ }
1625
+ // Calculate month and date from Www-E
1626
+ const jan1 = new Date(Date.UTC(year, 0, 1, 0, 0, 0, 0));
1627
+ // Date#getDay|getUTCDay returns 0: sunday, 6: saturday
1628
+ const jan1Day = jan1.getUTCDay();
1629
+ // If 1 January is on a Monday, Tuesday, Wednesday or Thursday, it is in week 01.
1630
+ // Else 1 January is on last week of previous year (week 52 or 53)
1631
+ // So, to summarize (jan1Day: W01-1 date) is:
1632
+ // 1: Jan 1
1633
+ // 2: Dec 31
1634
+ // 3: Dec 30
1635
+ // 4: Dec 29
1636
+ // 5: Jan 4
1637
+ // 6: Jan 3
1638
+ // 0: Jan 2
1639
+ // Date constructor allows overflow of date and correctly recalculate the upper parts (month, year)
1640
+ // Source is kept verbose. This is basically doing this:
1641
+ // const weekDate = (jan1Day === 2 || jan1Day === 3 || jan1Day === 4) ? //
1642
+ // new Date(Date.UTC(year - 1, 11, 7 * (week - 1) + (33 - jan1Day) + (dayOfWeek - 1), 0, 0, 0, 0)) : //
1643
+ // new Date(Date.UTC(year, 0, 7 * (week - 1) + ((9 - jan1Day) % 7) + (dayOfWeek - 1), 0, 0, 0, 0));
1644
+ const week1Day1Year = jan1Day === 2 || jan1Day === 3 || jan1Day === 4 ? year - 1 : year;
1645
+ const week1Day1Month = jan1Day === 2 || jan1Day === 3 || jan1Day === 4 ? 12 : 1;
1646
+ const week1Day1Date = jan1Day === 2 || jan1Day === 3 || jan1Day === 4
1647
+ ? 33 - jan1Day
1648
+ : (9 - jan1Day) % 7;
1649
+ // Date constructor allows overflow of date and correctly recalculate the upper parts (month, year)
1650
+ const weekDate = new Date(Date.UTC(week1Day1Year, week1Day1Month - 1, week1Day1Date + 7 * (week - 1) + (dayOfWeek - 1), 0, 0, 0, 0));
1651
+ // Override year, month and date
1652
+ year = weekDate.getUTCFullYear();
1653
+ month = weekDate.getUTCMonth() + 1;
1654
+ day = weekDate.getUTCDate();
1655
+ }
1656
+ else if (matches[INDEX_ORDINAL]) {
1657
+ // Ordinal dates
1658
+ // 1: Jan 1, 365|366: Dec 31
1659
+ const ordinal = Number(matches[INDEX_ORDINAL]); // DDD : ordinal day part
1660
+ // Validate ordinal
1661
+ if (!isValidOrdinal(ordinal, isLeapYear)) {
1662
+ throw new Error("Unparseable date '" + value + "'");
1663
+ }
1664
+ // Date constructor allows overflow of date and correctly recalculate the upper parts (month, year)
1665
+ const ordinalDate = new Date(Date.UTC(year, 0, ordinal, 0, 0, 0, 0));
1666
+ // Override month and date
1667
+ month = ordinalDate.getUTCMonth() + 1;
1668
+ day = ordinalDate.getUTCDate();
1669
+ }
1670
+ }
1671
+ else if (year === undefined &&
1672
+ matches[INDEX_MONTH_MONTHDATE_WEEK_ORDINAL] &&
1673
+ matches[INDEX_MONTHDATE]) {
1674
+ // Only --MM-dd and --MMdd representation do not have year defined.
1675
+ // We support this pattern by using 2000 as year since it is a leap year (to allow --02-29).
1676
+ // We can default lesser significant part (e.g. month, date, hours) and have a sensibly usable value,
1677
+ // but, this representation is not suitable for JS Date since the significant part (year) of a calendar date is missing.
1678
+ // Calendar date
1679
+ year = 2000; // year defaults 2000
1680
+ month = Number(matches[INDEX_MONTHDATE]); // MM : month part
1681
+ day = Number(matches[INDEX_MONTHDATE + 1]); // dd : date part
1682
+ isLeapYear = new Date(Date.UTC(year, 1, 29, 0, 0, 0, 0)).getUTCDate() === 29;
1683
+ isFullDate = false;
1684
+ isDateExtendedFormat =
1685
+ isDateExtendedFormat ||
1686
+ matches[INDEX_MONTH_MONTHDATE_WEEK_ORDINAL].indexOf('-') >= 0;
1687
+ }
1688
+ else {
1689
+ throw new Error("Unparseable date '" + value + "'");
1690
+ }
1691
+ if (!isValidDate(month, day, isLeapYear)) {
1692
+ throw new Error("Unparseable date '" + value + "'");
1693
+ }
1694
+ // Time parts
1695
+ let hour = 0;
1696
+ let minute = 0;
1697
+ let second = 0;
1698
+ let millisecond = 0;
1699
+ let isTimeExtendedFormat = false;
1700
+ let isHourOnly = true;
1701
+ // matches[INDEX_TIME] examples: 'T12:34:56.789+10:00', 'T00:00Z'
1702
+ if (matches[INDEX_TIME] !== undefined) {
1703
+ if (!isFullDate) {
1704
+ throw new Error("Unparseable date '" + value + "'");
1705
+ }
1706
+ hour = Number(matches[INDEX_TIME + 1]); // hh : hour part is required
1707
+ if (matches[INDEX_MINUTE]) {
1708
+ minute = Number(matches[INDEX_MINUTE + 1]); // mm : minute part is optional
1709
+ isTimeExtendedFormat =
1710
+ isTimeExtendedFormat || matches[INDEX_MINUTE].startsWith(':');
1711
+ isHourOnly = false;
1712
+ }
1713
+ if (matches[INDEX_SECOND] !== undefined) {
1714
+ second = Number(matches[INDEX_SECOND + 1]); // ss : second part is optional
1715
+ isTimeExtendedFormat =
1716
+ isTimeExtendedFormat || matches[INDEX_SECOND].startsWith(':');
1717
+ isHourOnly = false;
1718
+ }
1719
+ if (matches[INDEX_MILLISECOND] !== undefined) {
1720
+ millisecond = Number(matches[INDEX_MILLISECOND + 1]); // SSS : millisecond part is optional
1721
+ // period on millisecond is required on both basic and extended format
1722
+ }
1723
+ // matches[INDEX_TZ] examples: 'Z', '+09:00', '-1100', '+09', '-11'
1724
+ if (matches[INDEX_TZ] !== undefined) {
1725
+ // Only adjust offset if this is not UTC
1726
+ if (matches[INDEX_TZ] !== 'Z') {
1727
+ let offset = Number(matches[INDEX_TZ + 2]) * 60; // offset hour part is required for non 'Z' offset
1728
+ if (matches[INDEX_TZ_MINUTE] !== undefined) {
1729
+ // offset minute part is optional
1730
+ offset += Number(matches[INDEX_TZ_MINUTE + 1]);
1731
+ }
1732
+ if (matches[INDEX_TZ + 1] === '+') {
1733
+ // plus/minus is required for non 'Z' offset
1734
+ // If offset is positive, substract offset from minutes
1735
+ // e.g. 11am in +09:00 is 2am in UTC (11:00 - 09:00)
1736
+ offset = -offset;
1737
+ }
1738
+ minute += offset;
1739
+ }
1740
+ }
1741
+ // Combined date and time must have the same format
1742
+ // Hour only is valid, and the same for both basic and extended format
1743
+ if (isDateExtendedFormat !== isTimeExtendedFormat && !isHourOnly) {
1744
+ throw new Error("Unparseable date '" + value + "'");
1745
+ }
1746
+ }
1747
+ // Date is instantiated with runtime timezone offset.
1748
+ // We use Date.UTC to generate a timestamp and use that to instantiate the Date instance.
1749
+ const timestamp = Date.UTC(year, month - 1, day, hour, minute, second, millisecond);
1750
+ return new Date(timestamp);
1751
+ }
1752
+ throw new Error("Unparseable date '" + value + "'");
1753
+ }
1754
+ /**
1755
+ * Parse an ISO-8601 datetime string representation to a Date object, or throw on unparseable value.
1756
+ *
1757
+ * This parser adheres to ISO-8601:2019, where the time separator 'T' is required, and full date part is required, and minute/second/millisecond is optional.
1758
+ *
1759
+ * Time defaults to 00:00:00.000Z if not specified.
1760
+ *
1761
+ * Timezone defaults to UTC if not specified.
1762
+ *
1763
+ * Only calendar (e.g. 2021-01-07) representation is supported for date part.
1764
+ * Week (e.g. 2021-W01-5 for 2021-01-07) or ordinal (e.g. 2021-007 for 2021-01-07) representations are not supported.
1765
+ */
1766
+ class DateTimeIso8601Parse {
1767
+ /**
1768
+ * Constructor
1769
+ *
1770
+ * @constructor
1771
+ * @param options Options
1772
+ */
1773
+ constructor() { }
1774
+ /**
1775
+ * Parse an ISO-8601 datetime string representation to a Date object, or throw on unparseable value.
1776
+ * If parser instance constructed with a timeZone option, returned Date instance will be offsetted to that time zone.
1777
+ *
1778
+ * @param value {string} Value to parse
1779
+ * @returns {Date} Date instance
1780
+ */
1781
+ parse(value) {
1782
+ return parseDateTimeIsoString(value);
1783
+ }
1784
+ }
1785
+
1786
+ const iso8601Parser = new DateTimeIso8601Parse();
1787
+ /**
1788
+ * Instantiate or returns a cached value of a CLDR-based date time parser, instantiated with given a parser options.
1789
+ *
1790
+ * @param options {DateTimeIso8601ParseOptions} Parser options
1791
+ * @returns {DateTimeParse} Parser instance
1792
+ */
1793
+ function getDateTimeISO8601Parser() {
1794
+ return iso8601Parser;
1795
+ }
1796
+
1797
+ /**
1798
+ * Localizer
1799
+ *
1800
+ * @author aazizy
1801
+ */
1802
+ function clearCache() {
1803
+ clearDateTimeFormatCache();
1804
+ clearDateTimeCLDRParserCache();
1805
+ clearRelativeTimeFormatCache();
1806
+ clearNumberFormatCache();
1807
+ clearNumberParserCache();
1808
+ }
1809
+
1810
+ export { clearCache, getDateTimeCLDRParser, getDateTimeFormat, getDateTimeISO8601Parser, getNumberFormat, getNumberParser, getRelativeTimeFormat };