@bbn/bbn 2.0.29 → 2.0.31

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.
@@ -1,34 +1 @@
1
- type Style = 'full' | 'long' | 'medium' | 'short';
2
- type CommonFormats = {
3
- date: Array<{
4
- style: Style;
5
- pattern: string;
6
- sample: string;
7
- options: Intl.DateTimeFormatOptions;
8
- }>;
9
- time: Array<{
10
- style: Style;
11
- pattern: string;
12
- sample: string;
13
- options: Intl.DateTimeFormatOptions;
14
- }>;
15
- datetime: Array<{
16
- dateStyle: Style;
17
- timeStyle: Style;
18
- pattern: string;
19
- sample: string;
20
- options: Intl.DateTimeFormatOptions;
21
- }>;
22
- };
23
- /**
24
- * Get all common date/time/datetime formats for a given locale using Intl.DateTimeFormat.
25
- *
26
- * - Date formats: dateStyle only (full/long/medium/short)
27
- * - Time formats: timeStyle only
28
- * - Datetime formats: all combinations of dateStyle × timeStyle
29
- *
30
- * Returns tokens using your convention: YYYY, MM, DD, HH, II, SS, A, etc.
31
- */
32
- export declare function getCommonFormatsForLocale(lng: string | string[]): CommonFormats;
33
1
  export default function buildLocaleFromIntl(): void;
34
- export {};
@@ -1,45 +1,28 @@
1
1
  import extend from "../../fn/object/extend.js";
2
2
  import numProperties from "../../fn/object/numProperties.js";
3
- /**
4
- * Build a token pattern like "DD/MM/YYYY HH:II:SS" from Intl.DateTimeFormat parts.
5
- */
6
3
  function partsToPattern(parts, hourCycle) {
7
4
  let pattern = '';
8
- // If we see a dayPeriod in parts, it's definitely 12-hour clock
9
5
  const hasDayPeriod = parts.some(p => p.type === 'dayPeriod');
10
6
  const is12h = hasDayPeriod || hourCycle === 'h12' || hourCycle === 'h11';
11
7
  for (const p of parts) {
12
8
  switch (p.type) {
13
9
  case 'year':
14
- // Usually "2000" → "YYYY"
15
10
  pattern += 'YYYY';
16
11
  break;
17
12
  case 'month':
18
- if (/^\d+$/.test(p.value)) {
19
- // numeric month
13
+ if (/^\d+$/.test(p.value))
20
14
  pattern += p.value.length === 2 ? 'MM' : 'M';
21
- }
22
- else {
23
- // textual month
15
+ else
24
16
  pattern += p.value.length > 3 ? 'MMMM' : 'MMM';
25
- }
26
17
  break;
27
18
  case 'day':
28
19
  pattern += p.value.length === 2 ? 'DD' : 'D';
29
20
  break;
30
21
  case 'weekday':
31
- // You can refine this if you care about full vs short
32
22
  pattern += p.value.length > 3 ? 'dddd' : 'ddd';
33
23
  break;
34
24
  case 'hour':
35
- if (is12h) {
36
- // 12-hour clock
37
- pattern += p.value.length === 2 ? 'hh' : 'h';
38
- }
39
- else {
40
- // 24-hour clock
41
- pattern += p.value.length === 2 ? 'HH' : 'H';
42
- }
25
+ pattern += is12h ? (p.value.length === 2 ? 'hh' : 'h') : (p.value.length === 2 ? 'HH' : 'H');
43
26
  break;
44
27
  case 'minute':
45
28
  pattern += 'II';
@@ -48,14 +31,14 @@ function partsToPattern(parts, hourCycle) {
48
31
  pattern += 'SS';
49
32
  break;
50
33
  case 'dayPeriod':
51
- // AM/PM
52
34
  pattern += 'A';
53
35
  break;
54
36
  case 'timeZoneName':
55
- // You may want 'z' or 'Z' depending on your conventions
56
37
  pattern += 'z';
57
38
  break;
58
39
  case 'literal':
40
+ pattern += p.value;
41
+ break;
59
42
  default:
60
43
  pattern += p.value;
61
44
  break;
@@ -64,69 +47,92 @@ function partsToPattern(parts, hourCycle) {
64
47
  return pattern;
65
48
  }
66
49
  /**
67
- * Get all common date/time/datetime formats for a given locale using Intl.DateTimeFormat.
68
- *
69
- * - Date formats: dateStyle only (full/long/medium/short)
70
- * - Time formats: timeStyle only
71
- * - Datetime formats: all combinations of dateStyle × timeStyle
72
- *
73
- * Returns tokens using your convention: YYYY, MM, DD, HH, II, SS, A, etc.
50
+ * Returns all common date/time/datetime formats + weekday formats for a given locale.
74
51
  */
75
- export function getCommonFormatsForLocale(lng) {
52
+ function getCommonFormatsForLocale(lng) {
76
53
  const dateStyles = ['full', 'long', 'medium', 'short'];
77
54
  const timeStyles = ['full', 'long', 'medium', 'short'];
78
- // A fixed sample date to generate patterns.
79
- // 2 Jan 2000, 13:45:30 — avoids 01/01 ambiguity and crosses 12h/24h boundaries.
80
- const sampleDate = new Date(Date.UTC(2000, 0, 2, 13, 45, 30));
81
- const date = [];
82
- const time = [];
83
- const datetime = [];
84
- // --- Date-only formats ---
55
+ const sample = new Date(Date.UTC(2000, 0, 2, 13, 45, 30));
56
+ const result = {
57
+ date: [],
58
+ time: [],
59
+ datetime: [],
60
+ dateWithWeekday: [],
61
+ datetimeWithWeekday: []
62
+ };
63
+ // --- Date only ---
85
64
  for (const ds of dateStyles) {
86
65
  const options = { dateStyle: ds };
87
66
  const fmt = new Intl.DateTimeFormat(lng, options);
88
- const parts = fmt.formatToParts(sampleDate);
89
- const pattern = partsToPattern(parts, fmt.resolvedOptions().hourCycle);
90
- date.push({
67
+ const parts = fmt.formatToParts(sample);
68
+ result.date.push({
91
69
  style: ds,
92
- pattern,
93
- sample: fmt.format(sampleDate),
70
+ pattern: partsToPattern(parts, fmt.resolvedOptions().hourCycle),
71
+ sample: fmt.format(sample),
94
72
  options
95
73
  });
96
74
  }
97
- // --- Time-only formats ---
75
+ // --- Time only ---
98
76
  for (const ts of timeStyles) {
99
77
  const options = { timeStyle: ts };
100
78
  const fmt = new Intl.DateTimeFormat(lng, options);
101
- const parts = fmt.formatToParts(sampleDate);
102
- const pattern = partsToPattern(parts, fmt.resolvedOptions().hourCycle);
103
- time.push({
79
+ const parts = fmt.formatToParts(sample);
80
+ result.time.push({
104
81
  style: ts,
105
- pattern,
106
- sample: fmt.format(sampleDate),
82
+ pattern: partsToPattern(parts, fmt.resolvedOptions().hourCycle),
83
+ sample: fmt.format(sample),
107
84
  options
108
85
  });
109
86
  }
110
- // --- Date + time formats (all combinations) ---
87
+ // --- Date + Time ---
111
88
  for (const ds of dateStyles) {
112
89
  for (const ts of timeStyles) {
113
- const options = {
114
- dateStyle: ds,
115
- timeStyle: ts
116
- };
90
+ const options = { dateStyle: ds, timeStyle: ts };
117
91
  const fmt = new Intl.DateTimeFormat(lng, options);
118
- const parts = fmt.formatToParts(sampleDate);
119
- const pattern = partsToPattern(parts, fmt.resolvedOptions().hourCycle);
120
- datetime.push({
92
+ const parts = fmt.formatToParts(sample);
93
+ result.datetime.push({
121
94
  dateStyle: ds,
122
95
  timeStyle: ts,
123
- pattern,
124
- sample: fmt.format(sampleDate),
96
+ pattern: partsToPattern(parts, fmt.resolvedOptions().hourCycle),
97
+ sample: fmt.format(sample),
125
98
  options
126
99
  });
127
100
  }
128
101
  }
129
- return { date, time, datetime };
102
+ // --- Date with Weekday (long + short) ---
103
+ for (const ds of dateStyles) {
104
+ for (const w of ["long", "short"]) {
105
+ const options = { dateStyle: ds, weekday: w };
106
+ const fmt = new Intl.DateTimeFormat(lng, options);
107
+ const parts = fmt.formatToParts(sample);
108
+ result.dateWithWeekday.push({
109
+ style: ds,
110
+ weekday: w,
111
+ pattern: partsToPattern(parts, fmt.resolvedOptions().hourCycle),
112
+ sample: fmt.format(sample),
113
+ options
114
+ });
115
+ }
116
+ }
117
+ // --- Date + Time + Weekday ---
118
+ for (const ds of dateStyles) {
119
+ for (const ts of timeStyles) {
120
+ for (const w of ["long", "short"]) {
121
+ const options = { dateStyle: ds, timeStyle: ts, weekday: w };
122
+ const fmt = new Intl.DateTimeFormat(lng, options);
123
+ const parts = fmt.formatToParts(sample);
124
+ result.datetimeWithWeekday.push({
125
+ dateStyle: ds,
126
+ timeStyle: ts,
127
+ weekday: w,
128
+ pattern: partsToPattern(parts, fmt.resolvedOptions().hourCycle),
129
+ sample: fmt.format(sample),
130
+ options
131
+ });
132
+ }
133
+ }
134
+ }
135
+ return result;
130
136
  }
131
137
  export default function buildLocaleFromIntl() {
132
138
  if (numProperties(bbn.dt.locales)) {
@@ -0,0 +1,12 @@
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;
@@ -0,0 +1,120 @@
1
+ import buildLocaleFromIntl from './buildLocaleFromIntl.js';
2
+ import parse from './parse.js';
3
+ /**
4
+ * Guess a date format string for the given input.
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;
16
+ const str = input.trim();
17
+ if (!str) {
18
+ return null;
19
+ }
20
+ const tryFormats = (formatsToTry) => {
21
+ for (const fmt of formatsToTry) {
22
+ try {
23
+ // We only care that it parses without throwing
24
+ parse(str, fmt);
25
+ return fmt;
26
+ }
27
+ catch (_a) {
28
+ // ignore
29
+ }
30
+ }
31
+ return null;
32
+ };
33
+ // If user provided formats, restrict to those only
34
+ if (formats) {
35
+ const list = Array.isArray(formats) ? formats : [formats];
36
+ return tryFormats(list);
37
+ }
38
+ // -------- Autodetection mode (no user-provided formats) --------
39
+ const lower = str.toLowerCase();
40
+ // Access locales for full-text formats (months / weekdays)
41
+ buildLocaleFromIntl();
42
+ const loc = ((_a = bbn === null || bbn === void 0 ? void 0 : bbn.dt) === null || _a === void 0 ? void 0 : _a.locales) || {};
43
+ const monthsLong = loc.monthsLong || [];
44
+ const monthsShort = loc.monthsShort || [];
45
+ const weekdaysLong = loc.weekdaysLong || [];
46
+ const weekdaysShort = loc.weekdaysShort || [];
47
+ const timeFormats = loc.time || [];
48
+ const dateFormats = loc.date || [];
49
+ const datetimeFormats = loc.datetime || [];
50
+ const hasMonthName = monthsLong.some(m => lower.includes(m.toLowerCase())) ||
51
+ monthsShort.some(m => lower.includes(m.toLowerCase()));
52
+ const hasWeekdayName = weekdaysLong.some(w => lower.includes(w.toLowerCase())) ||
53
+ weekdaysShort.some(w => lower.includes(w.toLowerCase()));
54
+ const hasLetterTZ = /gmt|utc|[+-]\d{2}:?\d{2}|z$/i.test(str);
55
+ const looksISO = /^\d{4}-\d{2}-\d{2}t\d{2}:\d{2}:\d{2}(\.\d+)?(z|[+\-]\d{2}:?\d{2})?$/i.test(str);
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
+ }
119
+ return null;
120
+ }
package/dist/dt.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import parse from './dt/functions/parse.js';
2
+ import guessFormat from './dt/functions/guessFormat.js';
2
3
  declare const dt: {
3
4
  (value: any, inputFormat?: null | String): void;
4
5
  locales: any;
5
6
  parse: typeof parse;
7
+ guessFormat: typeof guessFormat;
6
8
  time(): void;
7
9
  date(): void;
8
10
  dateTime(): void;
package/dist/dt.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import parse from './dt/functions/parse.js';
2
+ import guessFormat from './dt/functions/guessFormat.js';
2
3
  const patterns = [
3
4
  // MariaDB DATETIME "YYYY-MM-DD HH:MM:SS"
4
5
  {
@@ -191,6 +192,7 @@ const dt = (value, inputFormat = null) => {
191
192
  };
192
193
  dt.locales = Object.create(null);
193
194
  dt.parse = parse;
195
+ dt.guessFormat = guessFormat;
194
196
  dt.time = () => { };
195
197
  dt.date = () => { };
196
198
  dt.dateTime = () => { };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbn/bbn",
3
- "version": "2.0.29",
3
+ "version": "2.0.31",
4
4
  "description": "Javascript toolkit",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",