@bbn/bbn 2.0.3 → 2.0.5

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/date.d.ts CHANGED
@@ -1,5 +1,27 @@
1
1
  declare class bbnDateTool {
2
2
  #private;
3
+ /**
4
+ * Parses a date string strictly according to a format.
5
+ *
6
+ * Supported tokens:
7
+ * Years: YYYY, YY, Y
8
+ * Months: MMMM, MMM, MM, M, m
9
+ * Days: DD, D, d
10
+ * Weekday: dddd, ddd, EE (validation only)
11
+ * Hours: HH, H, h
12
+ * Minutes: II, I, i
13
+ * Seconds: SS, S, s
14
+ * Milli: ms
15
+ * Weeks: WWWW, WWW, WW, W (parsed but not used to build the Date)
16
+ *
17
+ * @throws Error if parsing fails or the date is invalid.
18
+ */
19
+ static parse(input: string, format: string, locale?: {
20
+ monthsLong?: string[];
21
+ monthsShort?: string[];
22
+ weekdaysLong?: string[];
23
+ weekdaysShort?: string[];
24
+ }): Date;
3
25
  constructor(value: any, inputFormat?: null | String);
4
26
  toString(): string;
5
27
  year(v?: number): number | bbnDateTool;
package/dist/date.js CHANGED
@@ -204,6 +204,38 @@ const unitsCorrespondence = {
204
204
  'W': 'w',
205
205
  'w': 'w'
206
206
  };
207
+ const buildLocaleFromIntl = () => {
208
+ const langs = [bbn.env.lang, ...navigator.languages];
209
+ const fmtMonthLong = new Intl.DateTimeFormat(langs, { month: 'long' });
210
+ const fmtMonthShort = new Intl.DateTimeFormat(langs, { month: 'short' });
211
+ const fmtWeekLong = new Intl.DateTimeFormat(langs, { weekday: 'long' });
212
+ const fmtWeekShort = new Intl.DateTimeFormat(langs, { weekday: 'short' });
213
+ // Create 12 dates for months (2020 chosen arbitrarily)
214
+ const monthsLong = [];
215
+ const monthsShort = [];
216
+ for (let m = 0; m < 12; m++) {
217
+ const d = new Date(2020, m, 1);
218
+ monthsLong.push(fmtMonthLong.format(d));
219
+ monthsShort.push(fmtMonthShort.format(d));
220
+ }
221
+ // Create 7 dates for weekdays (starting from Sunday 2020-02-02 which *is* Sunday)
222
+ // 2020-02-02 is Sunday → guarantees stable weekday list
223
+ const baseSunday = new Date(2020, 1, 2); // YYYY, MM (0-based), DD
224
+ const weekdaysLong = [];
225
+ const weekdaysShort = [];
226
+ for (let i = 0; i < 7; i++) {
227
+ const d = new Date(baseSunday.getTime() + i * 86400000);
228
+ weekdaysLong.push(fmtWeekLong.format(d));
229
+ weekdaysShort.push(fmtWeekShort.format(d));
230
+ }
231
+ return {
232
+ monthsLong,
233
+ monthsShort,
234
+ weekdaysLong,
235
+ weekdaysShort,
236
+ };
237
+ };
238
+ const locales = buildLocaleFromIntl();
207
239
  class bbnDateDuration {
208
240
  constructor(len, unit, fromMs = false) {
209
241
  _bbnDateDuration_instances.add(this);
@@ -235,6 +267,17 @@ class bbnDateDuration {
235
267
  // Day.js style
236
268
  // "asX" conversions
237
269
  // -----------------------
270
+ toJSON() {
271
+ return {
272
+ years: this.years(true),
273
+ months: this.months(true),
274
+ days: this.days(true),
275
+ hours: this.hours(true),
276
+ minutes: this.minutes(true),
277
+ seconds: this.seconds(true),
278
+ milliseconds: this.toMilliseconds()
279
+ };
280
+ }
238
281
  /**
239
282
  * Returns the full duration expressed as X (float), like Day.js.
240
283
  */
@@ -312,6 +355,414 @@ _bbnDateDuration_durationMs = new WeakMap(), _bbnDateDuration_unit = new WeakMap
312
355
  return Math.floor(remainingMs / unitMs);
313
356
  };
314
357
  class bbnDateTool {
358
+ /**
359
+ * Parses a date string strictly according to a format.
360
+ *
361
+ * Supported tokens:
362
+ * Years: YYYY, YY, Y
363
+ * Months: MMMM, MMM, MM, M, m
364
+ * Days: DD, D, d
365
+ * Weekday: dddd, ddd, EE (validation only)
366
+ * Hours: HH, H, h
367
+ * Minutes: II, I, i
368
+ * Seconds: SS, S, s
369
+ * Milli: ms
370
+ * Weeks: WWWW, WWW, WW, W (parsed but not used to build the Date)
371
+ *
372
+ * @throws Error if parsing fails or the date is invalid.
373
+ */
374
+ static parse(input, format, locale) {
375
+ var _a, _b, _c, _d;
376
+ const loc = {
377
+ monthsLong: (_a = locale === null || locale === void 0 ? void 0 : locale.monthsLong) !== null && _a !== void 0 ? _a : locales.monthsLong,
378
+ monthsShort: (_b = locale === null || locale === void 0 ? void 0 : locale.monthsShort) !== null && _b !== void 0 ? _b : locales.monthsShort,
379
+ weekdaysLong: (_c = locale === null || locale === void 0 ? void 0 : locale.weekdaysLong) !== null && _c !== void 0 ? _c : locales.weekdaysLong,
380
+ weekdaysShort: (_d = locale === null || locale === void 0 ? void 0 : locale.weekdaysShort) !== null && _d !== void 0 ? _d : locales.weekdaysShort
381
+ };
382
+ const ctx = {
383
+ year: 1970,
384
+ month: 1,
385
+ day: 1,
386
+ hour: 0,
387
+ minute: 0,
388
+ second: 0,
389
+ ms: 0
390
+ };
391
+ const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
392
+ const tokenSpecs = [
393
+ // Years
394
+ {
395
+ token: 'YYYY',
396
+ regex: '\\d{4}',
397
+ apply: v => { ctx.year = parseInt(v, 10); }
398
+ },
399
+ {
400
+ token: 'YY',
401
+ regex: '\\d{2}',
402
+ apply: v => {
403
+ const n = parseInt(v, 10);
404
+ ctx.year = n >= 70 ? 1900 + n : 2000 + n;
405
+ }
406
+ },
407
+ {
408
+ token: 'Y',
409
+ regex: '[+-]?\\d{1,6}',
410
+ apply: v => { ctx.year = parseInt(v, 10); }
411
+ },
412
+ // Months
413
+ {
414
+ token: 'MMMM',
415
+ regex: '[^\\d\\s]+',
416
+ apply: v => {
417
+ const idx = loc.monthsLong
418
+ .findIndex(m => m.toLowerCase() === v.toLowerCase());
419
+ if (idx === -1) {
420
+ throw new Error('Invalid month name: ' + v);
421
+ }
422
+ ctx.month = idx + 1;
423
+ }
424
+ },
425
+ {
426
+ token: 'MMM',
427
+ regex: '[^\\d\\s]+',
428
+ apply: v => {
429
+ const idx = loc.monthsShort
430
+ .findIndex(m => m.toLowerCase() === v.toLowerCase());
431
+ if (idx === -1) {
432
+ throw new Error('Invalid short month name: ' + v);
433
+ }
434
+ ctx.month = idx + 1;
435
+ }
436
+ },
437
+ {
438
+ token: 'MM',
439
+ regex: '\\d{2}',
440
+ apply: v => {
441
+ const n = parseInt(v, 10);
442
+ if (n < 1 || n > 12) {
443
+ throw new Error('Invalid month: ' + n);
444
+ }
445
+ ctx.month = n;
446
+ }
447
+ },
448
+ {
449
+ token: 'M',
450
+ regex: '\\d{1,2}',
451
+ apply: v => {
452
+ const n = parseInt(v, 10);
453
+ if (n < 1 || n > 12) {
454
+ throw new Error('Invalid month: ' + n);
455
+ }
456
+ ctx.month = n;
457
+ }
458
+ },
459
+ {
460
+ token: 'm', // PHP-like month
461
+ regex: '\\d{2}',
462
+ apply: v => {
463
+ const n = parseInt(v, 10);
464
+ if (n < 1 || n > 12) {
465
+ throw new Error('Invalid month: ' + n);
466
+ }
467
+ ctx.month = n;
468
+ }
469
+ },
470
+ // Day of month
471
+ {
472
+ token: 'DD',
473
+ regex: '\\d{2}',
474
+ apply: v => {
475
+ const n = parseInt(v, 10);
476
+ if (n < 1 || n > 31) {
477
+ throw new Error('Invalid day of month: ' + n);
478
+ }
479
+ ctx.day = n;
480
+ }
481
+ },
482
+ {
483
+ token: 'D',
484
+ regex: '\\d{1,2}',
485
+ apply: v => {
486
+ const n = parseInt(v, 10);
487
+ if (n < 1 || n > 31) {
488
+ throw new Error('Invalid day of month: ' + n);
489
+ }
490
+ ctx.day = n;
491
+ }
492
+ },
493
+ {
494
+ token: 'd', // PHP-like day-of-month
495
+ regex: '\\d{2}',
496
+ apply: v => {
497
+ const n = parseInt(v, 10);
498
+ if (n < 1 || n > 31) {
499
+ throw new Error('Invalid day of month: ' + n);
500
+ }
501
+ ctx.day = n;
502
+ }
503
+ },
504
+ // Weekday (only validated)
505
+ {
506
+ token: 'dddd',
507
+ regex: '[^\\d\\s]+',
508
+ apply: v => {
509
+ const idx = loc.weekdaysLong
510
+ .findIndex(w => w.toLowerCase() === v.toLowerCase());
511
+ if (idx === -1) {
512
+ throw new Error('Invalid weekday name: ' + v);
513
+ }
514
+ ctx.weekday = idx; // 0-6, Sunday-based
515
+ }
516
+ },
517
+ {
518
+ token: 'ddd',
519
+ regex: '[^\\d\\s]+',
520
+ apply: v => {
521
+ const idx = loc.weekdaysShort
522
+ .findIndex(w => w.toLowerCase() === v.toLowerCase());
523
+ if (idx === -1) {
524
+ throw new Error('Invalid short weekday name: ' + v);
525
+ }
526
+ ctx.weekday = idx; // 0-6
527
+ }
528
+ },
529
+ {
530
+ token: 'EE',
531
+ regex: '\\d{1}',
532
+ apply: v => {
533
+ const n = parseInt(v, 10);
534
+ if (n < 0 || n > 7) {
535
+ throw new Error('Invalid weekday number: ' + n);
536
+ }
537
+ ctx.weekday = n;
538
+ }
539
+ },
540
+ // Hours
541
+ {
542
+ token: 'HH',
543
+ regex: '\\d{2}',
544
+ apply: v => {
545
+ const n = parseInt(v, 10);
546
+ if (n < 0 || n > 23) {
547
+ throw new Error('Invalid hour: ' + n);
548
+ }
549
+ ctx.hour = n;
550
+ }
551
+ },
552
+ {
553
+ token: 'H',
554
+ regex: '\\d{1,2}',
555
+ apply: v => {
556
+ const n = parseInt(v, 10);
557
+ if (n < 0 || n > 23) {
558
+ throw new Error('Invalid hour: ' + n);
559
+ }
560
+ ctx.hour = n;
561
+ }
562
+ },
563
+ {
564
+ token: 'h', // PHP-like 24h alias here
565
+ regex: '\\d{2}',
566
+ apply: v => {
567
+ const n = parseInt(v, 10);
568
+ if (n < 0 || n > 23) {
569
+ throw new Error('Invalid hour: ' + n);
570
+ }
571
+ ctx.hour = n;
572
+ }
573
+ },
574
+ // Minutes
575
+ {
576
+ token: 'II',
577
+ regex: '\\d{2}',
578
+ apply: v => {
579
+ const n = parseInt(v, 10);
580
+ if (n < 0 || n > 59) {
581
+ throw new Error('Invalid minute: ' + n);
582
+ }
583
+ ctx.minute = n;
584
+ }
585
+ },
586
+ {
587
+ token: 'I',
588
+ regex: '\\d{1,2}',
589
+ apply: v => {
590
+ const n = parseInt(v, 10);
591
+ if (n < 0 || n > 59) {
592
+ throw new Error('Invalid minute: ' + n);
593
+ }
594
+ ctx.minute = n;
595
+ }
596
+ },
597
+ {
598
+ token: 'i', // PHP-like minutes
599
+ regex: '\\d{2}',
600
+ apply: v => {
601
+ const n = parseInt(v, 10);
602
+ if (n < 0 || n > 59) {
603
+ throw new Error('Invalid minute: ' + n);
604
+ }
605
+ ctx.minute = n;
606
+ }
607
+ },
608
+ // Seconds
609
+ {
610
+ token: 'SS',
611
+ regex: '\\d{2}',
612
+ apply: v => {
613
+ const n = parseInt(v, 10);
614
+ if (n < 0 || n > 59) {
615
+ throw new Error('Invalid second: ' + n);
616
+ }
617
+ ctx.second = n;
618
+ }
619
+ },
620
+ {
621
+ token: 'S',
622
+ regex: '\\d{1,2}',
623
+ apply: v => {
624
+ const n = parseInt(v, 10);
625
+ if (n < 0 || n > 59) {
626
+ throw new Error('Invalid second: ' + n);
627
+ }
628
+ ctx.second = n;
629
+ }
630
+ },
631
+ {
632
+ token: 's', // PHP-like seconds
633
+ regex: '\\d{2}',
634
+ apply: v => {
635
+ const n = parseInt(v, 10);
636
+ if (n < 0 || n > 59) {
637
+ throw new Error('Invalid second: ' + n);
638
+ }
639
+ ctx.second = n;
640
+ }
641
+ },
642
+ // Milliseconds
643
+ {
644
+ token: 'ms',
645
+ regex: '\\d{1,3}',
646
+ apply: v => {
647
+ const n = parseInt(v, 10);
648
+ if (n < 0 || n > 999) {
649
+ throw new Error('Invalid millisecond: ' + n);
650
+ }
651
+ ctx.ms = n;
652
+ }
653
+ },
654
+ // Weeks (parsed, but not used to construct the Date yet)
655
+ {
656
+ token: 'WWWW',
657
+ regex: '\\d{1,2}',
658
+ apply: v => {
659
+ const n = parseInt(v, 10);
660
+ if (n < 1 || n > 53) {
661
+ throw new Error('Invalid week number: ' + n);
662
+ }
663
+ ctx.week = n;
664
+ }
665
+ },
666
+ {
667
+ token: 'WWW',
668
+ regex: '\\d{1,2}',
669
+ apply: v => {
670
+ const n = parseInt(v, 10);
671
+ if (n < 1 || n > 53) {
672
+ throw new Error('Invalid week number: ' + n);
673
+ }
674
+ ctx.week = n;
675
+ }
676
+ },
677
+ {
678
+ token: 'WW',
679
+ regex: '\\d{1,2}',
680
+ apply: v => {
681
+ const n = parseInt(v, 10);
682
+ if (n < 1 || n > 53) {
683
+ throw new Error('Invalid week number: ' + n);
684
+ }
685
+ ctx.week = n;
686
+ }
687
+ },
688
+ {
689
+ token: 'W',
690
+ regex: '\\d{1,2}',
691
+ apply: v => {
692
+ const n = parseInt(v, 10);
693
+ if (n < 1 || n > 53) {
694
+ throw new Error('Invalid week number: ' + n);
695
+ }
696
+ ctx.week = n;
697
+ }
698
+ }
699
+ ];
700
+ // Sort tokens by length (desc) so we match 'YYYY' before 'YY', 'ms' before 'm'/'s', etc.
701
+ const tokensByLength = [...tokenSpecs].sort((a, b) => b.token.length - a.token.length);
702
+ let pattern = '';
703
+ const applyFns = [];
704
+ let i = 0;
705
+ while (i < format.length) {
706
+ let matchedToken = null;
707
+ for (const spec of tokensByLength) {
708
+ if (format.startsWith(spec.token, i)) {
709
+ matchedToken = spec;
710
+ break;
711
+ }
712
+ }
713
+ if (matchedToken) {
714
+ pattern += `(${matchedToken.regex})`;
715
+ if (matchedToken.apply) {
716
+ applyFns.push(matchedToken.apply);
717
+ }
718
+ else {
719
+ // Still consume the capturing group, but ignore value
720
+ applyFns.push(() => { });
721
+ }
722
+ i += matchedToken.token.length;
723
+ }
724
+ else {
725
+ // Literal character
726
+ pattern += escapeRegex(format[i]);
727
+ i += 1;
728
+ }
729
+ }
730
+ const fullRegex = new RegExp('^' + pattern + '$');
731
+ const match = fullRegex.exec(input);
732
+ if (!match) {
733
+ throw new Error(`Date string "${input}" does not match format "${format}"`);
734
+ }
735
+ // Apply captured groups
736
+ for (let idx = 1; idx < match.length; idx++) {
737
+ const value = match[idx];
738
+ const apply = applyFns[idx - 1];
739
+ if (value != null && apply) {
740
+ apply(value);
741
+ }
742
+ }
743
+ // Build Date (local time)
744
+ const date = new Date(ctx.year, ctx.month - 1, ctx.day, ctx.hour, ctx.minute, ctx.second, ctx.ms);
745
+ // Strict validation: ensure Date didn't overflow (e.g. 31 Feb)
746
+ if (date.getFullYear() !== ctx.year ||
747
+ date.getMonth() !== ctx.month - 1 ||
748
+ date.getDate() !== ctx.day ||
749
+ date.getHours() !== ctx.hour ||
750
+ date.getMinutes() !== ctx.minute ||
751
+ date.getSeconds() !== ctx.second ||
752
+ date.getMilliseconds() !== ctx.ms) {
753
+ throw new Error('Invalid date produced from components');
754
+ }
755
+ // Optional: validate weekday if provided
756
+ if (typeof ctx.weekday !== 'undefined') {
757
+ const jsWeekday = date.getDay(); // 0 (Sunday) - 6 (Saturday)
758
+ if (ctx.weekday === 0 || ctx.weekday === 7) {
759
+ // If you decide EE is 0–6 or 1–7, adjust here as needed.
760
+ // Not enforcing a strict rule beyond basic numeric check above.
761
+ }
762
+ // You could enforce consistency between ctx.weekday and jsWeekday here if you want.
763
+ }
764
+ return date;
765
+ }
315
766
  constructor(value, inputFormat = null) {
316
767
  _bbnDateTool_value.set(this, void 0);
317
768
  _bbnDateTool_isDuration.set(this, false);
@@ -320,8 +771,12 @@ class bbnDateTool {
320
771
  __classPrivateFieldSet(this, _bbnDateTool_value, new Date(), "f");
321
772
  }
322
773
  else if (inputFormat) {
323
- /** @todo read the date from the input format */
324
- __classPrivateFieldSet(this, _bbnDateTool_value, new Date(), "f");
774
+ try {
775
+ __classPrivateFieldSet(this, _bbnDateTool_value, bbnDateTool.parse(value, inputFormat), "f");
776
+ }
777
+ catch (e) {
778
+ throw new Error('Error parsing date with format "' + inputFormat + '": ' + e.message);
779
+ }
325
780
  }
326
781
  else {
327
782
  if (t === 'number' || (isNumber(value) && value !== '')) {
@@ -611,7 +1066,7 @@ class bbnDateTool {
611
1066
  const t = new Intl.DateTimeFormat([bbn.env.lang, ...navigator.languages], opt);
612
1067
  return t.format(__classPrivateFieldGet(this, _bbnDateTool_value, "f"));
613
1068
  }
614
- format(format = 'y-m-d h:i:s') {
1069
+ format(format = 'YYYY-MM-DD HH:II:SS') {
615
1070
  let str = '';
616
1071
  if (format) {
617
1072
  const reg = new RegExp('(\[|\]|' + Object.keys(unitsCorrespondence).join('|') + ')', 'g');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbn/bbn",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "Javascript toolkit",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",