@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
- const partsToPattern = (parts, resolved, requestedOpts) => {
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 hasYear = !!requestedOpts.year;
12
- const hasMonth = !!requestedOpts.month;
13
- const hasDay = !!requestedOpts.day;
14
- const hasWeekday = !!requestedOpts.weekday;
15
- const hasTextMonth = requestedOpts.month === 'short' || requestedOpts.month === 'long';
16
- // "Whole numeric date" = year+month+day, all numeric, no weekday, no text month
17
- const isWholeNumericDate = hasYear && hasMonth && hasDay && !hasWeekday && !hasTextMonth;
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
- // Keep YY when the locale actually resolved to 2-digit
22
- if (resolved.year === '2-digit') {
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
- // textual month
32
- if (requestedOpts.month === 'short' || requestedOpts.month === 'long') {
33
- pattern += requestedOpts.month === 'long' ? 'MMMM' : 'MMM';
35
+ // Textual month first
36
+ if (opts.month === 'short') {
37
+ pattern += 'MMM';
34
38
  break;
35
39
  }
36
- if (isWholeNumericDate) {
37
- // whole numeric date → always use M (accepts 1–2 digits)
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
- // other numeric month cases (e.g. yearmonth or month–day)
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
- if (isWholeNumericDate) {
52
- // whole numeric date → always use D (accepts 1–2 digits)
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 (requestedOpts.weekday === 'short' || requestedOpts.weekday === 'narrow') {
69
+ if (opts.weekday === 'short' || opts.weekday === 'narrow') {
61
70
  pattern += 'ddd';
62
71
  }
63
- else if (requestedOpts.weekday === 'long') {
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 normalizeNumericDM(fmt, str);
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbn/bbn",
3
- "version": "2.0.61",
3
+ "version": "2.0.62",
4
4
  "description": "Javascript toolkit",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",