@simplysm/core-common 13.0.69 → 13.0.71
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/README.md +66 -267
- package/dist/common.types.d.ts +14 -14
- package/dist/errors/argument-error.d.ts +10 -10
- package/dist/errors/argument-error.d.ts.map +1 -1
- package/dist/errors/argument-error.js +2 -2
- package/dist/errors/argument-error.js.map +1 -1
- package/dist/errors/not-implemented-error.d.ts +8 -8
- package/dist/errors/not-implemented-error.js +2 -2
- package/dist/errors/not-implemented-error.js.map +1 -1
- package/dist/errors/sd-error.d.ts +10 -10
- package/dist/errors/sd-error.d.ts.map +1 -1
- package/dist/errors/timeout-error.d.ts +10 -10
- package/dist/errors/timeout-error.js +3 -3
- package/dist/errors/timeout-error.js.map +1 -1
- package/dist/extensions/arr-ext.d.ts +2 -2
- package/dist/extensions/arr-ext.helpers.d.ts +8 -8
- package/dist/extensions/arr-ext.helpers.js +1 -1
- package/dist/extensions/arr-ext.helpers.js.map +1 -1
- package/dist/extensions/arr-ext.js +13 -13
- package/dist/extensions/arr-ext.js.map +1 -1
- package/dist/extensions/arr-ext.types.d.ts +57 -57
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/map-ext.d.ts +16 -16
- package/dist/extensions/set-ext.d.ts +11 -11
- package/dist/features/debounce-queue.d.ts +17 -15
- package/dist/features/debounce-queue.d.ts.map +1 -1
- package/dist/features/debounce-queue.js +6 -6
- package/dist/features/debounce-queue.js.map +1 -1
- package/dist/features/event-emitter.d.ts +20 -20
- package/dist/features/event-emitter.js +17 -17
- package/dist/features/serial-queue.d.ts +11 -11
- package/dist/features/serial-queue.js +5 -5
- package/dist/features/serial-queue.js.map +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/types/date-only.d.ts +64 -64
- package/dist/types/date-only.d.ts.map +1 -1
- package/dist/types/date-only.js +63 -63
- package/dist/types/date-time.d.ts +37 -37
- package/dist/types/date-time.d.ts.map +1 -1
- package/dist/types/date-time.js +54 -37
- package/dist/types/date-time.js.map +1 -1
- package/dist/types/lazy-gc-map.d.ts +26 -26
- package/dist/types/lazy-gc-map.d.ts.map +1 -1
- package/dist/types/lazy-gc-map.js +26 -26
- package/dist/types/lazy-gc-map.js.map +1 -1
- package/dist/types/time.d.ts +25 -25
- package/dist/types/time.d.ts.map +1 -1
- package/dist/types/time.js +25 -25
- package/dist/types/time.js.map +1 -1
- package/dist/types/uuid.d.ts +11 -11
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +12 -12
- package/dist/types/uuid.js.map +1 -1
- package/dist/utils/bytes.d.ts +17 -17
- package/dist/utils/bytes.js +4 -4
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/date-format.d.ts +45 -45
- package/dist/utils/date-format.js +1 -1
- package/dist/utils/date-format.js.map +1 -1
- package/dist/utils/error.d.ts +4 -4
- package/dist/utils/json.d.ts +17 -17
- package/dist/utils/json.js +3 -3
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/num.d.ts +23 -23
- package/dist/utils/obj.d.ts +111 -111
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +3 -3
- package/dist/utils/obj.js.map +1 -1
- package/dist/utils/path.d.ts +10 -10
- package/dist/utils/primitive.d.ts +5 -5
- package/dist/utils/primitive.js +1 -1
- package/dist/utils/primitive.js.map +1 -1
- package/dist/utils/str.d.ts +46 -46
- package/dist/utils/str.d.ts.map +1 -1
- package/dist/utils/str.js +5 -5
- package/dist/utils/str.js.map +1 -1
- package/dist/utils/template-strings.d.ts +26 -26
- package/dist/utils/transferable.d.ts +18 -18
- package/dist/utils/transferable.js +1 -1
- package/dist/utils/transferable.js.map +1 -1
- package/dist/utils/wait.d.ts +9 -9
- package/dist/utils/xml.d.ts +13 -13
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +1 -0
- package/dist/utils/xml.js.map +1 -1
- package/dist/zip/sd-zip.d.ts +22 -22
- package/dist/zip/sd-zip.js +16 -16
- package/package.json +4 -4
- package/src/common.types.ts +17 -17
- package/src/errors/argument-error.ts +15 -15
- package/src/errors/not-implemented-error.ts +9 -9
- package/src/errors/sd-error.ts +12 -12
- package/src/errors/timeout-error.ts +12 -12
- package/src/extensions/arr-ext.helpers.ts +10 -10
- package/src/extensions/arr-ext.ts +57 -57
- package/src/extensions/arr-ext.types.ts +59 -59
- package/src/extensions/map-ext.ts +16 -16
- package/src/extensions/set-ext.ts +11 -11
- package/src/features/debounce-queue.ts +21 -19
- package/src/features/event-emitter.ts +25 -25
- package/src/features/serial-queue.ts +13 -13
- package/src/globals.ts +4 -4
- package/src/index.ts +1 -1
- package/src/types/date-only.ts +83 -83
- package/src/types/date-time.ts +64 -44
- package/src/types/lazy-gc-map.ts +45 -45
- package/src/types/time.ts +34 -34
- package/src/types/uuid.ts +17 -17
- package/src/utils/bytes.ts +35 -35
- package/src/utils/date-format.ts +65 -65
- package/src/utils/error.ts +4 -4
- package/src/utils/json.ts +39 -39
- package/src/utils/num.ts +23 -23
- package/src/utils/obj.ts +138 -138
- package/src/utils/path.ts +10 -10
- package/src/utils/primitive.ts +6 -6
- package/src/utils/str.ts +260 -261
- package/src/utils/template-strings.ts +29 -29
- package/src/utils/transferable.ts +284 -284
- package/src/utils/wait.ts +10 -10
- package/src/utils/xml.ts +20 -19
- package/src/zip/sd-zip.ts +25 -25
- package/tests/errors/errors.spec.ts +80 -0
- package/tests/extensions/array-extension.spec.ts +796 -0
- package/tests/extensions/map-extension.spec.ts +147 -0
- package/tests/extensions/set-extension.spec.ts +74 -0
- package/tests/types/date-only.spec.ts +638 -0
- package/tests/types/date-time.spec.ts +391 -0
- package/tests/types/lazy-gc-map.spec.ts +692 -0
- package/tests/types/time.spec.ts +559 -0
- package/tests/types/uuid.spec.ts +74 -0
- package/tests/utils/bytes-utils.spec.ts +230 -0
- package/tests/utils/date-format.spec.ts +373 -0
- package/tests/utils/debounce-queue.spec.ts +272 -0
- package/tests/utils/json.spec.ts +486 -0
- package/tests/utils/number.spec.ts +157 -0
- package/tests/utils/object.spec.ts +829 -0
- package/tests/utils/path.spec.ts +78 -0
- package/tests/utils/primitive.spec.ts +43 -0
- package/tests/utils/sd-event-emitter.spec.ts +216 -0
- package/tests/utils/serial-queue.spec.ts +365 -0
- package/tests/utils/string.spec.ts +281 -0
- package/tests/utils/template-strings.spec.ts +57 -0
- package/tests/utils/transferable.spec.ts +703 -0
- package/tests/utils/wait.spec.ts +145 -0
- package/tests/utils/xml.spec.ts +146 -0
- package/tests/zip/sd-zip.spec.ts +238 -0
- package/docs/extensions.md +0 -503
- package/docs/features.md +0 -109
- package/docs/types.md +0 -486
- package/docs/utils.md +0 -780
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
ArgumentError,
|
|
4
|
+
bytesConcat as concat,
|
|
5
|
+
bytesToHex as toHex,
|
|
6
|
+
bytesFromHex as fromHex,
|
|
7
|
+
bytesToBase64 as toBase64,
|
|
8
|
+
bytesFromBase64 as fromBase64,
|
|
9
|
+
} from "@simplysm/core-common";
|
|
10
|
+
|
|
11
|
+
describe("BytesUtils", () => {
|
|
12
|
+
//#region concat
|
|
13
|
+
|
|
14
|
+
describe("concat()", () => {
|
|
15
|
+
it("Concatenates multiple Uint8Arrays", () => {
|
|
16
|
+
const arr1 = new Uint8Array([1, 2, 3]);
|
|
17
|
+
const arr2 = new Uint8Array([4, 5]);
|
|
18
|
+
const arr3 = new Uint8Array([6, 7, 8, 9]);
|
|
19
|
+
|
|
20
|
+
const result = concat([arr1, arr2, arr3]);
|
|
21
|
+
|
|
22
|
+
expect(result).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("Handles empty array", () => {
|
|
26
|
+
const result = concat([]);
|
|
27
|
+
|
|
28
|
+
expect(result).toEqual(new Uint8Array([]));
|
|
29
|
+
expect(result.length).toBe(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("Handles single array", () => {
|
|
33
|
+
const arr = new Uint8Array([1, 2, 3]);
|
|
34
|
+
|
|
35
|
+
const result = concat([arr]);
|
|
36
|
+
|
|
37
|
+
expect(result).toEqual(new Uint8Array([1, 2, 3]));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("Handles empty Uint8Array in array", () => {
|
|
41
|
+
const arr1 = new Uint8Array([1, 2]);
|
|
42
|
+
const arr2 = new Uint8Array([]);
|
|
43
|
+
const arr3 = new Uint8Array([3, 4]);
|
|
44
|
+
|
|
45
|
+
const result = concat([arr1, arr2, arr3]);
|
|
46
|
+
|
|
47
|
+
expect(result).toEqual(new Uint8Array([1, 2, 3, 4]));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
|
|
53
|
+
//#region toHex/fromHex
|
|
54
|
+
|
|
55
|
+
describe("toHex()", () => {
|
|
56
|
+
it("Converts Uint8Array to hex string", () => {
|
|
57
|
+
const bytes = new Uint8Array([0, 1, 15, 16, 255]);
|
|
58
|
+
|
|
59
|
+
const result = toHex(bytes);
|
|
60
|
+
|
|
61
|
+
expect(result).toBe("00010f10ff");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("Handles empty array", () => {
|
|
65
|
+
const result = toHex(new Uint8Array([]));
|
|
66
|
+
|
|
67
|
+
expect(result).toBe("");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("Handles single byte", () => {
|
|
71
|
+
expect(toHex(new Uint8Array([0]))).toBe("00");
|
|
72
|
+
expect(toHex(new Uint8Array([255]))).toBe("ff");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("fromHex()", () => {
|
|
77
|
+
it("Converts hex string to Uint8Array", () => {
|
|
78
|
+
const result = fromHex("00010f10ff");
|
|
79
|
+
|
|
80
|
+
expect(result).toEqual(new Uint8Array([0, 1, 15, 16, 255]));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("Handles empty string", () => {
|
|
84
|
+
const result = fromHex("");
|
|
85
|
+
|
|
86
|
+
expect(result).toEqual(new Uint8Array([]));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("Handles uppercase hex", () => {
|
|
90
|
+
const result = fromHex("FF0A");
|
|
91
|
+
|
|
92
|
+
expect(result).toEqual(new Uint8Array([255, 10]));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("Throws error for odd-length string", () => {
|
|
96
|
+
expect(() => fromHex("abc")).toThrow(ArgumentError);
|
|
97
|
+
expect(() => fromHex("a")).toThrow(ArgumentError);
|
|
98
|
+
expect(() => fromHex("12345")).toThrow(ArgumentError);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("Throws error for invalid hex characters", () => {
|
|
102
|
+
expect(() => fromHex("zz")).toThrow(ArgumentError);
|
|
103
|
+
expect(() => fromHex("gh")).toThrow(ArgumentError);
|
|
104
|
+
expect(() => fromHex("12g4")).toThrow(ArgumentError);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("toHex/fromHex round-trip conversion", () => {
|
|
109
|
+
it("Round-trip conversion matches", () => {
|
|
110
|
+
const original = new Uint8Array([0, 127, 128, 255, 1, 2, 3]);
|
|
111
|
+
|
|
112
|
+
const hex = toHex(original);
|
|
113
|
+
const restored = fromHex(hex);
|
|
114
|
+
|
|
115
|
+
expect(restored).toEqual(original);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("Round-trip conversion matches all byte values (0-255)", () => {
|
|
119
|
+
const original = new Uint8Array(256);
|
|
120
|
+
for (let i = 0; i < 256; i++) {
|
|
121
|
+
original[i] = i;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const hex = toHex(original);
|
|
125
|
+
const restored = fromHex(hex);
|
|
126
|
+
|
|
127
|
+
expect(restored).toEqual(original);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
//#endregion
|
|
132
|
+
|
|
133
|
+
//#region toBase64/fromBase64
|
|
134
|
+
|
|
135
|
+
describe("toBase64()", () => {
|
|
136
|
+
it("Handles empty array", () => {
|
|
137
|
+
expect(toBase64(new Uint8Array([]))).toBe("");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("Converts general data", () => {
|
|
141
|
+
expect(toBase64(new Uint8Array([72, 101, 108, 108, 111]))).toBe("SGVsbG8=");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("Handles large data (1MB) without stack overflow", () => {
|
|
145
|
+
const data = new Uint8Array(1024 * 1024);
|
|
146
|
+
expect(() => toBase64(data)).not.toThrow();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("Handles case with no padding needed", () => {
|
|
150
|
+
// Multiple of 3 length - no padding
|
|
151
|
+
expect(toBase64(new Uint8Array([1, 2, 3]))).toBe("AQID");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("Handles case with single padding needed", () => {
|
|
155
|
+
// Remainder 2 when divided by 3 - 1 padding
|
|
156
|
+
expect(toBase64(new Uint8Array([1, 2]))).toBe("AQI=");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("Handles case with double padding needed", () => {
|
|
160
|
+
// Remainder 1 when divided by 3 - 2 padding
|
|
161
|
+
expect(toBase64(new Uint8Array([1]))).toBe("AQ==");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("fromBase64()", () => {
|
|
166
|
+
it("Handles empty string", () => {
|
|
167
|
+
expect(fromBase64("")).toEqual(new Uint8Array([]));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("Converts general data", () => {
|
|
171
|
+
expect(fromBase64("SGVsbG8=")).toEqual(new Uint8Array([72, 101, 108, 108, 111]));
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("Throws error for invalid base64 characters", () => {
|
|
175
|
+
expect(() => fromBase64("!!invalid!!")).toThrow(ArgumentError);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("Throws error for invalid base64 length (remainder 1)", () => {
|
|
179
|
+
expect(() => fromBase64("A")).toThrow(ArgumentError);
|
|
180
|
+
expect(() => fromBase64("AAAAA")).toThrow(ArgumentError);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("Handles base64 without padding", () => {
|
|
184
|
+
expect(fromBase64("AQID")).toEqual(new Uint8Array([1, 2, 3]));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("Handles base64 with whitespace", () => {
|
|
188
|
+
expect(fromBase64("SGVs bG8=")).toEqual(new Uint8Array([72, 101, 108, 108, 111]));
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe("toBase64/fromBase64 round-trip conversion", () => {
|
|
193
|
+
it("Round-trip conversion matches", () => {
|
|
194
|
+
const original = new Uint8Array([0, 127, 128, 255, 1, 2, 3]);
|
|
195
|
+
|
|
196
|
+
const base64 = toBase64(original);
|
|
197
|
+
const restored = fromBase64(base64);
|
|
198
|
+
|
|
199
|
+
expect(restored).toEqual(original);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("Round-trip conversion matches all byte values (0-255)", () => {
|
|
203
|
+
const original = new Uint8Array(256);
|
|
204
|
+
for (let i = 0; i < 256; i++) {
|
|
205
|
+
original[i] = i;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const base64 = toBase64(original);
|
|
209
|
+
const restored = fromBase64(base64);
|
|
210
|
+
|
|
211
|
+
expect(restored).toEqual(original);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("Round-trip conversion matches various lengths (1-10 bytes)", () => {
|
|
215
|
+
for (let len = 1; len <= 10; len++) {
|
|
216
|
+
const original = new Uint8Array(len);
|
|
217
|
+
for (let i = 0; i < len; i++) {
|
|
218
|
+
original[i] = (i * 37 + 13) % 256;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const base64 = toBase64(original);
|
|
222
|
+
const restored = fromBase64(base64);
|
|
223
|
+
|
|
224
|
+
expect(restored).toEqual(original);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
//#endregion
|
|
230
|
+
});
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { formatDate, normalizeMonth } from "@simplysm/core-common";
|
|
3
|
+
|
|
4
|
+
describe("formatDateTime", () => {
|
|
5
|
+
//#region Year pattern
|
|
6
|
+
|
|
7
|
+
describe("Year pattern", () => {
|
|
8
|
+
it("outputs 4-digit year with yyyy format", () => {
|
|
9
|
+
expect(formatDate("yyyy", { year: 2025 })).toBe("2025");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("outputs 2-digit year with yy format", () => {
|
|
13
|
+
expect(formatDate("yy", { year: 2025 })).toBe("25");
|
|
14
|
+
expect(formatDate("yy", { year: 2000 })).toBe("00");
|
|
15
|
+
expect(formatDate("yy", { year: 1999 })).toBe("99");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
|
|
21
|
+
//#region Month pattern
|
|
22
|
+
|
|
23
|
+
describe("Month pattern", () => {
|
|
24
|
+
it("outputs 2-digit month with MM format", () => {
|
|
25
|
+
expect(formatDate("MM", { month: 1 })).toBe("01");
|
|
26
|
+
expect(formatDate("MM", { month: 9 })).toBe("09");
|
|
27
|
+
expect(formatDate("MM", { month: 12 })).toBe("12");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("outputs month without padding with M format", () => {
|
|
31
|
+
expect(formatDate("M", { month: 1 })).toBe("1");
|
|
32
|
+
expect(formatDate("M", { month: 9 })).toBe("9");
|
|
33
|
+
expect(formatDate("M", { month: 12 })).toBe("12");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
|
|
39
|
+
//#region Day pattern
|
|
40
|
+
|
|
41
|
+
describe("Day pattern", () => {
|
|
42
|
+
it("outputs 2-digit day with dd format", () => {
|
|
43
|
+
expect(formatDate("dd", { day: 1 })).toBe("01");
|
|
44
|
+
expect(formatDate("dd", { day: 9 })).toBe("09");
|
|
45
|
+
expect(formatDate("dd", { day: 31 })).toBe("31");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("outputs day without padding with d format", () => {
|
|
49
|
+
expect(formatDate("d", { day: 1 })).toBe("1");
|
|
50
|
+
expect(formatDate("d", { day: 9 })).toBe("9");
|
|
51
|
+
expect(formatDate("d", { day: 31 })).toBe("31");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
|
|
57
|
+
//#region Day of week pattern
|
|
58
|
+
|
|
59
|
+
describe("Day of week pattern", () => {
|
|
60
|
+
it("outputs day of week in Korean with ddd format", () => {
|
|
61
|
+
// 2025-01-18 is Saturday
|
|
62
|
+
expect(formatDate("ddd", { year: 2025, month: 1, day: 18 })).toBe("토");
|
|
63
|
+
// 2025-01-19 is Sunday
|
|
64
|
+
expect(formatDate("ddd", { year: 2025, month: 1, day: 19 })).toBe("일");
|
|
65
|
+
// 2025-01-20 is Monday
|
|
66
|
+
expect(formatDate("ddd", { year: 2025, month: 1, day: 20 })).toBe("월");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
|
|
72
|
+
//#region Hour pattern
|
|
73
|
+
|
|
74
|
+
describe("Hour pattern", () => {
|
|
75
|
+
it("outputs 12-hour format with padding using hh format", () => {
|
|
76
|
+
expect(formatDate("hh", { hour: 0 })).toBe("12");
|
|
77
|
+
expect(formatDate("hh", { hour: 1 })).toBe("01");
|
|
78
|
+
expect(formatDate("hh", { hour: 12 })).toBe("12");
|
|
79
|
+
expect(formatDate("hh", { hour: 13 })).toBe("01");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("outputs 12-hour format without padding using h format", () => {
|
|
83
|
+
expect(formatDate("h", { hour: 0 })).toBe("12");
|
|
84
|
+
expect(formatDate("h", { hour: 1 })).toBe("1");
|
|
85
|
+
expect(formatDate("h", { hour: 9 })).toBe("9");
|
|
86
|
+
expect(formatDate("h", { hour: 10 })).toBe("10");
|
|
87
|
+
expect(formatDate("h", { hour: 12 })).toBe("12");
|
|
88
|
+
expect(formatDate("h", { hour: 13 })).toBe("1");
|
|
89
|
+
expect(formatDate("h", { hour: 23 })).toBe("11");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("outputs 24-hour format with padding using HH format", () => {
|
|
93
|
+
expect(formatDate("HH", { hour: 0 })).toBe("00");
|
|
94
|
+
expect(formatDate("HH", { hour: 9 })).toBe("09");
|
|
95
|
+
expect(formatDate("HH", { hour: 23 })).toBe("23");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("outputs 24-hour format without padding using H format", () => {
|
|
99
|
+
expect(formatDate("H", { hour: 0 })).toBe("0");
|
|
100
|
+
expect(formatDate("H", { hour: 9 })).toBe("9");
|
|
101
|
+
expect(formatDate("H", { hour: 23 })).toBe("23");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("outputs AM/PM with tt format", () => {
|
|
105
|
+
expect(formatDate("tt", { hour: 0 })).toBe("AM");
|
|
106
|
+
expect(formatDate("tt", { hour: 11 })).toBe("AM");
|
|
107
|
+
expect(formatDate("tt", { hour: 12 })).toBe("PM");
|
|
108
|
+
expect(formatDate("tt", { hour: 23 })).toBe("PM");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
|
|
114
|
+
//#region Minute pattern
|
|
115
|
+
|
|
116
|
+
describe("Minute pattern", () => {
|
|
117
|
+
it("outputs 2-digit minute with mm format", () => {
|
|
118
|
+
expect(formatDate("mm", { minute: 0 })).toBe("00");
|
|
119
|
+
expect(formatDate("mm", { minute: 5 })).toBe("05");
|
|
120
|
+
expect(formatDate("mm", { minute: 59 })).toBe("59");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("outputs minute without padding with m format", () => {
|
|
124
|
+
expect(formatDate("m", { minute: 0 })).toBe("0");
|
|
125
|
+
expect(formatDate("m", { minute: 5 })).toBe("5");
|
|
126
|
+
expect(formatDate("m", { minute: 59 })).toBe("59");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
|
|
132
|
+
//#region Second pattern
|
|
133
|
+
|
|
134
|
+
describe("Second pattern", () => {
|
|
135
|
+
it("outputs 2-digit second with ss format", () => {
|
|
136
|
+
expect(formatDate("ss", { second: 0 })).toBe("00");
|
|
137
|
+
expect(formatDate("ss", { second: 5 })).toBe("05");
|
|
138
|
+
expect(formatDate("ss", { second: 59 })).toBe("59");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("outputs second without padding with s format", () => {
|
|
142
|
+
expect(formatDate("s", { second: 0 })).toBe("0");
|
|
143
|
+
expect(formatDate("s", { second: 5 })).toBe("5");
|
|
144
|
+
expect(formatDate("s", { second: 59 })).toBe("59");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
|
|
150
|
+
//#region Millisecond pattern
|
|
151
|
+
|
|
152
|
+
describe("Millisecond pattern", () => {
|
|
153
|
+
it("outputs 3-digit millisecond with fff format", () => {
|
|
154
|
+
expect(formatDate("fff", { millisecond: 0 })).toBe("000");
|
|
155
|
+
expect(formatDate("fff", { millisecond: 5 })).toBe("005");
|
|
156
|
+
expect(formatDate("fff", { millisecond: 50 })).toBe("050");
|
|
157
|
+
expect(formatDate("fff", { millisecond: 500 })).toBe("500");
|
|
158
|
+
expect(formatDate("fff", { millisecond: 999 })).toBe("999");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("outputs 2-digit millisecond with ff format", () => {
|
|
162
|
+
expect(formatDate("ff", { millisecond: 0 })).toBe("00");
|
|
163
|
+
expect(formatDate("ff", { millisecond: 5 })).toBe("00");
|
|
164
|
+
expect(formatDate("ff", { millisecond: 50 })).toBe("05");
|
|
165
|
+
expect(formatDate("ff", { millisecond: 500 })).toBe("50");
|
|
166
|
+
expect(formatDate("ff", { millisecond: 999 })).toBe("99");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("outputs 1-digit millisecond with f format", () => {
|
|
170
|
+
expect(formatDate("f", { millisecond: 0 })).toBe("0");
|
|
171
|
+
expect(formatDate("f", { millisecond: 5 })).toBe("0");
|
|
172
|
+
expect(formatDate("f", { millisecond: 100 })).toBe("1");
|
|
173
|
+
expect(formatDate("f", { millisecond: 500 })).toBe("5");
|
|
174
|
+
expect(formatDate("f", { millisecond: 999 })).toBe("9");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
|
|
180
|
+
//#region Timezone pattern
|
|
181
|
+
|
|
182
|
+
describe("Timezone pattern", () => {
|
|
183
|
+
describe("Positive offset (East)", () => {
|
|
184
|
+
it("outputs +HH:mm format with zzz format", () => {
|
|
185
|
+
// UTC+9 (540 minutes)
|
|
186
|
+
expect(formatDate("zzz", { timezoneOffsetMinutes: 540 })).toBe("+09:00");
|
|
187
|
+
// UTC+5:30 (330 minutes)
|
|
188
|
+
expect(formatDate("zzz", { timezoneOffsetMinutes: 330 })).toBe("+05:30");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("outputs +HH format with zz format", () => {
|
|
192
|
+
expect(formatDate("zz", { timezoneOffsetMinutes: 540 })).toBe("+09");
|
|
193
|
+
expect(formatDate("zz", { timezoneOffsetMinutes: 60 })).toBe("+01");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("outputs +H format without padding with z format", () => {
|
|
197
|
+
expect(formatDate("z", { timezoneOffsetMinutes: 540 })).toBe("+9");
|
|
198
|
+
expect(formatDate("z", { timezoneOffsetMinutes: 60 })).toBe("+1");
|
|
199
|
+
expect(formatDate("z", { timezoneOffsetMinutes: 600 })).toBe("+10");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("Negative offset (West)", () => {
|
|
204
|
+
it("outputs -HH:mm format with zzz format", () => {
|
|
205
|
+
// UTC-5 (-300 minutes) - Integer hour offset
|
|
206
|
+
expect(formatDate("zzz", { timezoneOffsetMinutes: -300 })).toBe("-05:00");
|
|
207
|
+
// UTC-8 (-480 minutes) - Integer hour offset
|
|
208
|
+
expect(formatDate("zzz", { timezoneOffsetMinutes: -480 })).toBe("-08:00");
|
|
209
|
+
// UTC-3:30 (-210 minutes) - Newfoundland Standard Time
|
|
210
|
+
expect(formatDate("zzz", { timezoneOffsetMinutes: -210 })).toBe("-03:30");
|
|
211
|
+
// UTC-9:30 (-570 minutes) - Marquesas Islands
|
|
212
|
+
expect(formatDate("zzz", { timezoneOffsetMinutes: -570 })).toBe("-09:30");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("outputs -HH format with zz format", () => {
|
|
216
|
+
expect(formatDate("zz", { timezoneOffsetMinutes: -300 })).toBe("-05");
|
|
217
|
+
expect(formatDate("zz", { timezoneOffsetMinutes: -60 })).toBe("-01");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("outputs -H format without padding with z format", () => {
|
|
221
|
+
expect(formatDate("z", { timezoneOffsetMinutes: -300 })).toBe("-5");
|
|
222
|
+
expect(formatDate("z", { timezoneOffsetMinutes: -60 })).toBe("-1");
|
|
223
|
+
expect(formatDate("z", { timezoneOffsetMinutes: -720 })).toBe("-12");
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe("UTC (0 offset)", () => {
|
|
228
|
+
it("outputs +00:00 with zzz format", () => {
|
|
229
|
+
expect(formatDate("zzz", { timezoneOffsetMinutes: 0 })).toBe("+00:00");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("outputs +00 with zz format", () => {
|
|
233
|
+
expect(formatDate("zz", { timezoneOffsetMinutes: 0 })).toBe("+00");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("outputs +0 with z format", () => {
|
|
237
|
+
expect(formatDate("z", { timezoneOffsetMinutes: 0 })).toBe("+0");
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
//#endregion
|
|
243
|
+
|
|
244
|
+
//#endregion
|
|
245
|
+
|
|
246
|
+
//#region Complex format
|
|
247
|
+
|
|
248
|
+
describe("Complex format", () => {
|
|
249
|
+
it("handles full date/time format", () => {
|
|
250
|
+
const result = formatDate("yyyy-MM-dd HH:mm:ss.fff", {
|
|
251
|
+
year: 2025,
|
|
252
|
+
month: 1,
|
|
253
|
+
day: 18,
|
|
254
|
+
hour: 14,
|
|
255
|
+
minute: 30,
|
|
256
|
+
second: 45,
|
|
257
|
+
millisecond: 123,
|
|
258
|
+
});
|
|
259
|
+
expect(result).toBe("2025-01-18 14:30:45.123");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("handles 12-hour format", () => {
|
|
263
|
+
const result = formatDate("yyyy-MM-dd tt h:mm:ss", {
|
|
264
|
+
year: 2025,
|
|
265
|
+
month: 1,
|
|
266
|
+
day: 18,
|
|
267
|
+
hour: 14,
|
|
268
|
+
minute: 5,
|
|
269
|
+
second: 9,
|
|
270
|
+
});
|
|
271
|
+
expect(result).toBe("2025-01-18 PM 2:05:09");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("handles format with timezone", () => {
|
|
275
|
+
const result = formatDate("yyyy-MM-ddTHH:mm:sszzz", {
|
|
276
|
+
year: 2025,
|
|
277
|
+
month: 1,
|
|
278
|
+
day: 18,
|
|
279
|
+
hour: 14,
|
|
280
|
+
minute: 30,
|
|
281
|
+
second: 0,
|
|
282
|
+
timezoneOffsetMinutes: 540,
|
|
283
|
+
});
|
|
284
|
+
expect(result).toBe("2025-01-18T14:30:00+09:00");
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe("normalizeMonth", () => {
|
|
292
|
+
//#region Normal range
|
|
293
|
+
|
|
294
|
+
describe("Normal range (1-12)", () => {
|
|
295
|
+
it("returns unchanged if month is within 1-12 range", () => {
|
|
296
|
+
expect(normalizeMonth(2025, 1, 15)).toEqual({ year: 2025, month: 1, day: 15 });
|
|
297
|
+
expect(normalizeMonth(2025, 6, 15)).toEqual({ year: 2025, month: 6, day: 15 });
|
|
298
|
+
expect(normalizeMonth(2025, 12, 15)).toEqual({ year: 2025, month: 12, day: 15 });
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
//#endregion
|
|
303
|
+
|
|
304
|
+
//#region Month overflow
|
|
305
|
+
|
|
306
|
+
describe("Month overflow (13 or more)", () => {
|
|
307
|
+
it("month 13 becomes January next year", () => {
|
|
308
|
+
expect(normalizeMonth(2025, 13, 15)).toEqual({ year: 2026, month: 1, day: 15 });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("month 14 becomes February next year", () => {
|
|
312
|
+
expect(normalizeMonth(2025, 14, 15)).toEqual({ year: 2026, month: 2, day: 15 });
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("month 25 becomes January 2 years later", () => {
|
|
316
|
+
expect(normalizeMonth(2025, 25, 15)).toEqual({ year: 2027, month: 1, day: 15 });
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("month 24 becomes December next year", () => {
|
|
320
|
+
expect(normalizeMonth(2025, 24, 15)).toEqual({ year: 2026, month: 12, day: 15 });
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
//#endregion
|
|
325
|
+
|
|
326
|
+
//#region Month underflow
|
|
327
|
+
|
|
328
|
+
describe("Month underflow (0 or less)", () => {
|
|
329
|
+
it("month 0 becomes December previous year", () => {
|
|
330
|
+
expect(normalizeMonth(2025, 0, 15)).toEqual({ year: 2024, month: 12, day: 15 });
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("month -1 becomes November previous year", () => {
|
|
334
|
+
expect(normalizeMonth(2025, -1, 15)).toEqual({ year: 2024, month: 11, day: 15 });
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("month -11 becomes January previous year", () => {
|
|
338
|
+
expect(normalizeMonth(2025, -11, 15)).toEqual({ year: 2024, month: 1, day: 15 });
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("month -12 becomes December 2 years ago", () => {
|
|
342
|
+
expect(normalizeMonth(2025, -12, 15)).toEqual({ year: 2023, month: 12, day: 15 });
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("month -13 becomes November 2 years ago", () => {
|
|
346
|
+
expect(normalizeMonth(2025, -13, 15)).toEqual({ year: 2023, month: 11, day: 15 });
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
|
|
352
|
+
//#region Day adjustment
|
|
353
|
+
|
|
354
|
+
describe("Day adjustment (target month's last day)", () => {
|
|
355
|
+
it("day 31 adjusted to 28 when changing to February (non-leap year)", () => {
|
|
356
|
+
expect(normalizeMonth(2025, 2, 31)).toEqual({ year: 2025, month: 2, day: 28 });
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("day 31 adjusted to 29 when changing to February (leap year)", () => {
|
|
360
|
+
expect(normalizeMonth(2024, 2, 31)).toEqual({ year: 2024, month: 2, day: 29 });
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("day 31 adjusted to 30 when changing to April", () => {
|
|
364
|
+
expect(normalizeMonth(2025, 4, 31)).toEqual({ year: 2025, month: 4, day: 30 });
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("day unchanged if less than target month's day count", () => {
|
|
368
|
+
expect(normalizeMonth(2025, 3, 15)).toEqual({ year: 2025, month: 3, day: 15 });
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
//#endregion
|
|
373
|
+
});
|