@bbn/bbn 2.0.25 → 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.
- package/dist/bbn.js +1 -1
- package/dist/bbn.js.map +1 -1
- package/dist/dt/functions/buildLocaleFromIntl.d.ts +33 -0
- package/dist/dt/functions/buildLocaleFromIntl.js +129 -0
- package/dist/dt/functions/parse.js +53 -11
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/package.json +1 -1
|
@@ -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
|
|
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/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Temporal } from 'temporal-polyfill';
|
|
1
2
|
import _ from './_.js';
|
|
2
3
|
import $ from './$.js';
|
|
3
4
|
import lng from './lng.js';
|
|
@@ -95,5 +96,8 @@ const bbn = {
|
|
|
95
96
|
};
|
|
96
97
|
if ('undefined' !== typeof window) {
|
|
97
98
|
window.bbn = bbn;
|
|
99
|
+
if (!window.Temporal) {
|
|
100
|
+
window.Temporal = Temporal;
|
|
101
|
+
}
|
|
98
102
|
}
|
|
99
|
-
export { bbn as default, bbn };
|
|
103
|
+
export { bbn as default, bbn, Temporal };
|