@bbn/bbn 2.0.61 → 2.0.62
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.
|
@@ -3,23 +3,27 @@ import extend from "../../fn/object/extend.js";
|
|
|
3
3
|
* Build a token pattern (YYYY, MM, DD, dddd, HH, II, SS, A, z) from Intl parts.
|
|
4
4
|
* Uses Intl options to distinguish MMM vs MMMM, ddd vs dddd, etc.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
function partsToPattern(parts, hourCycle, opts) {
|
|
7
7
|
let pattern = '';
|
|
8
|
-
const hourCycle = resolved.hourCycle;
|
|
9
8
|
const hasDayPeriod = parts.some(p => p.type === 'dayPeriod');
|
|
10
9
|
const is12h = hasDayPeriod || hourCycle === 'h12' || hourCycle === 'h11';
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
const hasWeekday = !!opts.weekday;
|
|
11
|
+
const hasTextMonth = opts.month === 'short' || opts.month === 'long';
|
|
12
|
+
// when we *request* year/month/day in "numeric" (not 2-digit, not short),
|
|
13
|
+
// and all three are present, then we want D/M/YYYY in our mask,
|
|
14
|
+
// in the locale's order, even if the sample shows "02" / "01".
|
|
15
|
+
const requestedPureNumericYMD = opts.year === 'numeric' &&
|
|
16
|
+
opts.month === 'numeric' &&
|
|
17
|
+
opts.day === 'numeric' &&
|
|
18
|
+
!hasWeekday &&
|
|
19
|
+
!hasTextMonth;
|
|
18
20
|
for (const p of parts) {
|
|
19
21
|
switch (p.type) {
|
|
20
22
|
case 'year': {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
+
// If you want YY vs YYYY disambiguation you can still
|
|
24
|
+
// look at opts.year here; I’ll keep it simple:
|
|
25
|
+
// numeric → YYYY, '2-digit' → YY
|
|
26
|
+
if (opts.year === '2-digit') {
|
|
23
27
|
pattern += 'YY';
|
|
24
28
|
}
|
|
25
29
|
else {
|
|
@@ -28,28 +32,33 @@ const partsToPattern = (parts, resolved, requestedOpts) => {
|
|
|
28
32
|
break;
|
|
29
33
|
}
|
|
30
34
|
case 'month': {
|
|
31
|
-
//
|
|
32
|
-
if (
|
|
33
|
-
pattern +=
|
|
35
|
+
// Textual month first
|
|
36
|
+
if (opts.month === 'short') {
|
|
37
|
+
pattern += 'MMM';
|
|
34
38
|
break;
|
|
35
39
|
}
|
|
36
|
-
if (
|
|
37
|
-
|
|
40
|
+
if (opts.month === 'long') {
|
|
41
|
+
pattern += 'MMMM';
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
// 👉 Pure numeric Y-M-D requested: always M
|
|
45
|
+
if (requestedPureNumericYMD) {
|
|
38
46
|
pattern += 'M';
|
|
39
47
|
break;
|
|
40
48
|
}
|
|
41
|
-
//
|
|
49
|
+
// Other numeric month cases (year-month, month-day, or 2-digit)
|
|
42
50
|
if (/^\d+$/.test(p.value)) {
|
|
43
51
|
pattern += p.value.length === 2 ? 'MM' : 'M';
|
|
44
52
|
}
|
|
45
53
|
else {
|
|
54
|
+
// should not happen, but be defensive
|
|
46
55
|
pattern += p.value.length > 3 ? 'MMMM' : 'MMM';
|
|
47
56
|
}
|
|
48
57
|
break;
|
|
49
58
|
}
|
|
50
59
|
case 'day': {
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
// 👉 Pure numeric Y-M-D requested: always D
|
|
61
|
+
if (requestedPureNumericYMD) {
|
|
53
62
|
pattern += 'D';
|
|
54
63
|
break;
|
|
55
64
|
}
|
|
@@ -57,10 +66,10 @@ const partsToPattern = (parts, resolved, requestedOpts) => {
|
|
|
57
66
|
break;
|
|
58
67
|
}
|
|
59
68
|
case 'weekday': {
|
|
60
|
-
if (
|
|
69
|
+
if (opts.weekday === 'short' || opts.weekday === 'narrow') {
|
|
61
70
|
pattern += 'ddd';
|
|
62
71
|
}
|
|
63
|
-
else if (
|
|
72
|
+
else if (opts.weekday === 'long') {
|
|
64
73
|
pattern += 'dddd';
|
|
65
74
|
}
|
|
66
75
|
else {
|
|
@@ -90,20 +99,15 @@ const partsToPattern = (parts, resolved, requestedOpts) => {
|
|
|
90
99
|
pattern += 'z';
|
|
91
100
|
break;
|
|
92
101
|
case 'literal': {
|
|
93
|
-
if (!p.value)
|
|
102
|
+
if (!p.value)
|
|
94
103
|
break;
|
|
95
|
-
|
|
96
|
-
// If the literal contains any letter (ASCII or basic Latin-1),
|
|
97
|
-
// wrap it in [ ... ] so it can't be confused with tokens.
|
|
98
|
-
// Otherwise (spaces, /, -, :, commas, etc.) keep it raw.
|
|
104
|
+
// Only wrap *text* literals in [ ... ], not separators like /, -, spaces, etc.
|
|
99
105
|
const hasLetter = /[A-Za-zÀ-ÖØ-öø-ÿ]/.test(p.value);
|
|
100
106
|
if (hasLetter) {
|
|
101
|
-
// Escape ']' inside the bracketed literal
|
|
102
107
|
const v = p.value.replace(/]/g, '\\]');
|
|
103
108
|
pattern += `[${v}]`;
|
|
104
109
|
}
|
|
105
110
|
else {
|
|
106
|
-
// Non-problematic punctuation/whitespace: just append as-is
|
|
107
111
|
pattern += p.value;
|
|
108
112
|
}
|
|
109
113
|
break;
|
|
@@ -114,7 +118,8 @@ const partsToPattern = (parts, resolved, requestedOpts) => {
|
|
|
114
118
|
}
|
|
115
119
|
}
|
|
116
120
|
return pattern;
|
|
117
|
-
}
|
|
121
|
+
}
|
|
122
|
+
;
|
|
118
123
|
/**
|
|
119
124
|
* Get a curated set of *common* date, time and datetime formats
|
|
120
125
|
* for the given locale, without exploding into thousands of combos.
|
|
@@ -162,7 +167,7 @@ const getCommonFormatsForLocale = (lng) => {
|
|
|
162
167
|
const fmt = new Intl.DateTimeFormat(lng, opts);
|
|
163
168
|
const parts = fmt.formatToParts(sample);
|
|
164
169
|
const resolved = fmt.resolvedOptions();
|
|
165
|
-
const pattern = partsToPattern(parts, resolved, opts);
|
|
170
|
+
const pattern = partsToPattern(parts, resolved.hourCycle, opts);
|
|
166
171
|
if (!seenDatePatterns.has(pattern)) {
|
|
167
172
|
seenDatePatterns.add(pattern);
|
|
168
173
|
date.push({
|
|
@@ -189,7 +194,7 @@ const getCommonFormatsForLocale = (lng) => {
|
|
|
189
194
|
const fmt = new Intl.DateTimeFormat(lng, opts);
|
|
190
195
|
const parts = fmt.formatToParts(sample);
|
|
191
196
|
const resolved = fmt.resolvedOptions();
|
|
192
|
-
const pattern = partsToPattern(parts, resolved, opts);
|
|
197
|
+
const pattern = partsToPattern(parts, resolved.hourCycle, opts);
|
|
193
198
|
if (!seenTimePatterns.has(pattern)) {
|
|
194
199
|
seenTimePatterns.add(pattern);
|
|
195
200
|
time.push({
|
|
@@ -206,7 +211,7 @@ const getCommonFormatsForLocale = (lng) => {
|
|
|
206
211
|
const fmt = new Intl.DateTimeFormat(lng, opts);
|
|
207
212
|
const parts = fmt.formatToParts(sample);
|
|
208
213
|
const resolved = fmt.resolvedOptions();
|
|
209
|
-
const pattern = partsToPattern(parts, resolved, opts);
|
|
214
|
+
const pattern = partsToPattern(parts, resolved.hourCycle, opts);
|
|
210
215
|
if (!seenDateTimePatterns.has(pattern)) {
|
|
211
216
|
seenDateTimePatterns.add(pattern);
|
|
212
217
|
datetime.push({
|
|
@@ -30,54 +30,6 @@ const MYSQL_AND_NATIVE_FORMATS = [
|
|
|
30
30
|
// Tue Oct 29 2024 14:30:00
|
|
31
31
|
'ddd MMM DD YYYY HH:II:SS'
|
|
32
32
|
];
|
|
33
|
-
const isPureNumericDateFormat = (fmt) => {
|
|
34
|
-
// Only Y/M/D tokens and literal separators, no time or AM/PM tokens
|
|
35
|
-
if (/[HhI SAz]/.test(fmt)) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
// Must have year and month and day tokens
|
|
39
|
-
if (!/[Y]/.test(fmt) || !/[M]/.test(fmt) || !/[D]/.test(fmt)) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
return true;
|
|
43
|
-
};
|
|
44
|
-
const makeRelaxedNumericFormat = (fmt) => {
|
|
45
|
-
// Relax DD -> D and MM -> M, but don't touch other tokens
|
|
46
|
-
return fmt.replace(/DD/g, 'D').replace(/MM/g, 'M');
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* If the format is a pure numeric date like D/M/YYYY or DD/MM/YYYY,
|
|
50
|
-
* and the input clearly uses 2-digit day and 2-digit month (22/11/2022),
|
|
51
|
-
* upgrade to DD/MM/YYYY.
|
|
52
|
-
*/
|
|
53
|
-
const normalizeNumericDM = (fmt, input) => {
|
|
54
|
-
// Only touch "pure numeric date" patterns: D/M/YYYY, DD-MM-YY, etc.
|
|
55
|
-
// No time tokens, no text months, no weekdays.
|
|
56
|
-
if (/[HhI SAzM]{2,}|[A-Za-z]/.test(fmt.replace(/[DMY]/g, ''))) {
|
|
57
|
-
// If there are other letters than D/M/Y (like MMM, ddd), don't touch.
|
|
58
|
-
// (We only want simple numeric dates)
|
|
59
|
-
return fmt;
|
|
60
|
-
}
|
|
61
|
-
// Quick check: must contain D and M and Y
|
|
62
|
-
if (!fmt.includes('D') || !fmt.includes('M') || !fmt.includes('Y')) {
|
|
63
|
-
return fmt;
|
|
64
|
-
}
|
|
65
|
-
// Extract numeric chunks from the input: ["22", "11", "2022"] for "22/11/2022"
|
|
66
|
-
const nums = input.split(/\D+/).filter(Boolean);
|
|
67
|
-
if (nums.length < 3) {
|
|
68
|
-
return fmt;
|
|
69
|
-
}
|
|
70
|
-
const [dayStr, monthStr] = nums;
|
|
71
|
-
// Only upgrade if both day and month are exactly 2-digit
|
|
72
|
-
if (dayStr.length === 2 && monthStr.length === 2) {
|
|
73
|
-
// Upgrade first D-group to DD and first M-group to MM
|
|
74
|
-
let out = fmt;
|
|
75
|
-
out = out.replace(/D+/, 'DD');
|
|
76
|
-
out = out.replace(/M+/, 'MM');
|
|
77
|
-
return out;
|
|
78
|
-
}
|
|
79
|
-
return fmt;
|
|
80
|
-
};
|
|
81
33
|
export default function guessFormat(input, formats, lng) {
|
|
82
34
|
const str = input.trim();
|
|
83
35
|
if (!str) {
|
|
@@ -89,24 +41,11 @@ export default function guessFormat(input, formats, lng) {
|
|
|
89
41
|
// 1) Try strict format first
|
|
90
42
|
try {
|
|
91
43
|
parse(str, fmt);
|
|
92
|
-
return
|
|
44
|
+
return fmt;
|
|
93
45
|
}
|
|
94
46
|
catch (_a) {
|
|
95
47
|
// ignore, we'll maybe try a relaxed version
|
|
96
48
|
}
|
|
97
|
-
// 2) If it's a pure numeric date pattern, try a relaxed version too
|
|
98
|
-
if (isPureNumericDateFormat(fmt)) {
|
|
99
|
-
const relaxed = makeRelaxedNumericFormat(fmt);
|
|
100
|
-
if (relaxed !== fmt) {
|
|
101
|
-
try {
|
|
102
|
-
parse(str, relaxed);
|
|
103
|
-
return relaxed;
|
|
104
|
-
}
|
|
105
|
-
catch (_b) {
|
|
106
|
-
// still nothing, move on
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
49
|
}
|
|
111
50
|
return null;
|
|
112
51
|
};
|