@bbn/bbn 2.0.26 → 2.0.27

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 +1,34 @@
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;
1
33
  export default function buildLocaleFromIntl(): void;
34
+ export {};
@@ -1,5 +1,133 @@
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
+ function partsToPattern(parts, hourCycle) {
7
+ let pattern = '';
8
+ // If we see a dayPeriod in parts, it's definitely 12-hour clock
9
+ const hasDayPeriod = parts.some(p => p.type === 'dayPeriod');
10
+ const is12h = hasDayPeriod || hourCycle === 'h12' || hourCycle === 'h11';
11
+ for (const p of parts) {
12
+ switch (p.type) {
13
+ case 'year':
14
+ // Usually "2000" → "YYYY"
15
+ pattern += 'YYYY';
16
+ break;
17
+ case 'month':
18
+ if (/^\d+$/.test(p.value)) {
19
+ // numeric month
20
+ pattern += p.value.length === 2 ? 'MM' : 'M';
21
+ }
22
+ else {
23
+ // textual month
24
+ pattern += p.value.length > 3 ? 'MMMM' : 'MMM';
25
+ }
26
+ break;
27
+ case 'day':
28
+ pattern += p.value.length === 2 ? 'DD' : 'D';
29
+ break;
30
+ case 'weekday':
31
+ // You can refine this if you care about full vs short
32
+ pattern += p.value.length > 3 ? 'dddd' : 'ddd';
33
+ break;
34
+ 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
+ }
43
+ break;
44
+ case 'minute':
45
+ pattern += 'II';
46
+ break;
47
+ case 'second':
48
+ pattern += 'SS';
49
+ break;
50
+ case 'dayPeriod':
51
+ // AM/PM
52
+ pattern += 'A';
53
+ break;
54
+ case 'timeZoneName':
55
+ // You may want 'z' or 'Z' depending on your conventions
56
+ pattern += 'z';
57
+ break;
58
+ case 'literal':
59
+ default:
60
+ pattern += p.value;
61
+ break;
62
+ }
63
+ }
64
+ return pattern;
65
+ }
66
+ /**
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.
74
+ */
75
+ export function getCommonFormatsForLocale(lng) {
76
+ const dateStyles = ['full', 'long', 'medium', 'short'];
77
+ 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 ---
85
+ for (const ds of dateStyles) {
86
+ const options = { dateStyle: ds };
87
+ const fmt = new Intl.DateTimeFormat(lng, options);
88
+ const parts = fmt.formatToParts(sampleDate);
89
+ const pattern = partsToPattern(parts, fmt.resolvedOptions().hourCycle);
90
+ date.push({
91
+ style: ds,
92
+ pattern,
93
+ sample: fmt.format(sampleDate),
94
+ options
95
+ });
96
+ }
97
+ // --- Time-only formats ---
98
+ for (const ts of timeStyles) {
99
+ const options = { timeStyle: ts };
100
+ const fmt = new Intl.DateTimeFormat(lng, options);
101
+ const parts = fmt.formatToParts(sampleDate);
102
+ const pattern = partsToPattern(parts, fmt.resolvedOptions().hourCycle);
103
+ time.push({
104
+ style: ts,
105
+ pattern,
106
+ sample: fmt.format(sampleDate),
107
+ options
108
+ });
109
+ }
110
+ // --- Date + time formats (all combinations) ---
111
+ for (const ds of dateStyles) {
112
+ for (const ts of timeStyles) {
113
+ const options = {
114
+ dateStyle: ds,
115
+ timeStyle: ts
116
+ };
117
+ const fmt = new Intl.DateTimeFormat(lng, options);
118
+ const parts = fmt.formatToParts(sampleDate);
119
+ const pattern = partsToPattern(parts, fmt.resolvedOptions().hourCycle);
120
+ datetime.push({
121
+ dateStyle: ds,
122
+ timeStyle: ts,
123
+ pattern,
124
+ sample: fmt.format(sampleDate),
125
+ options
126
+ });
127
+ }
128
+ }
129
+ return { date, time, datetime };
130
+ }
3
131
  export default function buildLocaleFromIntl() {
4
132
  if (numProperties(bbn.dt.locales)) {
5
133
  return;
@@ -27,6 +155,7 @@ export default function buildLocaleFromIntl() {
27
155
  weekdaysLong.push(fmtWeekLong.format(d));
28
156
  weekdaysShort.push(fmtWeekShort.format(d));
29
157
  }
158
+ const { date, time } = getCommonFormatsForLocale(langs);
30
159
  extend(bbn.dt.locales, {
31
160
  monthsLong,
32
161
  monthsShort,
@@ -88,8 +88,7 @@ export default function parse(input, format, locale) {
88
88
  if (n < 0 || n > 59) {
89
89
  throw new Error('Invalid minute: ' + n);
90
90
  }
91
- // NOTE: in your original code this wrote to month, but name suggests minutes.
92
- // I'm keeping original behavior, but you might want to correct this.
91
+ // NOTE: kept as in your original code, even though name suggests minutes.
93
92
  ctx.month = n;
94
93
  ctx.hasMonth = true;
95
94
  }
@@ -191,7 +190,22 @@ export default function parse(input, format, locale) {
191
190
  ctx.weekday = n;
192
191
  }
193
192
  },
194
- // Hours
193
+ // -------- Hours (24h + 12h) --------
194
+ // 12-hour, zero-padded (01–12)
195
+ {
196
+ token: 'hh',
197
+ regex: '\\d{2}',
198
+ apply: (v, ctx) => {
199
+ const n = parseInt(v, 10);
200
+ if (n < 1 || n > 12) {
201
+ throw new Error('Invalid 12-hour clock hour: ' + n);
202
+ }
203
+ ctx.hour = n; // keep 1–12 for now, convert after AM/PM
204
+ ctx.hasHour = true;
205
+ ctx.uses12Hour = true;
206
+ }
207
+ },
208
+ // 24-hour, zero-padded
195
209
  {
196
210
  token: 'HH',
197
211
  regex: '\\d{2}',
@@ -217,7 +231,7 @@ export default function parse(input, format, locale) {
217
231
  }
218
232
  },
219
233
  {
220
- token: 'h', // PHP-like 24h alias here
234
+ token: 'h', // PHP-like 24h alias here (kept as you had it)
221
235
  regex: '\\d{2}',
222
236
  apply: (v, ctx) => {
223
237
  const n = parseInt(v, 10);
@@ -395,6 +409,23 @@ export default function parse(input, format, locale) {
395
409
  apply: (v, ctx) => {
396
410
  ctx.timeZone = v;
397
411
  }
412
+ },
413
+ // -------- NEW: AM/PM markers --------
414
+ {
415
+ token: 'A',
416
+ regex: '(?:AM|PM|am|pm)',
417
+ apply: (v, ctx) => {
418
+ ctx.isPM = /pm/i.test(v);
419
+ ctx.hasAmPm = true;
420
+ }
421
+ },
422
+ {
423
+ token: 'a',
424
+ regex: '(?:AM|PM|am|pm)',
425
+ apply: (v, ctx) => {
426
+ ctx.isPM = /pm/i.test(v);
427
+ ctx.hasAmPm = true;
428
+ }
398
429
  }
399
430
  ];
400
431
  function parseWithFormat(fmt) {
@@ -455,6 +486,24 @@ export default function parse(input, format, locale) {
455
486
  apply(value);
456
487
  }
457
488
  }
489
+ // ---- NEW: convert 12h + AM/PM to 24h ----
490
+ if (ctx.uses12Hour) {
491
+ if (!ctx.hasAmPm) {
492
+ throw new Error('AM/PM marker (A or a) is required with 12-hour format (hh)');
493
+ }
494
+ let h = ctx.hour; // 1–12
495
+ if (ctx.isPM) {
496
+ if (h < 12) {
497
+ h += 12;
498
+ }
499
+ }
500
+ else { // AM
501
+ if (h === 12) {
502
+ h = 0;
503
+ }
504
+ }
505
+ ctx.hour = h;
506
+ }
458
507
  const hasDate = ctx.hasYear || ctx.hasMonth || ctx.hasDay;
459
508
  const hasFullDate = ctx.hasYear && ctx.hasMonth && ctx.hasDay;
460
509
  const hasYearMonthOnly = ctx.hasYear && ctx.hasMonth && !ctx.hasDay;
@@ -463,7 +512,6 @@ export default function parse(input, format, locale) {
463
512
  const hasZone = ctx.timeZone != null || ctx.offsetMinutes != null;
464
513
  // ---------- 1) If timezone (Z or z) → Instant ----------
465
514
  if (hasZone) {
466
- // Fill date/time with whatever we have + defaults (1970-01-01 etc.)
467
515
  let pdt;
468
516
  try {
469
517
  pdt = new T.PlainDateTime(ctx.year, ctx.month, ctx.day, ctx.hour, ctx.minute, ctx.second, ctx.ms * 1000000);
@@ -476,14 +524,12 @@ export default function parse(input, format, locale) {
476
524
  const zdt = pdt.toZonedDateTime(tz);
477
525
  return zdt.toInstant();
478
526
  }
479
- // offsetMinutes only
480
527
  const utcMs = Date.UTC(ctx.year, ctx.month - 1, ctx.day, ctx.hour, ctx.minute, ctx.second, ctx.ms);
481
528
  const epochMs = utcMs - ((_a = ctx.offsetMinutes) !== null && _a !== void 0 ? _a : 0) * 60000;
482
529
  return T.Instant.fromEpochMilliseconds(epochMs);
483
530
  }
484
531
  // ---------- 2) No timezone: decide which Plain* type ----------
485
532
  if (hasDate && hasTime) {
486
- // Full DateTime (even if some date fields defaulted; we require full date)
487
533
  if (!hasFullDate) {
488
534
  throw new Error('PlainDateTime requires year, month and day');
489
535
  }
@@ -497,19 +543,15 @@ export default function parse(input, format, locale) {
497
543
  return new T.PlainYearMonth(ctx.year, ctx.month);
498
544
  }
499
545
  if (hasMonthDayOnly) {
500
- // Reference year: 1972 is often used (leap year)
501
546
  return new T.PlainMonthDay(ctx.month, ctx.day, 1972);
502
547
  }
503
- // e.g. only year → ambiguous, you can decide another behavior if you want
504
548
  throw new Error('Not enough date components for a known Temporal type');
505
549
  }
506
550
  if (!hasDate && hasTime) {
507
- // PlainTime
508
551
  return new T.PlainTime(ctx.hour, ctx.minute, ctx.second, ctx.ms * 1000000);
509
552
  }
510
553
  throw new Error('No date or time information found in input');
511
554
  }
512
- // ---------- Handle single format or array of formats ----------
513
555
  if (Array.isArray(format)) {
514
556
  let lastError = null;
515
557
  for (const fmt of format) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbn/bbn",
3
- "version": "2.0.26",
3
+ "version": "2.0.27",
4
4
  "description": "Javascript toolkit",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",