@bbn/bbn 2.0.34 → 2.0.36
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.
|
@@ -16,15 +16,19 @@ type CommonFormats = {
|
|
|
16
16
|
}>;
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* - weekday / year / month / day
|
|
22
|
-
* - hour / minute / second / timeZoneName
|
|
19
|
+
* Get a curated set of *common* date, time and datetime formats
|
|
20
|
+
* for the given locale, without exploding into thousands of combos.
|
|
23
21
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* Rules:
|
|
23
|
+
* - Date: only sensible combos (Y-M-D ± weekday, Y-M, M-D).
|
|
24
|
+
* - Time: hour / hour:minute / hour:minute:second (+ optional TZ).
|
|
25
|
+
* - Datetime: only full dates (Y-M-D ± weekday) combined with time.
|
|
26
|
+
*
|
|
27
|
+
* Fully numeric date forms (like "1/1/1970") are explicitly included via:
|
|
28
|
+
* { year: 'numeric', month: 'numeric', day: 'numeric' }
|
|
29
|
+
* { year: '2-digit', month: 'numeric', day: 'numeric' }
|
|
30
|
+
* { year: 'numeric', month: '2-digit', day: '2-digit' }
|
|
31
|
+
* { year: '2-digit', month: '2-digit', day: '2-digit' }
|
|
28
32
|
*/
|
|
29
33
|
export declare function getCommonFormatsForLocale(lng: string | string[]): CommonFormats;
|
|
30
34
|
export default function buildLocaleFromIntl(): void;
|
|
@@ -2,8 +2,11 @@ import extend from "../../fn/object/extend.js";
|
|
|
2
2
|
import numProperties from "../../fn/object/numProperties.js";
|
|
3
3
|
/**
|
|
4
4
|
* Build a token pattern (YYYY, MM, DD, dddd, HH, II, SS, A, z) from Intl parts.
|
|
5
|
+
* Uses Intl options to distinguish MMM vs MMMM, ddd vs dddd, etc.
|
|
6
|
+
* All literal chunks are wrapped in square brackets so they can't be mistaken
|
|
7
|
+
* for tokens by the parser.
|
|
5
8
|
*/
|
|
6
|
-
function partsToPattern(parts, hourCycle) {
|
|
9
|
+
function partsToPattern(parts, hourCycle, opts) {
|
|
7
10
|
let pattern = '';
|
|
8
11
|
const hasDayPeriod = parts.some(p => p.type === 'dayPeriod');
|
|
9
12
|
const is12h = hasDayPeriod || hourCycle === 'h12' || hourCycle === 'h11';
|
|
@@ -13,18 +16,39 @@ function partsToPattern(parts, hourCycle) {
|
|
|
13
16
|
pattern += 'YYYY';
|
|
14
17
|
break;
|
|
15
18
|
case 'month':
|
|
16
|
-
if (
|
|
17
|
-
pattern +=
|
|
19
|
+
if (opts.month === 'short') {
|
|
20
|
+
pattern += 'MMM';
|
|
21
|
+
}
|
|
22
|
+
else if (opts.month === 'long') {
|
|
23
|
+
pattern += 'MMMM';
|
|
24
|
+
}
|
|
25
|
+
else if (opts.month === 'numeric' || opts.month === '2-digit') {
|
|
26
|
+
// numeric month
|
|
27
|
+
pattern += /^\d{2}$/.test(p.value) ? 'MM' : 'M';
|
|
18
28
|
}
|
|
19
29
|
else {
|
|
20
|
-
|
|
30
|
+
// Fallback: infer from value
|
|
31
|
+
if (/^\d+$/.test(p.value)) {
|
|
32
|
+
pattern += p.value.length === 2 ? 'MM' : 'M';
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
pattern += p.value.length > 3 ? 'MMMM' : 'MMM';
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
38
|
break;
|
|
23
39
|
case 'day':
|
|
24
40
|
pattern += p.value.length === 2 ? 'DD' : 'D';
|
|
25
41
|
break;
|
|
26
42
|
case 'weekday':
|
|
27
|
-
|
|
43
|
+
if (opts.weekday === 'short' || opts.weekday === 'narrow') {
|
|
44
|
+
pattern += 'ddd';
|
|
45
|
+
}
|
|
46
|
+
else if (opts.weekday === 'long') {
|
|
47
|
+
pattern += 'dddd';
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
pattern += p.value.length > 3 ? 'dddd' : 'ddd';
|
|
51
|
+
}
|
|
28
52
|
break;
|
|
29
53
|
case 'hour':
|
|
30
54
|
if (is12h) {
|
|
@@ -46,8 +70,16 @@ function partsToPattern(parts, hourCycle) {
|
|
|
46
70
|
case 'timeZoneName':
|
|
47
71
|
pattern += 'z';
|
|
48
72
|
break;
|
|
49
|
-
case 'literal':
|
|
73
|
+
case 'literal': {
|
|
74
|
+
// Wrap ALL literals into [ ... ] to avoid confusion with tokens
|
|
75
|
+
if (p.value.length) {
|
|
76
|
+
const v = p.value.replace(/]/g, '\\]');
|
|
77
|
+
pattern += `[${v}]`;
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
50
81
|
default:
|
|
82
|
+
// Fallback, should be rare
|
|
51
83
|
pattern += p.value;
|
|
52
84
|
break;
|
|
53
85
|
}
|
|
@@ -55,152 +87,111 @@ function partsToPattern(parts, hourCycle) {
|
|
|
55
87
|
return pattern;
|
|
56
88
|
}
|
|
57
89
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
90
|
+
* Get a curated set of *common* date, time and datetime formats
|
|
91
|
+
* for the given locale, without exploding into thousands of combos.
|
|
92
|
+
*
|
|
93
|
+
* Rules:
|
|
94
|
+
* - Date: only sensible combos (Y-M-D ± weekday, Y-M, M-D).
|
|
95
|
+
* - Time: hour / hour:minute / hour:minute:second (+ optional TZ).
|
|
96
|
+
* - Datetime: only full dates (Y-M-D ± weekday) combined with time.
|
|
62
97
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* -
|
|
66
|
-
*
|
|
98
|
+
* Fully numeric date forms (like "1/1/1970") are explicitly included via:
|
|
99
|
+
* { year: 'numeric', month: 'numeric', day: 'numeric' }
|
|
100
|
+
* { year: '2-digit', month: 'numeric', day: 'numeric' }
|
|
101
|
+
* { year: 'numeric', month: '2-digit', day: '2-digit' }
|
|
102
|
+
* { year: '2-digit', month: '2-digit', day: '2-digit' }
|
|
67
103
|
*/
|
|
68
104
|
export function getCommonFormatsForLocale(lng) {
|
|
69
|
-
// Fixed sample: 2 Jan 2000, 13:45:30 UTC
|
|
70
105
|
const sample = new Date(Date.UTC(2000, 0, 2, 13, 45, 30));
|
|
71
106
|
const date = [];
|
|
72
107
|
const time = [];
|
|
73
108
|
const datetime = [];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
'numeric',
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'numeric',
|
|
94
|
-
'
|
|
109
|
+
const seenDatePatterns = new Set();
|
|
110
|
+
const seenTimePatterns = new Set();
|
|
111
|
+
const seenDateTimePatterns = new Set();
|
|
112
|
+
// ---- 1) DATE: curated list of useful patterns ----
|
|
113
|
+
// Numeric full dates (this covers "1/1/1970"-style formats as masks like D/M/YYYY or DD/MM/YYYY).
|
|
114
|
+
const dateOptionsList = [
|
|
115
|
+
// Fully numeric YYYY-M-D variants
|
|
116
|
+
{ year: 'numeric', month: 'numeric', day: 'numeric' },
|
|
117
|
+
{ year: 'numeric', month: '2-digit', day: '2-digit' },
|
|
118
|
+
{ year: '2-digit', month: 'numeric', day: 'numeric' },
|
|
119
|
+
{ year: '2-digit', month: '2-digit', day: '2-digit' },
|
|
120
|
+
// Full dates with textual month
|
|
121
|
+
{ year: 'numeric', month: 'short', day: 'numeric' },
|
|
122
|
+
{ year: 'numeric', month: 'long', day: 'numeric' },
|
|
123
|
+
// Full dates with weekday
|
|
124
|
+
{ weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' },
|
|
125
|
+
{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' },
|
|
126
|
+
// Year–month
|
|
127
|
+
{ year: 'numeric', month: 'numeric' },
|
|
128
|
+
{ year: 'numeric', month: '2-digit' },
|
|
129
|
+
{ year: 'numeric', month: 'short' },
|
|
130
|
+
{ year: 'numeric', month: 'long' },
|
|
131
|
+
// Month–day (no year)
|
|
132
|
+
{ month: 'numeric', day: 'numeric' },
|
|
133
|
+
{ month: '2-digit', day: '2-digit' },
|
|
134
|
+
{ month: 'short', day: 'numeric' },
|
|
135
|
+
{ month: 'long', day: 'numeric' }
|
|
95
136
|
];
|
|
96
|
-
const
|
|
97
|
-
for (const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
options.day = day;
|
|
114
|
-
const key = JSON.stringify(options);
|
|
115
|
-
if (seenDateOptions.has(key)) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
seenDateOptions.add(key);
|
|
119
|
-
const fmt = new Intl.DateTimeFormat(lng, options);
|
|
120
|
-
const parts = fmt.formatToParts(sample);
|
|
121
|
-
const resolved = fmt.resolvedOptions();
|
|
122
|
-
const pattern = partsToPattern(parts, resolved.hourCycle);
|
|
123
|
-
dateOptionsList.push(options);
|
|
124
|
-
date.push({
|
|
125
|
-
pattern,
|
|
126
|
-
sample: fmt.format(sample),
|
|
127
|
-
options
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
137
|
+
const fullDateOptions = []; // Y+M+D (± weekday)
|
|
138
|
+
for (const opts of dateOptionsList) {
|
|
139
|
+
const fmt = new Intl.DateTimeFormat(lng, opts);
|
|
140
|
+
const parts = fmt.formatToParts(sample);
|
|
141
|
+
const resolved = fmt.resolvedOptions();
|
|
142
|
+
const pattern = partsToPattern(parts, resolved.hourCycle, opts);
|
|
143
|
+
if (!seenDatePatterns.has(pattern)) {
|
|
144
|
+
seenDatePatterns.add(pattern);
|
|
145
|
+
date.push({
|
|
146
|
+
pattern,
|
|
147
|
+
sample: fmt.format(sample),
|
|
148
|
+
options: opts
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
// keep track of "full dates" (year+month+day) for datetime
|
|
152
|
+
if (opts.year && opts.month && opts.day) {
|
|
153
|
+
fullDateOptions.push(opts);
|
|
131
154
|
}
|
|
132
155
|
}
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
'
|
|
137
|
-
'2-digit'
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
'numeric',
|
|
142
|
-
'2-digit'
|
|
156
|
+
// ---- 2) TIME: curated, valid combos (always have hour, then minute/second) ----
|
|
157
|
+
const timeOptionsList = [
|
|
158
|
+
{ hour: 'numeric' },
|
|
159
|
+
{ hour: '2-digit', minute: '2-digit' },
|
|
160
|
+
{ hour: '2-digit', minute: '2-digit', second: '2-digit' },
|
|
161
|
+
{ hour: '2-digit', minute: '2-digit', timeZoneName: 'short' },
|
|
162
|
+
{ hour: '2-digit', minute: '2-digit', second: '2-digit', timeZoneName: 'short' },
|
|
163
|
+
{ hour: '2-digit', minute: '2-digit', timeZoneName: 'long' }
|
|
143
164
|
];
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// - if we have second, we must have minute
|
|
157
|
-
if (second && !minute) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
const options = { hour };
|
|
161
|
-
if (minute)
|
|
162
|
-
options.minute = minute;
|
|
163
|
-
if (second)
|
|
164
|
-
options.second = second;
|
|
165
|
-
if (tzName)
|
|
166
|
-
options.timeZoneName = tzName;
|
|
167
|
-
const key = JSON.stringify(options);
|
|
168
|
-
if (seenTimeOptions.has(key)) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
seenTimeOptions.add(key);
|
|
172
|
-
const fmt = new Intl.DateTimeFormat(lng, options);
|
|
173
|
-
const parts = fmt.formatToParts(sample);
|
|
174
|
-
const resolved = fmt.resolvedOptions();
|
|
175
|
-
const pattern = partsToPattern(parts, resolved.hourCycle);
|
|
176
|
-
timeOptionsList.push(options);
|
|
177
|
-
time.push({
|
|
178
|
-
pattern,
|
|
179
|
-
sample: fmt.format(sample),
|
|
180
|
-
options
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
165
|
+
for (const opts of timeOptionsList) {
|
|
166
|
+
const fmt = new Intl.DateTimeFormat(lng, opts);
|
|
167
|
+
const parts = fmt.formatToParts(sample);
|
|
168
|
+
const resolved = fmt.resolvedOptions();
|
|
169
|
+
const pattern = partsToPattern(parts, resolved.hourCycle, opts);
|
|
170
|
+
if (!seenTimePatterns.has(pattern)) {
|
|
171
|
+
seenTimePatterns.add(pattern);
|
|
172
|
+
time.push({
|
|
173
|
+
pattern,
|
|
174
|
+
sample: fmt.format(sample),
|
|
175
|
+
options: opts
|
|
176
|
+
});
|
|
184
177
|
}
|
|
185
178
|
}
|
|
186
|
-
// ---- 3) DATETIME
|
|
187
|
-
for (const
|
|
188
|
-
for (const
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
if (seenDateTimeOptions.has(key)) {
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
seenDateTimeOptions.add(key);
|
|
195
|
-
const fmt = new Intl.DateTimeFormat(lng, options);
|
|
179
|
+
// ---- 3) DATETIME: only full dates (Y-M-D ± weekday) × time ----
|
|
180
|
+
for (const dOpts of fullDateOptions) {
|
|
181
|
+
for (const tOpts of timeOptionsList) {
|
|
182
|
+
const opts = Object.assign(Object.assign({}, dOpts), tOpts);
|
|
183
|
+
const fmt = new Intl.DateTimeFormat(lng, opts);
|
|
196
184
|
const parts = fmt.formatToParts(sample);
|
|
197
185
|
const resolved = fmt.resolvedOptions();
|
|
198
|
-
const pattern = partsToPattern(parts, resolved.hourCycle);
|
|
199
|
-
|
|
200
|
-
pattern
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
186
|
+
const pattern = partsToPattern(parts, resolved.hourCycle, opts);
|
|
187
|
+
if (!seenDateTimePatterns.has(pattern)) {
|
|
188
|
+
seenDateTimePatterns.add(pattern);
|
|
189
|
+
datetime.push({
|
|
190
|
+
pattern,
|
|
191
|
+
sample: fmt.format(sample),
|
|
192
|
+
options: opts
|
|
193
|
+
});
|
|
194
|
+
}
|
|
204
195
|
}
|
|
205
196
|
}
|
|
206
197
|
return { date, time, datetime };
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Guess a date format string for the given input.
|
|
3
|
-
*
|
|
4
|
-
* - If `formats` is provided, it will try those formats in order and return
|
|
5
|
-
* the first one that successfully parses.
|
|
6
|
-
* - If `formats` is not provided, it will try a set of built-in common formats
|
|
7
|
-
* (MySQL, ISO/JS, EU/US, full-text using bbn.dt.locales).
|
|
8
|
-
* - Returns `null` if no format matches.
|
|
9
|
-
*
|
|
10
|
-
* NOTE: It relies on `this.parse(input, format)` NOT throwing when the format is correct.
|
|
11
|
-
*/
|
|
12
|
-
export default function guessFormat(input: string, formats?: string[] | string): string | null;
|
|
1
|
+
export default function guessFormat(input: string, formats?: string | string[], lng?: string): string | null;
|
|
@@ -1,120 +1,45 @@
|
|
|
1
|
-
import buildLocaleFromIntl from './buildLocaleFromIntl.js';
|
|
2
1
|
import parse from './parse.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* - If `formats` is provided, it will try those formats in order and return
|
|
7
|
-
* the first one that successfully parses.
|
|
8
|
-
* - If `formats` is not provided, it will try a set of built-in common formats
|
|
9
|
-
* (MySQL, ISO/JS, EU/US, full-text using bbn.dt.locales).
|
|
10
|
-
* - Returns `null` if no format matches.
|
|
11
|
-
*
|
|
12
|
-
* NOTE: It relies on `this.parse(input, format)` NOT throwing when the format is correct.
|
|
13
|
-
*/
|
|
14
|
-
export default function guessFormat(input, formats) {
|
|
15
|
-
var _a;
|
|
2
|
+
import { getCommonFormatsForLocale } from './buildLocaleFromIntl.js';
|
|
3
|
+
export default function guessFormat(input, formats, lng) {
|
|
16
4
|
const str = input.trim();
|
|
17
5
|
if (!str) {
|
|
18
6
|
return null;
|
|
19
7
|
}
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
// helper: try a list of formats with your parse()
|
|
9
|
+
const tryFormats = (fmts) => {
|
|
10
|
+
for (const fmt of fmts) {
|
|
22
11
|
try {
|
|
23
|
-
|
|
24
|
-
parse(str, fmt);
|
|
12
|
+
parse(str, fmt); // will throw if not matching
|
|
25
13
|
return fmt;
|
|
26
14
|
}
|
|
27
15
|
catch (_a) {
|
|
28
|
-
// ignore
|
|
16
|
+
// ignore and continue
|
|
29
17
|
}
|
|
30
18
|
}
|
|
31
19
|
return null;
|
|
32
20
|
};
|
|
33
|
-
//
|
|
21
|
+
// if user provided formats, restrict to those
|
|
34
22
|
if (formats) {
|
|
35
23
|
const list = Array.isArray(formats) ? formats : [formats];
|
|
36
24
|
return tryFormats(list);
|
|
37
25
|
}
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const looksMySQLDateTime = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2}(\.\d+)?)?$/i.test(str);
|
|
57
|
-
const looksMySQLDate = /^\d{4}-\d{2}-\d{2}$/.test(str);
|
|
58
|
-
const looksTimeOnly = /^\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(str);
|
|
59
|
-
// Start building candidate formats (most specific first)
|
|
60
|
-
const candidates = [
|
|
61
|
-
...datetimeFormats.map(f => f.pattern),
|
|
62
|
-
];
|
|
63
|
-
// --- Full-text / locale-based formats ---
|
|
64
|
-
if (hasMonthName || hasWeekdayName) {
|
|
65
|
-
// e.g. "Monday 15 January 2024"
|
|
66
|
-
candidates.push('dddd, DD MMMM YYYY HH:II:SSZ', 'dddd, DD MMMM YYYY HH:II:SS', 'dddd, DD MMMM YYYY', 'DD MMMM YYYY HH:II:SSZ', 'DD MMMM YYYY HH:II:SS', 'DD MMMM YYYY', 'ddd, DD MMM YYYY HH:II:SSZ', 'ddd, DD MMM YYYY HH:II:SS', 'ddd, DD MMM YYYY');
|
|
67
|
-
// JS Date.toString() / toUTCString()-like
|
|
68
|
-
// "Tue Oct 29 2024 14:30:00 GMT+0200"
|
|
69
|
-
candidates.push('ddd MMM DD YYYY HH:II:SSZ', 'ddd, DD MMM YYYY HH:II:SS z');
|
|
70
|
-
}
|
|
71
|
-
// --- ISO / JS-like default formats ---
|
|
72
|
-
if (looksISO || str.includes('T')) {
|
|
73
|
-
candidates.push('YYYY-MM-DDTHH:II:SS.msZ', 'YYYY-MM-DDTHH:II:SSZ', 'YYYY-MM-DDTHH:II:SS.ms', 'YYYY-MM-DDTHH:II:SS', 'YYYY-MM-DDTHH:II:Z', 'YYYY-MM-DDTHH:II');
|
|
74
|
-
}
|
|
75
|
-
// --- MySQL classic formats ---
|
|
76
|
-
if (looksMySQLDateTime) {
|
|
77
|
-
candidates.push('YYYY-MM-DD HH:II:SS.msZ', 'YYYY-MM-DD HH:II:SSZ', 'YYYY-MM-DD HH:II:SS.ms', 'YYYY-MM-DD HH:II:SS', 'YYYY-MM-DD HH:II');
|
|
78
|
-
}
|
|
79
|
-
if (looksMySQLDate) {
|
|
80
|
-
candidates.push('YYYY-MM-DD');
|
|
81
|
-
}
|
|
82
|
-
// --- Time-only strings ---
|
|
83
|
-
if (looksTimeOnly) {
|
|
84
|
-
candidates.push(...timeFormats.map(f => f.pattern), 'HH:II:SS.msZ', 'HH:II:SS.ms', 'HH:II:SS', 'HH:II');
|
|
85
|
-
}
|
|
86
|
-
// --- Common EU / US formats ---
|
|
87
|
-
candidates.push(...dateFormats.map(f => f.pattern),
|
|
88
|
-
// European style
|
|
89
|
-
'DD/MM/YYYY HH:II:SSZ', 'DD/MM/YYYY HH:II:SS', 'DD/MM/YYYY HH:II', 'DD/MM/YYYY', 'DD-MM-YYYY HH:II:SSZ', 'DD-MM-YYYY HH:II:SS', 'DD-MM-YYYY HH:II', 'DD-MM-YYYY',
|
|
90
|
-
// US style
|
|
91
|
-
'MM/DD/YYYY HH:II:SSZ', 'MM/DD/YYYY HH:II:SS', 'MM/DD/YYYY HH:II', 'MM/DD/YYYY',
|
|
92
|
-
// Dot-separated
|
|
93
|
-
'YYYY.MM.DD HH:II:SSZ', 'YYYY.MM.DD HH:II:SS', 'YYYY.MM.DD',
|
|
94
|
-
// MySQL-ish (if we haven't already pushed them by detection)
|
|
95
|
-
'YYYY-MM-DD HH:II:SSZ', 'YYYY-MM-DD HH:II:SS', 'YYYY-MM-DD HH:II', 'YYYY-MM-DD');
|
|
96
|
-
// If we see clear timezone indicators, prioritize formats with Z / z
|
|
97
|
-
if (hasLetterTZ) {
|
|
98
|
-
const withTZ = candidates.filter(f => f.includes('Z') || f.includes('z'));
|
|
99
|
-
const withoutTZ = candidates.filter(f => !f.includes('Z') && !f.includes('z'));
|
|
100
|
-
const reordered = [...withTZ, ...withoutTZ];
|
|
101
|
-
const fmt = tryFormats(reordered);
|
|
102
|
-
if (fmt) {
|
|
103
|
-
return fmt;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
const fmt = tryFormats(candidates);
|
|
108
|
-
if (fmt) {
|
|
109
|
-
return fmt;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// --- Last resort: native JS parsing ---
|
|
113
|
-
const jsDate = new Date(str);
|
|
114
|
-
if (!isNaN(jsDate.getTime())) {
|
|
115
|
-
// You can treat "native" as a special keyword meaning:
|
|
116
|
-
// "use Date/Temporal to parse directly".
|
|
117
|
-
return 'native';
|
|
118
|
-
}
|
|
26
|
+
// autodetect via Intl-derived formats
|
|
27
|
+
const resolvedLocale = lng ||
|
|
28
|
+
(typeof navigator !== 'undefined'
|
|
29
|
+
? navigator.language
|
|
30
|
+
: Intl.DateTimeFormat().resolvedOptions().locale);
|
|
31
|
+
const common = getCommonFormatsForLocale(resolvedLocale);
|
|
32
|
+
const candidates = [];
|
|
33
|
+
// prioritize datetime patterns first, then date, then time
|
|
34
|
+
candidates.push(...common.datetime.map(f => f.pattern), ...common.date.map(f => f.pattern), ...common.time.map(f => f.pattern));
|
|
35
|
+
const fmt = tryFormats(candidates);
|
|
36
|
+
if (fmt) {
|
|
37
|
+
return fmt;
|
|
38
|
+
}
|
|
39
|
+
// last resort: if you *really* want a "native" fallback, do it here
|
|
40
|
+
// const d = new Date(str);
|
|
41
|
+
// if (!Number.isNaN(d.getTime())) {
|
|
42
|
+
// return 'native';
|
|
43
|
+
// }
|
|
119
44
|
return null;
|
|
120
45
|
}
|