@the-w1zard/dx 1.0.7 → 1.0.9

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