@the-w1zard/dx 1.0.11 → 1.0.13

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/src/date.ts ADDED
@@ -0,0 +1,660 @@
1
+ export const DATE_TAG = 0x4432; // "D2"
2
+ export const D2_CODEC = {
3
+ tag: DATE_TAG,
4
+ test(v: A): v is D2 { return v instanceof Date; },
5
+ enc(d: D): N { return d.getTime(); },
6
+ dec(ms: N): D2 { return new D2I(ms); }, // was `new D2(ms)` — D2 is the interface, D2I is the class
7
+ };
8
+ const FORMAT = {
9
+ full: { weekday: "short", year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" },
10
+ dm: { month: "short", day: "numeric" },
11
+ dmy: { year: "numeric", month: "short", day: "numeric" },
12
+ dmyw: { weekday: "short", year: "numeric", month: "short", day: "numeric" },
13
+ t: { hour: "2-digit", minute: "2-digit" }
14
+ };
15
+ export class D2I extends Date implements D2 {
16
+ constructor(value?: S | N | D) {
17
+ if (value instanceof Date) super(value.getTime());
18
+ else if (value === undefined) super();
19
+ else super(value);
20
+ }
21
+ public toLocalString(format: A = FORMAT.full): S { return super.toLocaleString("en-US", format); }
22
+ static now(): N { return Date.now(); }
23
+ static fromUnix(seconds: N): D2 { return new D2I(seconds * 1000); }
24
+ static fromUnixMs(ms: N): D2 { return new D2I(ms); }
25
+ static fromParts(year: N, month: N, day: N, hour = 0, minute = 0, second = 0, ms = 0): D2 {
26
+ return new D2I(new Date(year, month - 1, day, hour, minute, second, ms));
27
+ }
28
+ static today(): D2 { return new D2I().startOfDay(); }
29
+ static min(...dates: D[]): D2 { return new D2I(Math.min(...dates.map((date) => date.getTime()))); }
30
+ static max(...dates: D[]): D2 { return new D2I(Math.max(...dates.map((date) => date.getTime()))); }
31
+ public clone(): D2 { return new D2I(this); }
32
+ public isValid(): B { return Number.isFinite(this.getTime()); }
33
+ public month(): N { return this.getMonth() + 1; }
34
+ public dateOfWeek(): N { return this.getDay(); }
35
+ public dayOfYear(): N {
36
+ const start = new Date(this.getFullYear(), 0, 1);
37
+ const current = new Date(this.getFullYear(), this.getMonth(), this.getDate());
38
+ return Math.floor((current.getTime() - start.getTime()) / 86400000) + 1;
39
+ }
40
+ public quarter(): N { return Math.floor(this.getMonth() / 3) + 1; }
41
+ public unix(): N { return Math.floor(this.getTime() / 1000); }
42
+ public addMs(amount: N): D2 { return new D2I(this.getTime() + amount); }
43
+ public addSeconds(amount: N): D2 { return this.addMs(amount * 1000); }
44
+ public addMinutes(amount: N): D2 { return this.addMs(amount * 60000); }
45
+ public addHours(amount: N): D2 { return this.addMs(amount * 3600000); }
46
+ public addDays(amount: N): D2 {
47
+ const out = this.clone();
48
+ out.setDate(out.getDate() + amount);
49
+ return out;
50
+ }
51
+ public addWeeks(amount: N): D2 { return this.addDays(amount * 7); }
52
+ public addMonths(amount: N): D2 {
53
+ const out = this.clone();
54
+ const day = out.getDate();
55
+ out.setDate(1);
56
+ out.setMonth(out.getMonth() + amount);
57
+ out.setDate(Math.min(day, out.daysInMonth()));
58
+ return out;
59
+ }
60
+ public addYears(amount: N): D2 {
61
+ const out = this.clone();
62
+ const month = out.getMonth();
63
+ const day = out.getDate();
64
+ out.setDate(1);
65
+ out.setFullYear(out.getFullYear() + amount);
66
+ out.setMonth(month);
67
+ out.setDate(Math.min(day, out.daysInMonth()));
68
+ return out;
69
+ }
70
+ public subDays(amount: N): D2 { return this.addDays(-amount); }
71
+ public subMonths(amount: N): D2 { return this.addMonths(-amount); }
72
+ public subYears(amount: N): D2 { return this.addYears(-amount); }
73
+ public startOfDay(): D2 {
74
+ const out = this.clone();
75
+ out.setHours(0, 0, 0, 0);
76
+ return out;
77
+ }
78
+ public endOfDay(): D2 {
79
+ const out = this.clone();
80
+ out.setHours(23, 59, 59, 999);
81
+ return out;
82
+ }
83
+ public startOfWeek(weekStartsOn = 0): D2 {
84
+ const out = this.startOfDay();
85
+ out.setDate(out.getDate() - ((out.getDay() - weekStartsOn + 7) % 7));
86
+ return out;
87
+ }
88
+ public endOfWeek(weekStartsOn = 0): D2 { return this.startOfWeek(weekStartsOn).addDays(7).addMs(-1); }
89
+ public startOfMonth(): D2 {
90
+ const out = this.startOfDay();
91
+ out.setDate(1);
92
+ return out;
93
+ }
94
+ public endOfMonth(): D2 { return this.addMonths(1).startOfMonth().addMs(-1); }
95
+ public startOfQuarter(): D2 {
96
+ const out = this.startOfDay();
97
+ out.setMonth(Math.floor(out.getMonth() / 3) * 3, 1);
98
+ return out;
99
+ }
100
+ public endOfQuarter(): D2 { return this.startOfQuarter().addMonths(3).addMs(-1); }
101
+ public startOfYear(): D2 {
102
+ const out = this.startOfDay();
103
+ out.setMonth(0, 1);
104
+ return out;
105
+ }
106
+ public endOfYear(): D2 { return this.startOfYear().addYears(1).addMs(-1); }
107
+ public daysInMonth(): N { return new Date(this.getFullYear(), this.getMonth() + 1, 0).getDate(); }
108
+ public daysInYear(): N { return this.isLeapYear() ? 366 : 365; }
109
+ public isLeapYear(): B {
110
+ const year = this.getFullYear();
111
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
112
+ }
113
+ public isWeekend(): B { return this.getDay() === 0 || this.getDay() === 6; }
114
+ public isWeekday(): B { return !this.isWeekend(); }
115
+ public isBefore(other: D): B { return this.getTime() < other.getTime(); }
116
+ public isAfter(other: D): B { return this.getTime() > other.getTime(); }
117
+ public isEqual(other: D): B { return this.getTime() === other.getTime(); }
118
+ public isBetween(start: D, end: D): B { return this.getTime() >= start.getTime() && this.getTime() <= end.getTime(); }
119
+ public diffMs(other: D): N { return this.getTime() - other.getTime(); }
120
+ public diffSeconds(other: D): N { return this.diffMs(other) / 1000; }
121
+ public diffMinutes(other: D): N { return this.diffMs(other) / 60000; }
122
+ public diffHours(other: D): N { return this.diffMs(other) / 3600000; }
123
+ public diffDays(other: D): N { return this.diffMs(other) / 86400000; }
124
+ public sameDay(other: D): B {
125
+ return this.getFullYear() === other.getFullYear() &&
126
+ this.getMonth() === other.getMonth() &&
127
+ this.getDate() === other.getDate();
128
+ }
129
+ public sameMonth(other: D): B {
130
+ return this.getFullYear() === other.getFullYear() &&
131
+ this.getMonth() === other.getMonth();
132
+ }
133
+ public sameYear(other: D): B { return this.getFullYear() === other.getFullYear(); }
134
+ public toUnixMs(): N { return this.getTime(); }
135
+ public toUnix(): N { return this.unix(); }
136
+ public toD2(): D2 { return this.clone(); }
137
+ public ago(): S { return this.fromNow(); }
138
+ public fromNow(): S { return this.from(new Date()); }
139
+ public from(other: D): S {
140
+ const ms = this.getTime() - other.getTime();
141
+ const abs = Math.abs(ms);
142
+ const future = ms > 0;
143
+
144
+ const units: [N, S][] = [
145
+ [1000, "second"],
146
+ [60_000, "minute"],
147
+ [3_600_000, "hour"],
148
+ [86_400_000, "day"],
149
+ [604_800_000, "week"],
150
+ [2_592_000_000, "month"],
151
+ [31_536_000_000, "year"],
152
+ ];
153
+
154
+ if (abs < 1000) return "just now";
155
+
156
+ let n = 1;
157
+ let unit = "second";
158
+
159
+ for (let i = units.length - 1; i >= 0; i--) {
160
+ const [size, name] = units[i];
161
+ if (abs >= size) {
162
+ n = Math.floor(abs / size);
163
+ unit = name;
164
+ break;
165
+ }
166
+ }
167
+
168
+ const plural = n === 1 ? "" : "s";
169
+ return future ? `in ${n} ${unit}${plural}` : `${n} ${unit}${plural} ago`;
170
+ }
171
+ static from(v: A): D2 {
172
+ if (v instanceof D2I) return v.clone();
173
+ if (v instanceof Date) return new D2I(v.getTime());
174
+ if (typeof v === "number") return new D2I(v);
175
+
176
+ if (typeof v === "string") {
177
+ const s = v.trim();
178
+ if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return D2I.parseDate(s);
179
+ if (/^\d{4}-\d{2}-\d{2}T.*(Z|[+-]\d{2}:?\d{2})$/.test(s)) return new D2I(s);
180
+ if (/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/.test(s)) return D2I.parseDateTime(s.replace("T", " "));
181
+ return new D2I(s);
182
+ }
183
+
184
+ if (Array.isArray(v)) {
185
+ const [y, m, d, h = 0, min = 0, sec = 0, ms = 0] = v as N[];
186
+ return D2I.fromParts(y, m, d, h, min, sec, ms);
187
+ }
188
+
189
+ if (typeof v === "object" && v !== null) {
190
+ const o = v as A;
191
+
192
+ if ("date" in o || "time" in o) {
193
+ const dateStr = (o.date ?? "").trim();
194
+ const timeStr = (o.time ?? "").trim();
195
+ if (!dateStr) return new D2I(NaN);
196
+
197
+ const [y, m, d] = dateStr.split("-").map(Number);
198
+ if (![y, m, d].every(Number.isFinite)) return new D2I(NaN);
199
+
200
+ let h = 0, min = 0, sec = 0, ms = 0;
201
+ if (timeStr) {
202
+ const parts = timeStr.split(":").map(Number);
203
+ [h = 0, min = 0, sec = 0] = parts;
204
+ if (parts.length > 3) ms = parts[3];
205
+ if (![h, min, sec, ms].every(Number.isFinite)) return new D2I(NaN);
206
+ }
207
+ return D2I.fromParts(y, m, d, h, min, sec, ms);
208
+ }
209
+
210
+ if ("year" in o || "month" in o || "day" in o) {
211
+ const y = o.year ?? NaN;
212
+ const m = o.month ?? NaN;
213
+ const d = o.day ?? NaN;
214
+ if (![y, m, d].every(Number.isFinite)) return new D2I(NaN);
215
+ return D2I.fromParts(y, m, d, o.hour ?? 0, o.minute ?? 0, o.second ?? 0, o.ms ?? 0);
216
+ }
217
+ }
218
+
219
+ return new D2I(NaN);
220
+ }
221
+ public toInputDate(): S { return this.fmt("YYYY-MM-DD"); }
222
+ public toInputTime(): S { return this.fmt("HH:mm"); }
223
+ public toInputDateTime(): S { return this.fmt("YYYY-MM-DDTHH:mm"); }
224
+ public toInputTimeWithSeconds(): S { return this.fmt("HH:mm:ss"); }
225
+ public next(unit: DAYSD2): D2 {
226
+ const days = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
227
+
228
+ if (days.includes(unit)) {
229
+ const target = days.indexOf(unit);
230
+ const diff = (target - this.getDay() + 7) % 7 || 7;
231
+ return this.addDays(diff);
232
+ }
233
+ if (unit === "day") return this.addDays(1);
234
+ if (unit === "week") return this.addWeeks(1);
235
+ if (unit === "month") return this.addMonths(1);
236
+ if (unit === "quarter") return this.addMonths(3);
237
+ if (unit === "year") return this.addYears(1);
238
+
239
+ return this.clone();
240
+ }
241
+ public prev(unit: Parameters<D2["next"]>[0]): D2 {
242
+ const days = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
243
+
244
+ if (days.includes(unit)) {
245
+ const target = days.indexOf(unit);
246
+ const diff = (this.getDay() - target + 7) % 7 || 7;
247
+ return this.subDays(diff);
248
+ }
249
+ if (unit === "day") return this.subDays(1);
250
+ if (unit === "week") return this.subWeeks(1);
251
+ if (unit === "month") return this.subMonths(1);
252
+ if (unit === "quarter") return this.subMonths(3);
253
+ if (unit === "year") return this.subYears(1);
254
+
255
+ return this.clone();
256
+ }
257
+ public nextWeek(): D2 { return this.addWeeks(1); }
258
+ public prevWeek(): D2 { return this.subWeeks(1); }
259
+ public nextMonth(): D2 { return this.addMonths(1); }
260
+ public prevMonth(): D2 { return this.subMonths(1); }
261
+ public nextQuarter(): D2 { return this.addMonths(3); }
262
+ public prevQuarter(): D2 { return this.subMonths(3); }
263
+ public nextYear(): D2 { return this.addYears(1); }
264
+ public prevYear(): D2 { return this.subYears(1); }
265
+ public subWeeks(n: N): D2 { return this.addWeeks(-n); }
266
+ public at(hour: N, minute = 0, second = 0, ms = 0): D2 {
267
+ const out = this.clone();
268
+ out.setHours(hour, minute, second, ms);
269
+ return out;
270
+ }
271
+ public atNoon(): D2 { return this.at(12); }
272
+ public midnight(): D2 { return this.startOfDay(); }
273
+ public addBusinessDays(n: N): D2 {
274
+ let out = this.clone();
275
+ let left = Math.abs(n);
276
+ const dir = n < 0 ? -1 : 1;
277
+
278
+ while (left > 0) {
279
+ out = out.addDays(dir);
280
+ if (out.isWeekday()) left--;
281
+ }
282
+ return out;
283
+ }
284
+ public nextBusinessDay(): D2 { return this.addBusinessDays(1); }
285
+ public prevBusinessDay(): D2 { return this.addBusinessDays(-1); }
286
+ public isBusinessDay(): B { return this.isWeekday(); }
287
+ public calendarDays(other: D): N {
288
+ const a = new D2I(this).startOfDay();
289
+ const b = new D2I(other).startOfDay();
290
+ return Math.round(b.diffDays(a));
291
+ }
292
+ public businessDays(other: D): N {
293
+ let a = new D2I(this).startOfDay();
294
+ const b = new D2I(other).startOfDay();
295
+ let n = 0;
296
+
297
+ while (a.isBefore(b)) {
298
+ if (a.isWeekday()) n++;
299
+ a = a.addDays(1);
300
+ }
301
+ return n;
302
+ }
303
+ public diffHuman(other: D = new Date()): S {
304
+ const ms = Math.abs(this.diffMs(other));
305
+ const units: [N, S][] = [
306
+ [31_536_000_000, "y"],
307
+ [2_592_000_000, "mo"],
308
+ [604_800_000, "w"],
309
+ [86_400_000, "d"],
310
+ [3_600_000, "h"],
311
+ [60_000, "m"],
312
+ [1000, "s"],
313
+ ];
314
+
315
+ for (const [size, label] of units) {
316
+ if (ms >= size) return `${Math.floor(ms / size)}${label}`;
317
+ }
318
+ return `${ms}ms`;
319
+ }
320
+ public fmt(format: S): S {
321
+ const pad = (n: N, len = 2) => String(n).padStart(len, "0");
322
+ const map: Record<S, S> = {
323
+ YYYY: String(this.getFullYear()),
324
+ YY: pad(this.getFullYear() % 100),
325
+ MMMM: this.toLocaleString("en-US", { month: "long" }),
326
+ MMM: this.toLocaleString("en-US", { month: "short" }),
327
+ MM: pad(this.month()),
328
+ M: String(this.month()),
329
+ DD: pad(this.getDate()),
330
+ D: String(this.getDate()),
331
+ dddd: this.toLocaleString("en-US", { weekday: "long" }),
332
+ ddd: this.toLocaleString("en-US", { weekday: "short" }),
333
+ HH: pad(this.getHours()),
334
+ H: String(this.getHours()),
335
+ hh: pad(this.getHours() % 12 || 12),
336
+ h: String(this.getHours() % 12 || 12),
337
+ mm: pad(this.getMinutes()),
338
+ m: String(this.getMinutes()),
339
+ ss: pad(this.getSeconds()),
340
+ s: String(this.getSeconds()),
341
+ SSS: pad(this.getMilliseconds(), 3),
342
+ A: this.getHours() < 12 ? "AM" : "PM",
343
+ a: this.getHours() < 12 ? "am" : "pm",
344
+ };
345
+ return format.replace(
346
+ /YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|SSS|A|a/g,
347
+ token => map[token],
348
+ );
349
+ }
350
+ static parseDate(v: S): D2 {
351
+ const [y, m, d] = v.split("-").map(Number);
352
+ return D2I.fromParts(y, m, d);
353
+ }
354
+ static parseDateTime(v: S): D2 {
355
+ const [date, time = "00:00:00"] = v.split(" ");
356
+ const [y, m, d] = date.split("-").map(Number);
357
+ const [h, min, sec = 0] = time.split(":").map(Number);
358
+ return D2I.fromParts(y, m, d, h, min, sec);
359
+ }
360
+ public set(f: Partial<{ year: N; month: N; day: N; hour: N; minute: N; second: N; ms: N; }>): D2 {
361
+ const out = this.clone();
362
+
363
+ if (f.year !== undefined || f.month !== undefined || f.day !== undefined) {
364
+ const y = f.year ?? out.getFullYear();
365
+ const m = (f.month ?? out.getMonth() + 1) - 1;
366
+ const targetDay = f.day ?? out.getDate();
367
+
368
+ out.setDate(1);
369
+ out.setFullYear(y, m, 1);
370
+ out.setDate(Math.min(targetDay, out.daysInMonth()));
371
+ }
372
+
373
+ if (f.hour !== undefined) out.setHours(f.hour);
374
+ if (f.minute !== undefined) out.setMinutes(f.minute);
375
+ if (f.second !== undefined) out.setSeconds(f.second);
376
+ if (f.ms !== undefined) out.setMilliseconds(f.ms);
377
+
378
+ return out;
379
+ }
380
+ public withDate(year: N, month: N, day: N): D2 {
381
+ return this.set({ year, month, day });
382
+ }
383
+ public sameTimeOn(other: D): D2 {
384
+ return this.set({
385
+ year: other.getFullYear(),
386
+ month: other.getMonth() + 1,
387
+ day: other.getDate(),
388
+ });
389
+ }
390
+ public to(other: D): R2 { return new R2I(this.clone(), D2I.from(other)); }
391
+ public isSameOrBefore(other: D): B { return !this.isAfter(other); }
392
+ public isSameOrAfter(other: D): B { return !this.isBefore(other); }
393
+ public clamp(min: D, max: D): D2 {
394
+ if (this.isBefore(min)) return D2I.from(min);
395
+ if (this.isAfter(max)) return D2I.from(max);
396
+ return this.clone();
397
+ }
398
+ public isSame(unit: "day" | "week" | "month" | "quarter" | "year", other: D): B {
399
+ if (unit === "day") return this.sameDay(other);
400
+ if (unit === "week") return this.startOfWeek(0).sameDay(D2I.from(other).startOfWeek());
401
+ if (unit === "month") return this.sameMonth(other);
402
+ if (unit === "quarter") return this.sameYear(other) && this.quarter() === D2I.from(other).quarter();
403
+ if (unit === "year") return this.sameYear(other);
404
+ return false;
405
+ }
406
+ public isToday(): B { return this.sameDay(new D2I()); }
407
+ public isTomorrow(): B { return this.sameDay(new D2I().addDays(1)); }
408
+ public isYesterday(): B { return this.sameDay(new D2I().subDays(1)); }
409
+ public age(unit: "years" | "months" | "days" = "years"): N {
410
+ const now = new D2I();
411
+ if (unit === "days") return Math.floor(now.diffDays(this));
412
+ if (unit === "months") {
413
+ return (now.getFullYear() - this.getFullYear()) * 12
414
+ + (now.getMonth() - this.getMonth())
415
+ - (now.getDate() < this.getDate() ? 1 : 0);
416
+ }
417
+ let years = now.getFullYear() - this.getFullYear();
418
+ if (now.getMonth() < this.getMonth() || (now.getMonth() === this.getMonth() && now.getDate() < this.getDate())) years--;
419
+ return years;
420
+ }
421
+ }
422
+
423
+ export class R2I implements R2 {
424
+ constructor(public start: D2, public end: D2) {}
425
+ contains(d: D): B { return D2I.from(d).isBetween(this.start, this.end); }
426
+ overlaps(other: R2): B { return this.start.isBefore(other.end) && other.start.isBefore(this.end); }
427
+ durationMs(): N { return this.end.diffMs(this.start); }
428
+ days(): N { return Math.floor(this.durationMs() / 86_400_000); }
429
+ eachDay(fn: (d: D2, i: N) => void): void {
430
+ let cur = this.start.startOfDay();
431
+ for (let i = 0; cur.isBefore(this.end); i++) {
432
+ fn(cur, i);
433
+ cur = cur.addDays(1);
434
+ }
435
+ }
436
+ toArray(): A2I<D2> {
437
+ const out = new A2<D2>();
438
+ this.eachDay(d => out.push(d));
439
+ return out;
440
+ }
441
+ }
442
+ export const R2II = (start: D, end: D): R2 => new R2I(new D2I(start), new D2I(end));
443
+
444
+ globalThis.D2 = D2I;
445
+ globalThis.R2 = R2I;
446
+ globalThis.R2FN = R2II;
447
+
448
+ /* ====================================================================================================
449
+ I don't know about you, but for me... I have spent so much time fine tuning appt schedulers and
450
+ custom formatting the dates that come back, making sure the times line up with the time zone
451
+ making sure it outputs in the req format for the next system to consume... fuck that shit dude...
452
+ I know I have another project coming up where, there will be a literal fuck ton of that bullshit...
453
+ so this fucking thing is already huge, TO WHICH... I knew coming into this one that this was
454
+ going to be a rather large one in terms of surface area... just look at the libs that are available.
455
+
456
+ Lets get the hardest thing off the board, becasue I beleive everything else will fall into place with
457
+ it get completed... which is:
458
+ On the same level os ease and quicknesss as say... something like use state, we need to not only
459
+ preserve the values we do not edit... but also allows us to edit the req values needed and make
460
+ it so that, we can code it into our solutions like a reusable ui comp.
461
+ I know... rediculous, considering whats currently available... but lets try to hit that target and...
462
+ pray to the fucking dev gods that tthey bestow us with the bits and bytes that would help us
463
+ get to the finish line.
464
+ =============================================================== ⇦✦──⇦✦──✦──✦──← 'pew pew' 🧙 [D2] */
465
+ /* ====================================================================================================
466
+ D2.now().subMinutes(3).ago(); // "3 minutes ago"
467
+ D2.now().addHours(2).fromNow(); // "in 2 hours"
468
+
469
+ d.next("friday");
470
+ d.prev("monday").startOfDay();
471
+ d.nextMonth().startOfMonth();
472
+
473
+ d.at(9, 30); // today at 09:30 local
474
+ d.at(9, 30, 0, 0); // h, m, s, ms
475
+ d.atNoon();
476
+ d.midnight(); // alias for startOfDay
477
+
478
+ const range = D2.range(start, end);
479
+
480
+ range.contains(d);
481
+ range.days();
482
+ range.overlaps(other);
483
+ range.durationMs();
484
+
485
+ const sprint = R2(D2.today(), D2.today().addDays(14));
486
+
487
+ sprint.contains(D2.now());
488
+ sprint.days();
489
+ sprint.eachDay(d => console.log(d.toLocalString(FORMAT.dmy)));
490
+
491
+ D2.today().addBusinessDays(3);
492
+ D2.today().nextBusinessDay().at(9);
493
+
494
+ d.diffHuman(other); // "3d 4h 12m"
495
+ d.calendarDays(other); // 3
496
+ d.businessDays(other); // 2
497
+
498
+ d.fmt("YYYY-MM-DD");
499
+ d.fmt("MMM D, YYYY h:mm A");
500
+ d.fmt("dddd");
501
+
502
+ d.fmt("YYYY-MM-DD");
503
+ d.fmt("MMM D, YYYY h:mm A");
504
+ d.fmt("dddd, MMM D");
505
+
506
+ D2.parseDate("2026-10-28");
507
+ D2.parseDateTime("2026-10-28 14:30");
508
+
509
+ const d = D2.fromParts(2026, 10, 28, 14, 30, 0, 0); // 2026-10-28 14:30
510
+
511
+ d.set({ hour: 9 }); // 2026-10-28 09:30 (date + minute preserved)
512
+ d.set({ day: 15 }); // 2026-10-15 14:30 (time preserved)
513
+ d.set({ month: 12 }); // 2026-12-28 14:30
514
+ d.set({ year: 2027 }); // 2027-10-28 14:30
515
+ d.set({ hour: 9, minute: 0 }); // 2026-10-28 09:00
516
+
517
+ d.set({ month: 11, day: 15 }); // 2026-11-15 14:30
518
+ d.set({ year: 2027, hour: 0, minute: 0 }); // 2027-10-28 00:00
519
+
520
+ d.set({ year: 2027, month: 3, day: 15, hour: 9, minute: 30, second: 0, ms: 0 });
521
+ // 2027-03-15 09:30:00.000
522
+
523
+ const jan31 = D2.fromParts(2026, 1, 31, 10, 0);
524
+ jan31.set({ month: 2 }); // 2026-02-28 10:00 (Feb has 28 days in 2026)
525
+ jan31.set({ month: 2, day: 31 }); // 2026-02-28 10:00 (clamped)
526
+ D2.fromParts(2028, 1, 31).set({ month: 2 }); // 2028-02-29 (leap year)
527
+
528
+ d.at(9, 30); // set({ hour: 9, minute: 30 })
529
+ d.withDate(2027, 3, 15); // set({ year: 2027, month: 3, day: 15 })
530
+ d.startOfDay(); // set({ hour: 0, minute: 0, second: 0, ms: 0 })
531
+ d.endOfDay(); // set({ hour: 23, minute: 59, second: 59, ms: 999 })
532
+
533
+ const other = D2.fromParts(2026, 12, 25, 8, 0);
534
+ d.sameTimeOn(other); // 2026-12-25 14:30 (d's time, other's date)
535
+
536
+ // user picks 9:30am for an appointment, date stays
537
+ store.set('appointment', store.get('appointment').set({ hour: 9, minute: 30 }));
538
+
539
+ // user picks a new day, time stays
540
+ store.set('appointment', store.get('appointment').set({ day: 22 }));
541
+
542
+ // user picks a new month and day
543
+ store.set('appointment', store.get('appointment').set({ month: 11, day: 15 }));
544
+
545
+ // functional update — same shape as React's setState(prev => ...)
546
+ store.update('appointment', prev => prev.set({ hour: 9 }));
547
+
548
+ // change time, then date, each one preserving the rest
549
+ const moved = appt.set({ hour: 9 }).set({ month: 12 }).set({ day: 24 });
550
+ // 2026-12-24 09:30 — hour from first, month from second, day from third
551
+
552
+ // day cell clicked — keep time, change date
553
+ const onDayClick = (day: number) =>
554
+ store.set('selected', store.get('selected').set({ day }));
555
+
556
+ // time picker — keep date, change time
557
+ const onTimePick = (hour: number, minute: number) =>
558
+ store.set('selected', store.get('selected').set({ hour, minute }));
559
+
560
+ // month nav
561
+ const nextMonth = () => {
562
+ const d = store.get('selected');
563
+ store.set('selected', d.set({ month: d.month() % 12 + 1, year: d.month() === 12 ? d.getFullYear() + 1 : d.getFullYear() }));
564
+ };
565
+
566
+ // form gives split fields
567
+ const raw = { date: "2026-10-28", time: "14:30" };
568
+
569
+ // ingest
570
+ let appt = D2.from(raw); // 2026-10-28 14:30 local
571
+ if (!appt.isValid()) return; // form incomplete/invalid
572
+
573
+ // edit — same set as everything else
574
+ appt = appt.set({ minute: 0 }); // 14:30 → 14:00, date preserved
575
+ appt = appt.set({ day: 29 }); // 28 → 29, time preserved
576
+
577
+ // write back to form
578
+ form.date.value = appt.toInputDate(); // "2026-10-29"
579
+ form.time.value = appt.toInputTime(); // "14:00"
580
+
581
+ // or to the store
582
+ store.set("appointment", appt);
583
+
584
+ D2.from({ date: "2026-10-28" }) // 2026-10-28 00:00 local
585
+ D2.from({ time: "14:30" }) // Invalid Date — no date
586
+ D2.from({ date: "2026-10-28", time: "" }) // 2026-10-28 00:00 local
587
+ D2.from({ date: "2026-10-28", time: "14:30:15" }) // 14:30:15
588
+ D2.from("2026-10-28") // local midnight, not UTC
589
+ D2.from("2026-10-28T14:30:00Z") // correct instant, shown local
590
+ D2.from("2026-10-28T14:30") // local 14:30
591
+ D2.from("2026-10-28 14:30") // local 14:30 (space separator)
592
+ D2.from("garbage") // Invalid Date — isValid() false
593
+ D2.from(0) // 1970-01-01 00:00 local
594
+ D2.from(NaN) // Invalid Date
595
+ D2.from([2026, 10, 28]) // 2026-10-28 00:00
596
+ D2.from({ year: 2026, month: 10, day: 28 }) // 2026-10-28 00:00
597
+ D2.from({ year: 2026, month: 10, day: 28, hour: 9 }) // 2026-10-28 09:00
598
+
599
+ export interface D2 extends Date {
600
+ // ... existing ...
601
+
602
+ set(f: Partial<{
603
+ year: N; month: N; day: N;
604
+ hour: N; minute: N; second: N; ms: N;
605
+ }>): D2;
606
+
607
+ withDate(year: N, month: N, day: N): D2;
608
+ sameTimeOn(other: D): D2;
609
+
610
+ toInputDate(): S;
611
+ toInputTime(): S;
612
+ toInputTimeWithSeconds(): S;
613
+ toInputDateTime(): S;
614
+
615
+ // static (add to the class, not the interface — statics don't go in interface)
616
+ }
617
+ =============================================================== ⇦✦──⇦✦──✦──✦──← 'pew pew' 🧙 [D2] */
618
+
619
+ /**
620
+ * enc(value:A) {
621
+ if (D2_CODEC.test(value)) {
622
+ writeTag(DATE_TAG);
623
+ writeI64(value.getTime());
624
+ return;
625
+ }
626
+
627
+ // existing object / array / string / map handling
628
+ }
629
+ dec(value:A) {
630
+ if (tag === DATE_TAG) { return new D2(readI64()); }
631
+
632
+ // existing revival paths
633
+ }
634
+ */
635
+
636
+
637
+
638
+
639
+
640
+
641
+
642
+
643
+
644
+
645
+
646
+
647
+
648
+
649
+
650
+
651
+
652
+
653
+
654
+
655
+
656
+
657
+
658
+
659
+
660
+
package/src/dx.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { M2RI } from "./map_rows";
2
+ import { M2DI } from "./map_dense";
3
+ import { M2BINI } from "./map_bin";
1
4
  import { B2BI } from "./boolean_byte";
2
5
  import { M2I } from "./map";
3
6
  import { M2BI } from "./map_byte";
@@ -32,7 +35,10 @@ declare global {
32
35
  var B2B: typeof B2BI; // BOOLEAN BINARY
33
36
 
34
37
  var M2: typeof M2I; // map
35
- var M2B: typeof M2BI; // map
38
+ var M2B: typeof M2BI; // byte
39
+ var M2BIN: typeof M2BINI; // binary
40
+ var M2D: typeof M2DI; // dense
41
+ var M2R: typeof M2RI; // rows
36
42
 
37
43
  var SET2: typeof SET2I; // set
38
44
  var SET2B: typeof SET2BI; // set