@bbn/bbn 2.0.68 → 2.0.70

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,3 +1,4 @@
1
+ import { Temporal } from 'temporal-polyfill';
1
2
  import { bbnDtTemporal } from '../vars/types.js';
2
3
  import bbnDtDuration from './duration.js';
3
4
  export declare abstract class bbnDt<TValue extends bbnDtTemporal> {
@@ -5,6 +6,29 @@ export declare abstract class bbnDt<TValue extends bbnDtTemporal> {
5
6
  abstract readonly kind: bbnDtKind;
6
7
  constructor(value?: TValue);
7
8
  get value(): TValue | undefined;
9
+ /** System time zone ID (e.g. "Europe/Rome") */
10
+ private static readonly systemTimeZoneId;
11
+ /**
12
+ * Convert this.value (PlainDate, PlainTime, PlainDateTime, YearMonth,
13
+ * MonthDay, ZonedDateTime) into epoch milliseconds, using the system
14
+ * time zone when needed.
15
+ *
16
+ * Conventions:
17
+ * - time → today at that time in system tz
18
+ * - date → that date at 00:00 in system tz
19
+ * - year-month → first of that month at 00:00 in system tz
20
+ * - month-day → that month/day in *this year* at 00:00 in system tz
21
+ */
22
+ protected toEpochMs(): number;
23
+ /**
24
+ * "Now" value in the same *kind* as this instance.
25
+ */
26
+ protected static nowForKind(kind: bbnDtKind): Temporal.PlainDate | Temporal.PlainTime | Temporal.PlainDateTime | Temporal.PlainYearMonth | Temporal.PlainMonthDay | Temporal.ZonedDateTime;
27
+ /**
28
+ * Helper to rebuild the same concrete subclass with a new Temporal value.
29
+ * Assumes your subclass constructor takes the value as first argument.
30
+ */
31
+ protected withValue(newValue: any): this;
8
32
  static compare(a: any, b: any, unit: string | undefined): -1 | 0 | 1;
9
33
  static parse(input: string, format: string | string[], cls?: 'auto' | 'zoned' | 'dateTime' | 'date' | 'time' | 'yearMonth' | 'monthDay', locale?: {
10
34
  monthsLong?: string[];
@@ -17,7 +41,9 @@ export declare abstract class bbnDt<TValue extends bbnDtTemporal> {
17
41
  add(amount: number | bbnDtDuration | object, unit?: string): bbnDt<any>;
18
42
  subtract(amount: number | bbnDtDuration | object, unit?: string): bbnDt<any>;
19
43
  isBefore(other: any): boolean;
44
+ isBeforeOrSame(other: any): boolean;
20
45
  isAfter(other: any): boolean;
46
+ isAfterOrSame(other: any): boolean;
21
47
  isSame(other: any): boolean;
22
48
  equals(other: any): boolean;
23
49
  toJSON(): {
@@ -58,6 +84,7 @@ export declare abstract class bbnDt<TValue extends bbnDtTemporal> {
58
84
  get WW(): string;
59
85
  get W(): string;
60
86
  format(format?: string): string;
87
+ getWeekday(mode?: string, locale?: string): string;
61
88
  /**
62
89
  * Returns a NEW date that is the next (or previous if past=true)
63
90
  * occurrence of the given weekday, starting from this.#value.
@@ -68,5 +95,11 @@ export declare abstract class bbnDt<TValue extends bbnDtTemporal> {
68
95
  * @param {string} [locale] - Optional locale for weekday names.
69
96
  */
70
97
  setWeekday(weekday: number | string, past?: boolean, locale?: string): bbnDt<any>;
98
+ diff(date: any, unit?: string, abs?: boolean): number;
99
+ guessUnit(valueInMs: number): string | null;
100
+ fromNow(unit?: string): string;
101
+ fromDate(date: any, unit?: string): string;
102
+ startOf(unit?: string): bbnDt<any>;
103
+ endOf(unit?: string): bbnDt<any>;
71
104
  }
72
105
  export default bbnDt;
@@ -11,10 +11,12 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  };
12
12
  var _bbnDt_value;
13
13
  import { Temporal } from 'temporal-polyfill';
14
- import substr from '../../fn/string/substr.js';
15
14
  import { getWeekdayIndex, getWeekday } from '../functions/getWeekday.js';
16
- import { unitsCorrespondence, formatsMap } from '../vars/units.js';
15
+ import { unitsCorrespondence, units, formatsMap } from '../vars/units.js';
16
+ import _ from '../../_.js';
17
+ import substr from '../../fn/string/substr.js';
17
18
  import each from '../../fn/loop/each.js';
19
+ import getRow from '../../fn/object/getRow.js';
18
20
  import isPrimitive from '../../fn/type/isPrimitive.js';
19
21
  import bbnDtDuration from './duration.js';
20
22
  import camelToCss from '../../fn/string/camelToCss.js';
@@ -27,6 +29,98 @@ export class bbnDt {
27
29
  return __classPrivateFieldGet(this, _bbnDt_value, "f");
28
30
  }
29
31
  ;
32
+ /**
33
+ * Convert this.value (PlainDate, PlainTime, PlainDateTime, YearMonth,
34
+ * MonthDay, ZonedDateTime) into epoch milliseconds, using the system
35
+ * time zone when needed.
36
+ *
37
+ * Conventions:
38
+ * - time → today at that time in system tz
39
+ * - date → that date at 00:00 in system tz
40
+ * - year-month → first of that month at 00:00 in system tz
41
+ * - month-day → that month/day in *this year* at 00:00 in system tz
42
+ */
43
+ toEpochMs() {
44
+ const tz = bbnDt.systemTimeZoneId;
45
+ switch (this.kind) {
46
+ case 'zoned': {
47
+ const v = this.value;
48
+ return v.toInstant().epochMilliseconds;
49
+ }
50
+ case 'datetime': {
51
+ const v = this.value;
52
+ // RFC 9557 string: "YYYY-MM-DDTHH:mm:ss[Europe/Rome]"
53
+ const iso = `${v.toString()}[${tz}]`;
54
+ const zdt = Temporal.ZonedDateTime.from(iso);
55
+ return zdt.toInstant().epochMilliseconds;
56
+ }
57
+ case 'date': {
58
+ const d = this.value;
59
+ // "YYYY-MM-DDT00:00[Europe/Rome]"
60
+ const iso = `${d.toString()}T00:00[${tz}]`;
61
+ const zdt = Temporal.ZonedDateTime.from(iso);
62
+ return zdt.toInstant().epochMilliseconds;
63
+ }
64
+ case 'time': {
65
+ const t = this.value;
66
+ const today = Temporal.Now.plainDateISO();
67
+ // "YYYY-MM-DDTHH:mm[:ss][Europe/Rome]"
68
+ const iso = `${today.toString()}T${t.toString()}[${tz}]`;
69
+ const zdt = Temporal.ZonedDateTime.from(iso);
70
+ return zdt.toInstant().epochMilliseconds;
71
+ }
72
+ case 'year-month': {
73
+ const ym = this.value;
74
+ const d = ym.toPlainDate({ day: 1 }); // first day of month
75
+ const iso = `${d.toString()}T00:00[${tz}]`;
76
+ const zdt = Temporal.ZonedDateTime.from(iso);
77
+ return zdt.toInstant().epochMilliseconds;
78
+ }
79
+ case 'month-day': {
80
+ const md = this.value;
81
+ const today = Temporal.Now.plainDateISO();
82
+ const d = md.toPlainDate({ year: today.year }); // current year
83
+ const iso = `${d.toString()}T00:00[${tz}]`;
84
+ const zdt = Temporal.ZonedDateTime.from(iso);
85
+ return zdt.toInstant().epochMilliseconds;
86
+ }
87
+ default:
88
+ throw new Error(`Unsupported kind '${this.kind}' in toEpochMs`);
89
+ }
90
+ }
91
+ /**
92
+ * "Now" value in the same *kind* as this instance.
93
+ */
94
+ static nowForKind(kind) {
95
+ switch (kind) {
96
+ case 'zoned':
97
+ return Temporal.Now.zonedDateTimeISO();
98
+ case 'datetime':
99
+ return Temporal.Now.plainDateTimeISO();
100
+ case 'date':
101
+ return Temporal.Now.plainDateISO();
102
+ case 'time':
103
+ return Temporal.Now.plainTimeISO();
104
+ case 'year-month': {
105
+ const d = Temporal.Now.plainDateISO();
106
+ return new Temporal.PlainYearMonth(d.year, d.month);
107
+ }
108
+ case 'month-day': {
109
+ const d = Temporal.Now.plainDateISO();
110
+ return new Temporal.PlainMonthDay(d.month, d.day, undefined, d.year);
111
+ }
112
+ default:
113
+ throw new Error(`Unsupported kind '${kind}' in nowForKind`);
114
+ }
115
+ }
116
+ /**
117
+ * Helper to rebuild the same concrete subclass with a new Temporal value.
118
+ * Assumes your subclass constructor takes the value as first argument.
119
+ */
120
+ withValue(newValue) {
121
+ const Ctor = this.constructor;
122
+ return new Ctor(newValue);
123
+ }
30
124
  static compare(a, b, unit) {
31
125
  if (!a || !b) {
32
126
  throw new TypeError('Both arguments must be Temporal values');
@@ -115,9 +209,15 @@ export class bbnDt {
115
209
  isBefore(other) {
116
210
  return this.compare(other) < 0;
117
211
  }
212
+ isBeforeOrSame(other) {
213
+ return this.compare(other) <= 0;
214
+ }
118
215
  isAfter(other) {
119
216
  return this.compare(other) > 0;
120
217
  }
218
+ isAfterOrSame(other) {
219
+ return this.compare(other) >= 0;
220
+ }
121
221
  isSame(other) {
122
222
  return this.compare(other) === 0;
123
223
  }
@@ -421,6 +521,13 @@ export class bbnDt {
421
521
  }
422
522
  return str;
423
523
  }
524
+ getWeekday(mode = 'long', locale) {
525
+ const wd = this.weekday();
526
+ if (typeof wd === 'number') {
527
+ return getWeekday(wd, mode, locale);
528
+ }
529
+ return undefined;
530
+ }
424
531
  /**
425
532
  * Returns a NEW date that is the next (or previous if past=true)
426
533
  * occurrence of the given weekday, starting from this.#value.
@@ -465,6 +572,269 @@ export class bbnDt {
465
572
  }
466
573
  return this.add(diff, 'd');
467
574
  }
575
+ diff(date, unit = '', abs = false) {
576
+ let targetMs;
577
+ if (date instanceof bbnDt) {
578
+ targetMs = date.toEpochMs();
579
+ }
580
+ else if (date instanceof Temporal.ZonedDateTime ||
581
+ date instanceof Temporal.PlainDateTime ||
582
+ date instanceof Temporal.PlainDate ||
583
+ date instanceof Temporal.PlainTime ||
584
+ date instanceof Temporal.PlainYearMonth ||
585
+ date instanceof Temporal.PlainMonthDay) {
586
+ // Wrap it in a temporary bbnDt-like shim to reuse toEpochMs logic;
587
+ // or write a small standalone helper similar to toEpochMs(kind, value).
588
+ const temp = { kind: this.kind, value: date };
589
+ targetMs = temp.toEpochMs();
590
+ }
591
+ else if (typeof date === 'string' || typeof date === 'number') {
592
+ // Reuse your parse API: parse into same kind as `this`
593
+ const parsed = this.parse(String(date), ''); // format depends on your API
594
+ targetMs = parsed.toEpochMs();
595
+ }
596
+ else {
597
+ throw new TypeError('Unsupported date argument for diff');
598
+ }
599
+ const nowMs = this.toEpochMs();
600
+ let diff = nowMs - targetMs;
601
+ if (abs) {
602
+ diff = Math.abs(diff);
603
+ }
604
+ if (!unit) {
605
+ return diff;
606
+ }
607
+ const realUnit = unitsCorrespondence[unit] || unit;
608
+ const match = getRow(units, d => d[0] === realUnit);
609
+ if (!match) {
610
+ throw new Error('Invalid unit for diff: ' + unit);
611
+ }
612
+ const [, , ms] = match; // [shortUnit, rtfUnit, ms]
613
+ return Math.round(diff / ms);
614
+ }
615
+ guessUnit(valueInMs) {
616
+ const absDiff = Math.abs(valueInMs);
617
+ for (const [shortUnit, rtfUnit, ms] of units) {
618
+ if ((absDiff >= ms) || (rtfUnit === 'second')) {
619
+ return shortUnit;
620
+ }
621
+ }
622
+ return null;
623
+ }
624
+ fromNow(unit = '') {
625
+ const nowValue = bbnDt.nowForKind(this.kind);
626
+ const temp = { kind: this.kind, value: nowValue };
627
+ const rawDiffMs = this.diff(temp);
628
+ const chosenUnit = unitsCorrespondence[unit] || this.guessUnit(rawDiffMs);
629
+ if (!chosenUnit) {
630
+ throw new Error('Cannot guess unit for fromNow');
631
+ }
632
+ const diff = this.diff(temp, chosenUnit);
633
+ const rtf = new Intl.RelativeTimeFormat([bbn.env.lang, ...navigator.languages], { numeric: 'auto' });
634
+ const match = getRow(units, d => d[0] === chosenUnit);
635
+ if (!match) {
636
+ throw new Error('Invalid unit for fromNow: ' + unit);
637
+ }
638
+ const [, rtfUnit] = match; // [shortUnit, rtfUnit, ms]
639
+ return rtf.format(diff, rtfUnit);
640
+ }
641
+ fromDate(date, unit = '') {
642
+ const rawDiffMs = this.diff(date);
643
+ const chosenUnit = unitsCorrespondence[unit] || this.guessUnit(rawDiffMs);
644
+ if (!chosenUnit) {
645
+ throw new Error('Cannot guess unit for fromDate');
646
+ }
647
+ const diff = this.diff(date, chosenUnit);
648
+ const u = chosenUnit; // text unit, you may want a label here
649
+ return diff > 0
650
+ ? _('%d %s before', diff, u)
651
+ : diff < 0
652
+ ? _('%d %s after', -diff, u)
653
+ : _('The same %s', u);
654
+ }
655
+ startOf(unit = 'd') {
656
+ var _a, _b;
657
+ const u = unitsCorrespondence[unit] || unit;
658
+ if (!u) {
659
+ throw new Error('Invalid unit for startOf: ' + unit);
660
+ }
661
+ let v = this.value;
662
+ const zeroTime = (obj) => obj.with({
663
+ hour: 0, minute: 0, second: 0,
664
+ millisecond: 0, microsecond: 0, nanosecond: 0,
665
+ });
666
+ switch (u) {
667
+ case 'y':
668
+ if (!('year' in v)) {
669
+ throw new Error('startOf("y") only valid for year-based types');
670
+ }
671
+ v = zeroTime(v.with({ month: 1, day: 1 }));
672
+ break;
673
+ case 'm':
674
+ if (!('month' in v)) {
675
+ throw new Error('startOf("m") only valid for month-based types');
676
+ }
677
+ v = zeroTime(v.with({ day: 1 }));
678
+ break;
679
+ case 'w': {
680
+ // ISO dayOfWeek: 1 (Mon) .. 7 (Sun)
681
+ const d = ('toPlainDate' in v) ? v.toPlainDate() : v;
682
+ const dow = d.dayOfWeek; // 1..7
683
+ const diffToMonday = dow - 1; // 0 for Monday
684
+ const newDate = d.subtract({ days: diffToMonday });
685
+ if ('toPlainDateTime' in v) {
686
+ v = zeroTime(newDate.toPlainDateTime((_b = (_a = v.toPlainTime) === null || _a === void 0 ? void 0 : _a.call(v)) !== null && _b !== void 0 ? _b : Temporal.PlainTime.from('00:00')));
687
+ }
688
+ else {
689
+ v = newDate; // PlainDate case
690
+ }
691
+ break;
692
+ }
693
+ case 'd':
694
+ v = zeroTime(v);
695
+ break;
696
+ case 'h':
697
+ v = v.with({ minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0 });
698
+ break;
699
+ case 'i':
700
+ v = v.with({ second: 0, millisecond: 0, microsecond: 0, nanosecond: 0 });
701
+ break;
702
+ case 's':
703
+ v = v.with({ millisecond: 0, microsecond: 0, nanosecond: 0 });
704
+ break;
705
+ default:
706
+ throw new Error('Invalid unit for startOf: ' + unit);
707
+ }
708
+ return this.withValue(v);
709
+ }
710
+ endOf(unit = "d") {
711
+ const tz = Temporal.Now.timeZoneId();
712
+ const u = unitsCorrespondence[unit] || unit;
713
+ // 1. Convert current value to a ZonedDateTime (using your conventions)
714
+ const toZdt = (v) => {
715
+ switch (this.kind) {
716
+ case "zoned":
717
+ return this.value;
718
+ case "datetime": {
719
+ const pdt = this.value;
720
+ const iso = `${pdt.toString()}[${tz}]`;
721
+ return Temporal.ZonedDateTime.from(iso);
722
+ }
723
+ case "date": {
724
+ const d = this.value;
725
+ const iso = `${d.toString()}T00:00[${tz}]`;
726
+ return Temporal.ZonedDateTime.from(iso);
727
+ }
728
+ case "time": {
729
+ const t = this.value;
730
+ const today = Temporal.Now.plainDateISO();
731
+ const iso = `${today.toString()}T${t.toString()}[${tz}]`;
732
+ return Temporal.ZonedDateTime.from(iso);
733
+ }
734
+ case "year-month": {
735
+ const ym = this.value;
736
+ const d = ym.toPlainDate({ day: 1 });
737
+ const iso = `${d.toString()}T00:00[${tz}]`;
738
+ return Temporal.ZonedDateTime.from(iso);
739
+ }
740
+ case "month-day": {
741
+ const md = this.value;
742
+ const today = Temporal.Now.plainDateISO();
743
+ const d = md.toPlainDate({ year: today.year });
744
+ const iso = `${d.toString()}T00:00[${tz}]`;
745
+ return Temporal.ZonedDateTime.from(iso);
746
+ }
747
+ default:
748
+ throw new Error("Unsupported kind in endOf");
749
+ }
750
+ };
751
+ let zdt = toZdt(this.value);
752
+ // 2. compute start of next unit
753
+ let next;
754
+ switch (u) {
755
+ case "y": {
756
+ next = zdt.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0,
757
+ millisecond: 0, microsecond: 0, nanosecond: 0 })
758
+ .add({ years: 1 });
759
+ break;
760
+ }
761
+ case "m": {
762
+ next = zdt.with({ day: 1, hour: 0, minute: 0, second: 0,
763
+ millisecond: 0, microsecond: 0, nanosecond: 0 })
764
+ .add({ months: 1 });
765
+ break;
766
+ }
767
+ case "w": {
768
+ // ISO week: Monday = 1, Sunday = 7
769
+ const dow = zdt.toPlainDate().dayOfWeek;
770
+ const diffToMonday = -(dow - 1);
771
+ const weekStart = zdt.add({ days: diffToMonday }).with({
772
+ hour: 0, minute: 0, second: 0,
773
+ millisecond: 0, microsecond: 0, nanosecond: 0
774
+ });
775
+ next = weekStart.add({ weeks: 1 });
776
+ break;
777
+ }
778
+ case "d": {
779
+ next = zdt
780
+ .with({
781
+ hour: 0, minute: 0, second: 0,
782
+ millisecond: 0, microsecond: 0, nanosecond: 0
783
+ })
784
+ .add({ days: 1 });
785
+ break;
786
+ }
787
+ case "h": {
788
+ next = zdt.with({
789
+ minute: 0, second: 0,
790
+ millisecond: 0, microsecond: 0, nanosecond: 0
791
+ }).add({ hours: 1 });
792
+ break;
793
+ }
794
+ case "i": // minute
795
+ case "n": // minute alternative?
796
+ case "min": {
797
+ next = zdt.with({
798
+ second: 0,
799
+ millisecond: 0, microsecond: 0, nanosecond: 0
800
+ }).add({ minutes: 1 });
801
+ break;
802
+ }
803
+ case "s": {
804
+ next = zdt.with({
805
+ millisecond: 0, microsecond: 0, nanosecond: 0
806
+ }).add({ seconds: 1 });
807
+ break;
808
+ }
809
+ default:
810
+ throw new Error("Invalid unit for endOf: " + unit);
811
+ }
812
+ // 3. endOf = startOfNext - 1 millisecond
813
+ const end = (next.subtract({ milliseconds: 1 }));
814
+ // 4. Convert back to original kind
815
+ switch (this.kind) {
816
+ case "zoned":
817
+ return this.withValue(end);
818
+ case "datetime":
819
+ return this.withValue(end.toPlainDateTime());
820
+ case "date":
821
+ return this.withValue(end.toPlainDate());
822
+ case "time":
823
+ return this.withValue(end.toPlainTime());
824
+ case "year-month": {
825
+ const p = end.toPlainDate();
826
+ return this.withValue(new Temporal.PlainYearMonth(p.year, p.month));
827
+ }
828
+ case "month-day": {
829
+ const p = end.toPlainDate();
830
+ return this.withValue(new Temporal.PlainMonthDay(p.month, p.day));
831
+ }
832
+ default:
833
+ throw new Error("Unsupported kind in endOf");
834
+ }
835
+ }
468
836
  }
469
837
  _bbnDt_value = new WeakMap();
838
+ /** System time zone ID (e.g. "Europe/Rome") */
839
+ bbnDt.systemTimeZoneId = Temporal.Now.timeZoneId();
470
840
  export default bbnDt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbn/bbn",
3
- "version": "2.0.68",
3
+ "version": "2.0.70",
4
4
  "description": "Javascript toolkit",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",