@the-w1zard/dx 1.0.12 → 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/dist/index.js +4 -3
- package/package.json +2 -2
- package/src/array.ts +301 -0
- package/src/boolean_byte.ts +23 -0
- package/src/class.ts +0 -0
- package/src/date.ts +660 -0
- package/src/error.ts +81 -0
- package/src/index.ts +45 -0
- package/src/iterator.ts +407 -0
- package/src/map.ts +513 -0
- package/src/map_bin.ts +179 -0
- package/src/map_byte.ts +418 -0
- package/src/map_dense.ts +167 -0
- package/src/map_rows.ts +145 -0
- package/src/number.ts +212 -0
- package/src/object.ts +291 -0
- package/src/promise.ts +309 -0
- package/src/reg_ex.ts +423 -0
- package/src/set.ts +198 -0
- package/src/set_byte.ts +223 -0
- package/src/soa_codec.ts +27 -0
- package/src/string.ts +175 -0
- package/src/unit8array.ts +374 -0
- package/src/url_search_params.ts +187 -0
- package/src/weak_map.ts +155 -0
- package/src/weak_set.ts +101 -0
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
|
+
|