@esmalley/ts-utils 3.0.0 → 5.0.0

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.
Files changed (90) hide show
  1. package/dist/cjs/index.js +17 -27
  2. package/dist/cjs/index.js.map +7 -0
  3. package/dist/esm/index.js +17 -11
  4. package/dist/esm/index.js.map +7 -0
  5. package/dist/{cjs → types}/Kontororu/Socket.d.ts +8 -3
  6. package/dist/types/Kontororu/Socket.d.ts.map +1 -0
  7. package/dist/{esm/Text.d.ts → types/Textor.d.ts} +2 -2
  8. package/dist/types/Textor.d.ts.map +1 -0
  9. package/dist/{cjs → types}/index.d.ts +3 -1
  10. package/dist/types/index.d.ts.map +1 -0
  11. package/package.json +9 -7
  12. package/dist/cjs/Arithmetic.js +0 -9
  13. package/dist/cjs/Arrayifier.js +0 -69
  14. package/dist/cjs/CSV.js +0 -39
  15. package/dist/cjs/Color.js +0 -348
  16. package/dist/cjs/Dates.js +0 -358
  17. package/dist/cjs/Kontororu/Socket.d.ts.map +0 -1
  18. package/dist/cjs/Kontororu/Socket.js +0 -221
  19. package/dist/cjs/Kontororu.js +0 -27
  20. package/dist/cjs/Objector.js +0 -124
  21. package/dist/cjs/Sorter.js +0 -32
  22. package/dist/cjs/Style.js +0 -475
  23. package/dist/cjs/Text.d.ts +0 -5
  24. package/dist/cjs/Text.d.ts.map +0 -1
  25. package/dist/cjs/Text.js +0 -39
  26. package/dist/cjs/Theme.js +0 -507
  27. package/dist/cjs/Toaster.js +0 -49
  28. package/dist/cjs/index.d.ts.map +0 -1
  29. package/dist/esm/Arithmetic.d.ts +0 -4
  30. package/dist/esm/Arithmetic.d.ts.map +0 -1
  31. package/dist/esm/Arithmetic.js +0 -5
  32. package/dist/esm/Arrayifier.d.ts +0 -29
  33. package/dist/esm/Arrayifier.d.ts.map +0 -1
  34. package/dist/esm/Arrayifier.js +0 -65
  35. package/dist/esm/CSV.d.ts +0 -10
  36. package/dist/esm/CSV.d.ts.map +0 -1
  37. package/dist/esm/CSV.js +0 -35
  38. package/dist/esm/Color.d.ts +0 -93
  39. package/dist/esm/Color.d.ts.map +0 -1
  40. package/dist/esm/Color.js +0 -344
  41. package/dist/esm/Dates.d.ts +0 -51
  42. package/dist/esm/Dates.d.ts.map +0 -1
  43. package/dist/esm/Dates.js +0 -354
  44. package/dist/esm/Kontororu/Socket.d.ts +0 -44
  45. package/dist/esm/Kontororu/Socket.d.ts.map +0 -1
  46. package/dist/esm/Kontororu/Socket.js +0 -218
  47. package/dist/esm/Kontororu.d.ts +0 -8
  48. package/dist/esm/Kontororu.d.ts.map +0 -1
  49. package/dist/esm/Kontororu.js +0 -23
  50. package/dist/esm/Objector.d.ts +0 -32
  51. package/dist/esm/Objector.d.ts.map +0 -1
  52. package/dist/esm/Objector.js +0 -120
  53. package/dist/esm/Sorter.d.ts +0 -8
  54. package/dist/esm/Sorter.d.ts.map +0 -1
  55. package/dist/esm/Sorter.js +0 -28
  56. package/dist/esm/Style.d.ts +0 -63
  57. package/dist/esm/Style.d.ts.map +0 -1
  58. package/dist/esm/Style.js +0 -471
  59. package/dist/esm/Text.d.ts.map +0 -1
  60. package/dist/esm/Text.js +0 -35
  61. package/dist/esm/Theme.d.ts +0 -1413
  62. package/dist/esm/Theme.d.ts.map +0 -1
  63. package/dist/esm/Theme.js +0 -503
  64. package/dist/esm/Toaster.d.ts +0 -24
  65. package/dist/esm/Toaster.d.ts.map +0 -1
  66. package/dist/esm/Toaster.js +0 -46
  67. package/dist/esm/index.d.ts +0 -12
  68. package/dist/esm/index.d.ts.map +0 -1
  69. /package/dist/{cjs → types}/Arithmetic.d.ts +0 -0
  70. /package/dist/{cjs → types}/Arithmetic.d.ts.map +0 -0
  71. /package/dist/{cjs → types}/Arrayifier.d.ts +0 -0
  72. /package/dist/{cjs → types}/Arrayifier.d.ts.map +0 -0
  73. /package/dist/{cjs → types}/CSV.d.ts +0 -0
  74. /package/dist/{cjs → types}/CSV.d.ts.map +0 -0
  75. /package/dist/{cjs → types}/Color.d.ts +0 -0
  76. /package/dist/{cjs → types}/Color.d.ts.map +0 -0
  77. /package/dist/{cjs → types}/Dates.d.ts +0 -0
  78. /package/dist/{cjs → types}/Dates.d.ts.map +0 -0
  79. /package/dist/{cjs → types}/Kontororu.d.ts +0 -0
  80. /package/dist/{cjs → types}/Kontororu.d.ts.map +0 -0
  81. /package/dist/{cjs → types}/Objector.d.ts +0 -0
  82. /package/dist/{cjs → types}/Objector.d.ts.map +0 -0
  83. /package/dist/{cjs → types}/Sorter.d.ts +0 -0
  84. /package/dist/{cjs → types}/Sorter.d.ts.map +0 -0
  85. /package/dist/{cjs → types}/Style.d.ts +0 -0
  86. /package/dist/{cjs → types}/Style.d.ts.map +0 -0
  87. /package/dist/{cjs → types}/Theme.d.ts +0 -0
  88. /package/dist/{cjs → types}/Theme.d.ts.map +0 -0
  89. /package/dist/{cjs → types}/Toaster.d.ts +0 -0
  90. /package/dist/{cjs → types}/Toaster.d.ts.map +0 -0
package/dist/cjs/Color.js DELETED
@@ -1,348 +0,0 @@
1
- "use strict";
2
- /* eslint-disable no-mixed-operators */
3
- /* eslint-disable no-bitwise */
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.Color = void 0;
6
- class Color {
7
- // constructor() {
8
- // }
9
- /**
10
- * A linear interpolator for hexadecimal colors
11
- * @param {string} a
12
- * @param {string} b
13
- * @param {number} amount
14
- * @example
15
- * // returns #7F7F7F
16
- * lerpColor('#000000', '#ffffff', 0.5)
17
- * @return {string}
18
- */
19
- static lerpColor(a, b, amount) {
20
- const ah = +a.replace('#', '0x');
21
- const ar = ah >> 16;
22
- const ag = ah >> 8 & 0xff;
23
- const ab = ah & 0xff;
24
- const bh = +b.replace('#', '0x');
25
- const br = bh >> 16;
26
- const bg = bh >> 8 & 0xff;
27
- const bb = bh & 0xff;
28
- const rr = ar + amount * (br - ar);
29
- const rg = ag + amount * (bg - ag);
30
- const rb = ab + amount * (bb - ab);
31
- return `#${((1 << 24) + (rr << 16) + (rg << 8) + rb | 0).toString(16).slice(1)}`;
32
- }
33
- /**
34
- * Get a color that will look readable based on a background color
35
- */
36
- static getTextColor(color, backgroundColor, debug = false) {
37
- // Convert hex colors to RGB
38
- /*
39
- let [r1, g1, b1] = Color.hexToRgb(color);
40
- const [r2, g2, b2] = Color.hexToRgb(backgroundColor);
41
-
42
- const brightnessBg = Color.calculateBrightness(r2, g2, b2);
43
-
44
- const targetBrightness = brightnessBg > 128 ? 50 : 150; // target brightness threshold for contrast
45
-
46
- while (true) {
47
- const brightnessColor = Color.calculateBrightness(r1, g1, b1);
48
-
49
- if (
50
- (brightnessBg > 128 && brightnessColor < targetBrightness) ||
51
- (brightnessBg <= 128 && brightnessColor > targetBrightness)
52
- ) {
53
- break;
54
- }
55
-
56
- if (brightnessBg > 128) {
57
- r1 = Math.max(0, r1 - 25);
58
- g1 = Math.max(0, g1 - 25);
59
- b1 = Math.max(0, b1 - 25);
60
- } else {
61
- r1 = Math.min(255, r1 + 25);
62
- g1 = Math.min(255, g1 + 25);
63
- b1 = Math.min(255, b1 + 25);
64
- }
65
-
66
- if ((r1 === 0 && g1 === 0 && b1 === 0) || (r1 === 255 && g1 === 255 && b1 === 255)) {
67
- break; // avoid infinite loop
68
- }
69
- }
70
-
71
- // Convert back to hex
72
- return Color.rgbToHex(r1, g1, b1);
73
- */
74
- let [r, g, b] = Color.hexToRgb(color);
75
- const [br, bg, bb] = Color.hexToRgb(backgroundColor);
76
- const contrastTarget = 4.5;
77
- if (debug) {
78
- console.log('Color.getContrastRatio([r, g, b], [br, bg, bb])', Color.getContrastRatio([r, g, b], [br, bg, bb]));
79
- }
80
- if (Color.getContrastRatio([r, g, b], [br, bg, bb]) >= contrastTarget) {
81
- return Color.rgbToHex(r, g, b);
82
- }
83
- const contrastToBlack = Color.getContrastRatio([0, 0, 0], [br, bg, bb]);
84
- const contrastToWhite = Color.getContrastRatio([255, 255, 255], [br, bg, bb]);
85
- const direction = contrastToWhite > contrastToBlack ? 'lighter' : 'darker';
86
- if (debug) {
87
- console.log('contrastToBlack', contrastToBlack);
88
- console.log('contrastToWhite', contrastToWhite);
89
- console.log('direction', direction);
90
- }
91
- const adjust = (c, lighter) => {
92
- return lighter ? Math.min(255, c + 10) : Math.max(0, c - 10);
93
- };
94
- for (let i = 0; i < 25; i++) {
95
- r = adjust(r, direction === 'lighter');
96
- g = adjust(g, direction === 'lighter');
97
- b = adjust(b, direction === 'lighter');
98
- if (debug) {
99
- console.log('Color.getContrastRatio([r, g, b], [br, bg, bb]) 2', Color.getContrastRatio([r, g, b], [br, bg, bb]));
100
- }
101
- if (Color.getContrastRatio([r, g, b], [br, bg, bb]) >= contrastTarget) {
102
- break;
103
- }
104
- }
105
- return Color.rgbToHex(r, g, b);
106
- }
107
- static getContrastRatio(rgb1, rgb2) {
108
- const luminance = (r, g, b) => {
109
- const a = [r, g, b].map((v) => {
110
- v /= 255;
111
- return v <= 0.03928
112
- ? v / 12.92
113
- : Math.pow((v + 0.055) / 1.055, 2.4);
114
- });
115
- return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
116
- };
117
- const lum1 = luminance(...rgb1) + 0.05;
118
- const lum2 = luminance(...rgb2) + 0.05;
119
- return lum1 > lum2 ? lum1 / lum2 : lum2 / lum1;
120
- }
121
- /**
122
- * Take a hex (#fff) and an amount and darken the color, return a hex
123
- * @param {string} hex
124
- * @param {number} amount
125
- * @return {string} hex
126
- */
127
- // public static darken(hex: string, amount: number = 0.02): string {
128
- // const [r, g, b] = this.hexToRgb(hex);
129
- // const [h, s, l] = this.rgbToHsl(r, g, b);
130
- // const newL = Math.max(0, (l / 100) - amount) * 100;
131
- // const [r2, g2, b2] = this.hslToRgb(h, s, newL);
132
- // return this.rgbToHex(r2, g2, b2);
133
- // }
134
- /**
135
- * Take a hex (#fff) and an amount and lighten the color, return a hex
136
- * @param {string} hex
137
- * @param {number} amount
138
- * @return {string} hex
139
- */
140
- // public static lighten(hex: string, amount: number = 0.02): string {
141
- // const [r, g, b] = this.hexToRgb(hex);
142
- // const [h, s, l] = this.rgbToHsl(r, g, b);
143
- // const newL = Math.min(1, (l / 100) + amount) * 100;
144
- // const [r2, g2, b2] = this.hslToRgb(h, s, newL);
145
- // return this.rgbToHex(r2, g2, b2);
146
- // }
147
- /**
148
- * Mixes the color with black to create a Shade.
149
- * Prevents the "muddy/brown" look of HSL darkening.
150
- * @param {string} hex - The color to darken
151
- * @param {number} amount - 0 to 1 (e.g. 0.1 is 10% darker)
152
- * @return {string} hex
153
- */
154
- static darken(hex, amount = 0.1) {
155
- const [r, g, b] = this.hexToRgb(hex);
156
- // Calculate new color by mixing with black (0)
157
- // Formula: Current * (1 - amount)
158
- const remaining = 1 - amount;
159
- const r2 = Math.round(r * remaining);
160
- const g2 = Math.round(g * remaining);
161
- const b2 = Math.round(b * remaining);
162
- return this.rgbToHex(r2, g2, b2);
163
- }
164
- /**
165
- * Mixes the color with white to create a Tint.
166
- * cleaner and less "washed out" than HSL lightness adjustment.
167
- * @param {string} hex - The color to lighten
168
- * @param {number} amount - 0 to 1 (e.g. 0.1 is 10% lighter)
169
- * @return {string} hex
170
- */
171
- static lighten(hex, amount = 0.1) {
172
- const [r, g, b] = this.hexToRgb(hex);
173
- // Calculate new color by mixing with white (255)
174
- // Formula: Current + ((Target - Current) * amount)
175
- const r2 = Math.round(r + (255 - r) * amount);
176
- const g2 = Math.round(g + (255 - g) * amount);
177
- const b2 = Math.round(b + (255 - b) * amount);
178
- return this.rgbToHex(r2, g2, b2);
179
- }
180
- static shadeColor(hex, percent) {
181
- let [r, g, b] = Color.hexToRgb(hex);
182
- r = Math.min(255, Math.max(0, Math.round(r + (r * (percent / 100)))));
183
- g = Math.min(255, Math.max(0, Math.round(g + (g * (percent / 100)))));
184
- b = Math.min(255, Math.max(0, Math.round(b + (b * (percent / 100)))));
185
- return Color.rgbToHex(r, g, b);
186
- }
187
- /**
188
- * Are 2 colors similar to each other?
189
- * @param {string} color1
190
- * @param {string} color2
191
- * @param {number} threshold
192
- * @return {boolean}
193
- */
194
- static areColorsSimilar(color1, color2, threshold = 50) {
195
- const distance = Color.colorDistance(color1, color2);
196
- return distance < threshold;
197
- }
198
- /**
199
- * Inverts a hex color
200
- * @param {string} hex
201
- * @return {string} hex
202
- */
203
- static invertColor(hex) {
204
- const [r, g, b] = Color.hexToRgb(hex);
205
- // Invert each color component
206
- const invertedR = 255 - r;
207
- const invertedG = 255 - g;
208
- const invertedB = 255 - b;
209
- return Color.rgbToHex(invertedR, invertedG, invertedB);
210
- }
211
- /**
212
- * Takes a hex (#fff) and returns an rgba with the provided alpha
213
- * @param {string} hex
214
- * @param {number} alpha
215
- * @return {string} rgba()
216
- */
217
- static alphaColor(hex, alpha) {
218
- const [r, g, b] = this.hexToRgb(hex);
219
- return `rgba(${r}, ${g}, ${b}, ${alpha})`;
220
- }
221
- /**
222
- * Gets analogous colors
223
- * @param {string} hex
224
- * @return {Array<string>}
225
- */
226
- static getAnalogousColors(hex) {
227
- const [r, g, b] = Color.hexToRgb(hex);
228
- const [h, s, l] = Color.rgbToHsl(r, g, b);
229
- const analogousColors = [];
230
- const offset = 30; // Offset for analogous colors, usually 30 degrees
231
- for (let i = -1; i <= 1; i++) {
232
- if (i !== 0) {
233
- const newHue = (h + i * offset + 360) % 360;
234
- const [newR, newG, newB] = Color.hslToRgb(newHue, s, l);
235
- analogousColors.push(Color.rgbToHex(newR, newG, newB));
236
- }
237
- }
238
- return analogousColors;
239
- }
240
- /**
241
- * Convert hex to rgb
242
- * @param {string} hex
243
- * @return {Array} rgb
244
- */
245
- static hexToRgb(hex) {
246
- // Remove the hash at the start if it's there
247
- let h = hex.replace(/^#/, '');
248
- // Handle 3-character hex codes by expanding them to 6-character
249
- if (h.length === 3) {
250
- h = h.split('').map((char) => { return char + char; }).join('');
251
- }
252
- // Ensure it's a valid 6-character hex code
253
- if (h.length !== 6) {
254
- throw new Error(`Invalid hex color format: ${hex}`);
255
- }
256
- // Parse r, g, b values
257
- const bigint = parseInt(h, 16);
258
- const r = (bigint >> 16) & 255;
259
- const g = (bigint >> 8) & 255;
260
- const b = bigint & 255;
261
- return [r, g, b];
262
- }
263
- /**
264
- * Convert rgb to hex
265
- * @param {number} r
266
- * @param {number} g
267
- * @param {number} b
268
- * @return {string}
269
- */
270
- static rgbToHex(r, g, b) {
271
- return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
272
- }
273
- static rgbToHsl(r, g, b) {
274
- r /= 255;
275
- g /= 255;
276
- b /= 255;
277
- const max = Math.max(r, g, b);
278
- const min = Math.min(r, g, b);
279
- let h = 0;
280
- let s = 0;
281
- const l = (max + min) / 2;
282
- if (max === min) {
283
- h = s = 0; // achromatic
284
- }
285
- else {
286
- const d = max - min;
287
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
288
- switch (max) {
289
- case r:
290
- h = (g - b) / d + (g < b ? 6 : 0);
291
- break;
292
- case g:
293
- h = (b - r) / d + 2;
294
- break;
295
- case b:
296
- h = (r - g) / d + 4;
297
- break;
298
- }
299
- h /= 6;
300
- }
301
- return [h * 360, s * 100, l * 100];
302
- }
303
- static hslToRgb(h, s, l) {
304
- let r;
305
- let g;
306
- let b;
307
- h /= 360;
308
- s /= 100;
309
- l /= 100;
310
- if (s === 0) {
311
- r = g = b = l; // achromatic
312
- }
313
- else {
314
- const hue2rgb = (p, q, t) => {
315
- if (t < 0)
316
- t += 1;
317
- if (t > 1)
318
- t -= 1;
319
- if (t < 1 / 6)
320
- return p + (q - p) * 6 * t;
321
- if (t < 1 / 3)
322
- return q;
323
- if (t < 1 / 2)
324
- return p + (q - p) * (2 / 3 - t) * 6;
325
- return p;
326
- };
327
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
328
- const p = 2 * l - q;
329
- r = hue2rgb(p, q, h + 1 / 3);
330
- g = hue2rgb(p, q, h);
331
- b = hue2rgb(p, q, h - 1 / 3);
332
- }
333
- return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
334
- }
335
- static calculateBrightness(r, g, b) {
336
- // Calculate the brightness of the color
337
- return (r * 299 + g * 587 + b * 114) / 1000;
338
- }
339
- static colorDistance(color1, color2) {
340
- const [r1, g1, b1] = Color.hexToRgb(color1);
341
- const [r2, g2, b2] = Color.hexToRgb(color2);
342
- const dr = r1 - r2;
343
- const dg = g1 - g2;
344
- const db = b1 - b2;
345
- return Math.sqrt(dr * dr + dg * dg + db * db);
346
- }
347
- }
348
- exports.Color = Color;
package/dist/cjs/Dates.js DELETED
@@ -1,358 +0,0 @@
1
- "use strict";
2
- /* eslint-disable one-var-declaration-per-line */
3
- /* eslint-disable one-var */
4
- Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.Dates = void 0;
6
- class Dates {
7
- // constructor() {
8
- // }
9
- /*
10
- * Robust Date Parsing
11
- * Handles:
12
- * - Date Objects / Numbers (Timestamps)
13
- * - ISO Strings (2025-01-01) -> Forces Local Midnight
14
- * - US Formats (01/05/2026)
15
- * - Mixed Time Formats (23:00 pm, 5:00pm, 14:30:00)
16
- */
17
- static parse(str, utc = false) {
18
- // 1. Handle Null / Undefined -> Return Now
19
- if (!str) {
20
- return new Date();
21
- }
22
- // 2. Handle Existing Date Objects -> Return Copy
23
- if (str instanceof Date) {
24
- return new Date(str.getTime());
25
- }
26
- // 3. Handle Timestamps (Numbers)
27
- if (typeof str === 'number') {
28
- return new Date(str);
29
- }
30
- // 4. Handle Strings
31
- if (typeof str === 'string') {
32
- const input = str.trim();
33
- // CASE A: Strict ISO Date "YYYY-MM-DD"
34
- // Native JS parses this as UTC Midnight, which often shows as
35
- // previous day 7pm EST. We force "T00:00:00" to make it Local Midnight.
36
- if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
37
- return new Date(`${input}T00:00:00`);
38
- }
39
- // CASE B: Manual Parsing for Complex Strings
40
- // This handles "2026-01-05 23:03:19 pm", "01/05/2026", etc.
41
- // Step 1: Extract Date Part (YYYY-MM-DD or MM/DD/YYYY)
42
- // Regex looks for: (Group 1: Year/Month) -or/ (Group 2: Month/Day) -or/ (Group 3: Day/Year)
43
- let year, month, day, timePart = '';
44
- // Match YYYY-MM-DD or YYYY/MM/DD
45
- const isoMatch = input.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(.*)$/);
46
- // Match MM/DD/YYYY or MM-DD-YYYY
47
- const usMatch = input.match(/^(\d{1,2})[-/](\d{1,2})[-/](\d{4})(.*)$/);
48
- if (isoMatch) {
49
- year = parseInt(isoMatch[1], 10);
50
- month = parseInt(isoMatch[2], 10) - 1; // JS Months are 0-11
51
- day = parseInt(isoMatch[3], 10);
52
- timePart = isoMatch[4];
53
- }
54
- else if (usMatch) {
55
- year = parseInt(usMatch[3], 10);
56
- month = parseInt(usMatch[1], 10) - 1;
57
- day = parseInt(usMatch[2], 10);
58
- timePart = usMatch[4];
59
- }
60
- else {
61
- // Fallback: Let the browser try its best if our regex fails
62
- const d = new Date(input);
63
- return isNaN(d.getTime()) ? new Date() : d;
64
- }
65
- // Step 2: Extract Time Part
66
- let hours = 0;
67
- let minutes = 0;
68
- let seconds = 0;
69
- // Look for HH:MM(:SS) and optional AM/PM in the remaining string
70
- if (timePart && timePart.trim().length > 0) {
71
- // Matches: 23:03, 23:03:19, 5:00pm, 5:00 pm
72
- const timeMatch = timePart.match(/(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(am|pm|AM|PM)?/);
73
- if (timeMatch) {
74
- hours = parseInt(timeMatch[1], 10);
75
- minutes = parseInt(timeMatch[2], 10);
76
- seconds = timeMatch[3] ? parseInt(timeMatch[3], 10) : 0;
77
- const meridiem = timeMatch[4] ? timeMatch[4].toLowerCase() : null;
78
- // Step 3: Normalize Hours (12h to 24h)
79
- if (meridiem === 'pm' && hours < 12) {
80
- hours += 12;
81
- }
82
- if (meridiem === 'am' && hours === 12) {
83
- hours = 0;
84
- }
85
- // Note: If input is "23:00 pm", we ignore the 'pm' because 23 > 12.
86
- }
87
- }
88
- if (utc) {
89
- return new Date(Date.UTC(year, month, day, hours, minutes, seconds));
90
- }
91
- // Step 4: Construct Date in Local Time
92
- return new Date(year, month, day, hours, minutes, seconds);
93
- }
94
- return new Date();
95
- }
96
- static utc(date) {
97
- const d = this.parse(date);
98
- return new Date(d.getTime() + (d.getTimezoneOffset() * 60000));
99
- }
100
- static getMonthsShort() {
101
- return ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
102
- }
103
- static getMonths() {
104
- return ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
105
- }
106
- static getDaysShort() {
107
- return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
108
- }
109
- static getDays() {
110
- return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
111
- }
112
- /**
113
- * Format a date using php syntax
114
- *
115
- | Token | Meaning | Example |
116
- | ----- | ------------------------- | ------- |
117
- | `Y` | 4-digit year | 2025 |
118
- | `y` | 2-digit year | 25 |
119
- | `m` | 2-digit month | 03 |
120
- | `n` | month (no leading zero) | 3 |
121
- | `d` | day (2-digit) | 09 |
122
- | `j` | day (no leading zero) | 9 |
123
- | `S` | Ordinal suffix | th |
124
- | `H` | 24-hour | 14 |
125
- | `G` | 24-hour (no leading zero) | 14 |
126
- | `h` | 12-hour | 02 |
127
- | `g` | 12-hour (no leading zero) | 2 |
128
- | `i` | minutes | 05 |
129
- | `s` | seconds | 09 |
130
- | `A` | AM/PM | PM |
131
- | `a` | am/pm | pm |
132
- | `w` | day of week (0–6) | 1 |
133
- | `N` | day of week (1–7) | 2 |
134
- | `M` | short month name | Mar |
135
- | `F` | full month name | March |
136
- | `D` | short weekday | Mon |
137
- | `l` | full weekday | Monday |
138
- */
139
- static format(dateInput, format) {
140
- const date = this.parse(dateInput);
141
- const pad = (n) => String(n).padStart(2, '0');
142
- const monthsShort = this.getMonthsShort();
143
- const monthsLong = this.getMonths();
144
- const daysShort = this.getDaysShort();
145
- const daysLong = this.getDays();
146
- /*
147
- const Y = date.getUTCFullYear();
148
- const y = String(Y).slice(-2);
149
- const month = date.getUTCMonth(); // 0-11
150
- const dateNum = date.getUTCDate(); // 1-31
151
- const day = date.getUTCDay(); // 0-6, Sun = 0
152
- const hours = date.getUTCHours(); // 0-23
153
- const minutes = date.getUTCMinutes();
154
- const seconds = date.getUTCSeconds();
155
- */
156
- const Y = date.getFullYear();
157
- const y = String(Y).slice(-2);
158
- const month = date.getMonth(); // 0-11
159
- const dateNum = date.getDate(); // 1-31
160
- const day = date.getDay(); // 0-6, Sun = 0
161
- const hours = date.getHours(); // 0-23
162
- const minutes = date.getMinutes();
163
- const seconds = date.getSeconds();
164
- // Logic for ordinal suffix (st, nd, rd, th)
165
- const getOrdinalSuffix = (n) => {
166
- const v = n % 100;
167
- // 11th, 12th, 13th are exceptions to the 1st, 2nd, 3rd rule
168
- if (v >= 11 && v <= 13) {
169
- return 'th';
170
- }
171
- switch (n % 10) {
172
- case 1: {
173
- return 'st';
174
- }
175
- case 2: {
176
- return 'nd';
177
- }
178
- case 3: {
179
- return 'rd';
180
- }
181
- default: {
182
- return 'th';
183
- }
184
- }
185
- };
186
- const tokens = {
187
- /// Year
188
- Y: String(Y),
189
- y,
190
- // Month
191
- m: pad(month + 1),
192
- n: String(month + 1),
193
- M: monthsShort[month],
194
- F: monthsLong[month],
195
- // Day
196
- d: pad(dateNum),
197
- j: String(dateNum),
198
- D: daysShort[day],
199
- l: daysLong[day],
200
- w: String(day), // 0 (Sun) - 6
201
- N: String(day === 0 ? 7 : day), // 1 (Mon) - 7 (Sun)
202
- S: getOrdinalSuffix(dateNum),
203
- // Time
204
- H: pad(hours),
205
- G: String(hours),
206
- h: pad(((hours + 11) % 12) + 1),
207
- g: String(((hours + 11) % 12) + 1),
208
- i: pad(minutes),
209
- s: pad(seconds),
210
- // AM/PM
211
- A: hours < 12 ? 'AM' : 'PM',
212
- a: hours < 12 ? 'am' : 'pm',
213
- };
214
- // Replace tokens using regex
215
- return format.replace(/\\(.)|([a-zA-Z])/g, (_, esc, token) => {
216
- if (esc) {
217
- // literal escaped char like \H or \Y → return the raw letter
218
- return esc;
219
- }
220
- return tokens[token] ?? token;
221
- });
222
- }
223
- static add(date, amount, unit) {
224
- const d = this.parse(date);
225
- if (unit === 'years') {
226
- const originalDay = d.getDate();
227
- d.setFullYear(d.getFullYear() + amount);
228
- // Handle Leap Year Rollover:
229
- // Feb 29, 2024 + 1 year -> Mar 1, 2025 (Standard JS behavior)
230
- // If strict "same day or last day of month" logic is desired (turning it into Feb 28):
231
- if (d.getDate() !== originalDay) {
232
- d.setDate(0); // Set to last day of previous month (Feb 28)
233
- }
234
- }
235
- else if (unit === 'months') {
236
- const originalDay = d.getDate();
237
- d.setMonth(d.getMonth() + amount);
238
- // Handle rollover: Jan 31 + 1 month -> Feb 28/29
239
- if (d.getDate() !== originalDay) {
240
- d.setDate(0); // Set to last day of previous month
241
- }
242
- }
243
- else if (unit === 'days') {
244
- // Use setDate to be DST safe (24h addition via ms is unsafe across DST)
245
- d.setDate(d.getDate() + amount);
246
- }
247
- else {
248
- const map = {
249
- hours: amount * 60 * 60 * 1000,
250
- minutes: amount * 60 * 1000,
251
- };
252
- // Use getTime() for day/hour/minute units for simple millisecond addition
253
- d.setTime(d.getTime() + map[unit]);
254
- }
255
- return d;
256
- }
257
- static subtract(date, amount, unit) {
258
- return this.add(date, -amount, unit);
259
- }
260
- static fromNow(date) {
261
- const d = this.parse(date);
262
- const diff = Date.now() - d.getTime();
263
- const mins = Math.floor(diff / 60000);
264
- if (Math.abs(mins) < 1) {
265
- return 'just now';
266
- }
267
- // Handle future dates roughly
268
- if (mins < 0) {
269
- return 'in the future';
270
- }
271
- if (mins < 60) {
272
- return `${mins}m ago`;
273
- }
274
- const hours = Math.floor(mins / 60);
275
- if (hours < 24) {
276
- return `${hours}h ago`;
277
- }
278
- const days = Math.floor(hours / 24);
279
- return `${days}d ago`;
280
- }
281
- /**
282
- * Find the closest date in an array of dates
283
- */
284
- static getClosestDate(dateToMatch, datesArray) {
285
- if (!datesArray.length) {
286
- return null;
287
- }
288
- const matchDate = this.parse(dateToMatch).getTime();
289
- let closestDate = null;
290
- let closestDist = Infinity;
291
- // eslint-disable-next-line no-restricted-syntax
292
- for (const dateStr of datesArray) {
293
- const currDate = this.parse(dateStr).getTime();
294
- const dist = Math.abs(currDate - matchDate);
295
- if (dist < closestDist) {
296
- closestDist = dist;
297
- closestDate = dateStr;
298
- }
299
- else if (dist === closestDist) {
300
- // Tie-breaker: Prefer the date that is in the future relative to the matchDate
301
- // Or if both are same direction, just keep the current one (or implementation defined)
302
- // Requirement: "Both 17th and 19th have same dist. It should pick 19th"
303
- if (currDate > matchDate) {
304
- closestDate = dateStr;
305
- }
306
- }
307
- }
308
- return closestDate;
309
- }
310
- static getTodayEST() {
311
- return this.format(new Date().toLocaleString('en-US', { timeZone: 'America/New_York' }), 'Y-m-d');
312
- }
313
- static getStartOfDay(date) {
314
- const d = this.parse(date);
315
- d.setHours(0, 0, 0, 0);
316
- return d;
317
- }
318
- static getStartOfMonth(date) {
319
- const d = this.parse(date);
320
- d.setDate(1);
321
- d.setHours(0, 0, 0, 0);
322
- return d;
323
- }
324
- static getStartOfGrid(date) {
325
- const d = this.getStartOfMonth(date);
326
- const dayOfWeek = d.getDay(); // 0 (Sunday) is the start in standard JS
327
- // Move back to the beginning of the week
328
- const result = this.parse(d);
329
- // Subtract days to get to the start of the week (Sunday)
330
- result.setDate(d.getDate() - dayOfWeek);
331
- return result;
332
- }
333
- static isSameDay(date1, date2) {
334
- if (!date1 || !date2) {
335
- return false;
336
- }
337
- const d1 = this.parse(date1);
338
- const d2 = this.parse(date2);
339
- return (d1.getFullYear() === d2.getFullYear() &&
340
- d1.getMonth() === d2.getMonth() &&
341
- d1.getDate() === d2.getDate());
342
- }
343
- // Helper to check if one date is before another (ignoring time)
344
- static isBeforeDay(date1, date2) {
345
- if (!date1 || !date2) {
346
- return false;
347
- }
348
- return this.getStartOfDay(date1).getTime() < this.getStartOfDay(date2).getTime();
349
- }
350
- // Helper to check if one date is after another (ignoring time)
351
- static isAfterDay(date1, date2) {
352
- if (!date1 || !date2) {
353
- return false;
354
- }
355
- return this.getStartOfDay(date1).getTime() > this.getStartOfDay(date2).getTime();
356
- }
357
- }
358
- exports.Dates = Dates;