@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,559 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Time } from "@simplysm/core-common";
|
|
3
|
+
|
|
4
|
+
describe("Time", () => {
|
|
5
|
+
//#region Constructor
|
|
6
|
+
|
|
7
|
+
describe("constructor", () => {
|
|
8
|
+
it("Returns current time when created without arguments", () => {
|
|
9
|
+
const time = new Time();
|
|
10
|
+
|
|
11
|
+
// Time changes in real-time so range test
|
|
12
|
+
expect(time.hour).toBeGreaterThanOrEqual(0);
|
|
13
|
+
expect(time.hour).toBeLessThanOrEqual(23);
|
|
14
|
+
expect(time.minute).toBeGreaterThanOrEqual(0);
|
|
15
|
+
expect(time.minute).toBeLessThanOrEqual(59);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("Creates with hour, minute, second", () => {
|
|
19
|
+
const time = new Time(15, 30, 45);
|
|
20
|
+
|
|
21
|
+
expect(time.hour).toBe(15);
|
|
22
|
+
expect(time.minute).toBe(30);
|
|
23
|
+
expect(time.second).toBe(45);
|
|
24
|
+
expect(time.millisecond).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("Creates with hour, minute, second, millisecond", () => {
|
|
28
|
+
const time = new Time(15, 30, 45, 123);
|
|
29
|
+
|
|
30
|
+
expect(time.hour).toBe(15);
|
|
31
|
+
expect(time.minute).toBe(30);
|
|
32
|
+
expect(time.second).toBe(45);
|
|
33
|
+
expect(time.millisecond).toBe(123);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("Creates with tick (millisecond)", () => {
|
|
37
|
+
// 15:30:45.123 = (15*60*60 + 30*60 + 45)*1000 + 123
|
|
38
|
+
const tick = (15 * 60 * 60 + 30 * 60 + 45) * 1000 + 123;
|
|
39
|
+
const time = new Time(tick);
|
|
40
|
+
|
|
41
|
+
expect(time.hour).toBe(15);
|
|
42
|
+
expect(time.minute).toBe(30);
|
|
43
|
+
expect(time.second).toBe(45);
|
|
44
|
+
expect(time.millisecond).toBe(123);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("Creates with Date type", () => {
|
|
48
|
+
const date = new Date(2025, 0, 6, 15, 30, 45, 123);
|
|
49
|
+
const time = new Time(date);
|
|
50
|
+
|
|
51
|
+
expect(time.hour).toBe(15);
|
|
52
|
+
expect(time.minute).toBe(30);
|
|
53
|
+
expect(time.second).toBe(45);
|
|
54
|
+
expect(time.millisecond).toBe(123);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("Normalizes to 24 hours if exceeds", () => {
|
|
58
|
+
// 25 hours = 1 hour
|
|
59
|
+
const time = new Time(25, 0, 0);
|
|
60
|
+
|
|
61
|
+
expect(time.hour).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("Normalizes negative hours/minutes/seconds to within 24 hours", () => {
|
|
65
|
+
// -1 hour 30 minutes = 23 hours 30 minutes (24 - 0.5 = 23.5)
|
|
66
|
+
const time = new Time(-1, 30, 0);
|
|
67
|
+
|
|
68
|
+
expect(time.hour).toBe(23);
|
|
69
|
+
expect(time.minute).toBe(30);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("Normalizes negative tick to within 24 hours", () => {
|
|
73
|
+
// -1 hour = -3600000ms → 23 hours
|
|
74
|
+
const time = new Time(-3600000);
|
|
75
|
+
|
|
76
|
+
expect(time.hour).toBe(23);
|
|
77
|
+
expect(time.minute).toBe(0);
|
|
78
|
+
expect(time.second).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("Negative tick (-1ms) normalizes to 23:59:59.999", () => {
|
|
82
|
+
const time = new Time(-1);
|
|
83
|
+
|
|
84
|
+
expect(time.hour).toBe(23);
|
|
85
|
+
expect(time.minute).toBe(59);
|
|
86
|
+
expect(time.second).toBe(59);
|
|
87
|
+
expect(time.millisecond).toBe(999);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
|
|
93
|
+
//#region parse
|
|
94
|
+
|
|
95
|
+
describe("parse()", () => {
|
|
96
|
+
it("Parses HH:mm:ss format", () => {
|
|
97
|
+
const time = Time.parse("15:30:45");
|
|
98
|
+
|
|
99
|
+
expect(time.hour).toBe(15);
|
|
100
|
+
expect(time.minute).toBe(30);
|
|
101
|
+
expect(time.second).toBe(45);
|
|
102
|
+
expect(time.millisecond).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("Parses HH:mm:ss.fff format", () => {
|
|
106
|
+
const time = Time.parse("15:30:45.123");
|
|
107
|
+
|
|
108
|
+
expect(time.hour).toBe(15);
|
|
109
|
+
expect(time.minute).toBe(30);
|
|
110
|
+
expect(time.second).toBe(45);
|
|
111
|
+
expect(time.millisecond).toBe(123);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("Parses AM HH:mm:ss format", () => {
|
|
115
|
+
const time = Time.parse("AM 9:30:45");
|
|
116
|
+
|
|
117
|
+
expect(time.hour).toBe(9);
|
|
118
|
+
expect(time.minute).toBe(30);
|
|
119
|
+
expect(time.second).toBe(45);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("Parses PM HH:mm:ss format", () => {
|
|
123
|
+
const time = Time.parse("PM 3:30:45");
|
|
124
|
+
|
|
125
|
+
expect(time.hour).toBe(15); // 12 + 3
|
|
126
|
+
expect(time.minute).toBe(30);
|
|
127
|
+
expect(time.second).toBe(45);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("Pads milliseconds with 0 if insufficient digits", () => {
|
|
131
|
+
const time = Time.parse("15:30:45.1");
|
|
132
|
+
|
|
133
|
+
expect(time.millisecond).toBe(100); // '1' → '100'
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("Throws error for invalid format", () => {
|
|
137
|
+
expect(() => Time.parse("invalid-time")).toThrow();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("PM 12:00:00 is noon (12 o'clock)", () => {
|
|
141
|
+
const time = Time.parse("PM 12:00:00");
|
|
142
|
+
|
|
143
|
+
expect(time.hour).toBe(12);
|
|
144
|
+
expect(time.minute).toBe(0);
|
|
145
|
+
expect(time.second).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("AM 12:00:00 is midnight (0 o'clock)", () => {
|
|
149
|
+
const time = Time.parse("AM 12:00:00");
|
|
150
|
+
|
|
151
|
+
expect(time.hour).toBe(0);
|
|
152
|
+
expect(time.minute).toBe(0);
|
|
153
|
+
expect(time.second).toBe(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("PM 12:30:45 is after noon (12:30:45)", () => {
|
|
157
|
+
const time = Time.parse("PM 12:30:45");
|
|
158
|
+
|
|
159
|
+
expect(time.hour).toBe(12);
|
|
160
|
+
expect(time.minute).toBe(30);
|
|
161
|
+
expect(time.second).toBe(45);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("AM 12:30:45 is after midnight (0:30:45)", () => {
|
|
165
|
+
const time = Time.parse("AM 12:30:45");
|
|
166
|
+
|
|
167
|
+
expect(time.hour).toBe(0);
|
|
168
|
+
expect(time.minute).toBe(30);
|
|
169
|
+
expect(time.second).toBe(45);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("Parses time from ISO 8601 format (UTC -> local conversion)", () => {
|
|
173
|
+
// UTC time is converted to local time
|
|
174
|
+
const time = Time.parse("2025-01-15T10:30:45Z");
|
|
175
|
+
const expected = new Date("2025-01-15T10:30:45Z");
|
|
176
|
+
|
|
177
|
+
expect(time.hour).toBe(expected.getHours());
|
|
178
|
+
expect(time.minute).toBe(30);
|
|
179
|
+
expect(time.second).toBe(45);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("Parses milliseconds from ISO 8601 format (UTC -> local conversion)", () => {
|
|
183
|
+
// UTC time is converted to local time
|
|
184
|
+
const time = Time.parse("2025-01-15T10:30:45.123Z");
|
|
185
|
+
const expected = new Date("2025-01-15T10:30:45.123Z");
|
|
186
|
+
|
|
187
|
+
expect(time.hour).toBe(expected.getHours());
|
|
188
|
+
expect(time.minute).toBe(30);
|
|
189
|
+
expect(time.second).toBe(45);
|
|
190
|
+
expect(time.millisecond).toBe(123);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
|
|
196
|
+
//#region Getters
|
|
197
|
+
|
|
198
|
+
describe("Getters", () => {
|
|
199
|
+
it("Returns hour", () => {
|
|
200
|
+
const time = new Time(15, 30, 45, 123);
|
|
201
|
+
expect(time.hour).toBe(15);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("Returns minute", () => {
|
|
205
|
+
const time = new Time(15, 30, 45, 123);
|
|
206
|
+
expect(time.minute).toBe(30);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("Returns second", () => {
|
|
210
|
+
const time = new Time(15, 30, 45, 123);
|
|
211
|
+
expect(time.second).toBe(45);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("Returns millisecond", () => {
|
|
215
|
+
const time = new Time(15, 30, 45, 123);
|
|
216
|
+
expect(time.millisecond).toBe(123);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("Returns tick", () => {
|
|
220
|
+
const time = new Time(15, 30, 45, 123);
|
|
221
|
+
const expectedTick = (15 * 60 * 60 + 30 * 60 + 45) * 1000 + 123;
|
|
222
|
+
expect(time.tick).toBe(expectedTick);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
|
|
228
|
+
//#region tick comparison
|
|
229
|
+
|
|
230
|
+
describe("tick comparison", () => {
|
|
231
|
+
it("Same times have same tick", () => {
|
|
232
|
+
const t1 = new Time(10, 30, 45, 123);
|
|
233
|
+
const t2 = new Time(10, 30, 45, 123);
|
|
234
|
+
|
|
235
|
+
expect(t1.tick).toBe(t2.tick);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("Different times have different ticks", () => {
|
|
239
|
+
const t1 = new Time(10, 30, 45, 123);
|
|
240
|
+
const t2 = new Time(10, 30, 45, 124);
|
|
241
|
+
|
|
242
|
+
expect(t1.tick).not.toBe(t2.tick);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("Can compare time order by tick", () => {
|
|
246
|
+
const t1 = new Time(0, 0, 0);
|
|
247
|
+
const t2 = new Time(12, 30, 0);
|
|
248
|
+
const t3 = new Time(23, 59, 59);
|
|
249
|
+
|
|
250
|
+
expect(t1.tick).toBeLessThan(t2.tick);
|
|
251
|
+
expect(t2.tick).toBeLessThan(t3.tick);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("Millisecond precision comparison is possible", () => {
|
|
255
|
+
const t1 = new Time(10, 30, 45, 0);
|
|
256
|
+
const t2 = new Time(10, 30, 45, 1);
|
|
257
|
+
|
|
258
|
+
expect(t2.tick - t1.tick).toBe(1);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("Can calculate time difference by tick", () => {
|
|
262
|
+
const t1 = new Time(10, 0, 0);
|
|
263
|
+
const t2 = new Time(11, 0, 0);
|
|
264
|
+
|
|
265
|
+
// 1 hour = 3600000ms
|
|
266
|
+
expect(t2.tick - t1.tick).toBe(3600000);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
//#endregion
|
|
271
|
+
|
|
272
|
+
//#region setX methods (immutable)
|
|
273
|
+
|
|
274
|
+
describe("setHour()", () => {
|
|
275
|
+
it("Returns new instance with hour changed", () => {
|
|
276
|
+
const time = new Time(15, 30, 45, 123);
|
|
277
|
+
const newTime = time.setHour(20);
|
|
278
|
+
|
|
279
|
+
expect(newTime.hour).toBe(20);
|
|
280
|
+
expect(newTime.minute).toBe(30);
|
|
281
|
+
expect(newTime.second).toBe(45);
|
|
282
|
+
expect(newTime.millisecond).toBe(123);
|
|
283
|
+
expect(time.hour).toBe(15); // original immutable
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("setMinute()", () => {
|
|
288
|
+
it("Returns new instance with minute changed", () => {
|
|
289
|
+
const time = new Time(15, 30, 45, 123);
|
|
290
|
+
const newTime = time.setMinute(50);
|
|
291
|
+
|
|
292
|
+
expect(newTime.hour).toBe(15);
|
|
293
|
+
expect(newTime.minute).toBe(50);
|
|
294
|
+
expect(newTime.second).toBe(45);
|
|
295
|
+
expect(newTime.millisecond).toBe(123);
|
|
296
|
+
expect(time.minute).toBe(30); // original immutable
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("setSecond()", () => {
|
|
301
|
+
it("Returns new instance with second changed", () => {
|
|
302
|
+
const time = new Time(15, 30, 45, 123);
|
|
303
|
+
const newTime = time.setSecond(55);
|
|
304
|
+
|
|
305
|
+
expect(newTime.hour).toBe(15);
|
|
306
|
+
expect(newTime.minute).toBe(30);
|
|
307
|
+
expect(newTime.second).toBe(55);
|
|
308
|
+
expect(newTime.millisecond).toBe(123);
|
|
309
|
+
expect(time.second).toBe(45); // original immutable
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe("setMillisecond()", () => {
|
|
314
|
+
it("Returns new instance with millisecond changed", () => {
|
|
315
|
+
const time = new Time(15, 30, 45, 123);
|
|
316
|
+
const newTime = time.setMillisecond(456);
|
|
317
|
+
|
|
318
|
+
expect(newTime.hour).toBe(15);
|
|
319
|
+
expect(newTime.minute).toBe(30);
|
|
320
|
+
expect(newTime.second).toBe(45);
|
|
321
|
+
expect(newTime.millisecond).toBe(456);
|
|
322
|
+
expect(time.millisecond).toBe(123); // original immutable
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
//#endregion
|
|
327
|
+
|
|
328
|
+
//#region addX methods (immutable)
|
|
329
|
+
|
|
330
|
+
describe("addHours()", () => {
|
|
331
|
+
it("Adds positive hours", () => {
|
|
332
|
+
const time = new Time(15, 30, 45);
|
|
333
|
+
const newTime = time.addHours(3);
|
|
334
|
+
|
|
335
|
+
expect(newTime.hour).toBe(18);
|
|
336
|
+
expect(newTime.minute).toBe(30);
|
|
337
|
+
expect(newTime.second).toBe(45);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("Adds negative hours (subtraction)", () => {
|
|
341
|
+
const time = new Time(15, 30, 45);
|
|
342
|
+
const newTime = time.addHours(-5);
|
|
343
|
+
|
|
344
|
+
expect(newTime.hour).toBe(10);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("Keeps remainder if exceeds 24 hours", () => {
|
|
348
|
+
const time = new Time(22, 0, 0);
|
|
349
|
+
const newTime = time.addHours(5);
|
|
350
|
+
|
|
351
|
+
expect(newTime.hour).toBe(3); // (22 + 5) % 24
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe("addMinutes()", () => {
|
|
356
|
+
it("Adds positive minutes", () => {
|
|
357
|
+
const time = new Time(15, 30, 45);
|
|
358
|
+
const newTime = time.addMinutes(20);
|
|
359
|
+
|
|
360
|
+
expect(newTime.hour).toBe(15);
|
|
361
|
+
expect(newTime.minute).toBe(50);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("Adds negative minutes (subtraction)", () => {
|
|
365
|
+
const time = new Time(15, 30, 45);
|
|
366
|
+
const newTime = time.addMinutes(-20);
|
|
367
|
+
|
|
368
|
+
expect(newTime.minute).toBe(10);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("Increases hour if exceeds 60 minutes", () => {
|
|
372
|
+
const time = new Time(15, 50, 0);
|
|
373
|
+
const newTime = time.addMinutes(20);
|
|
374
|
+
|
|
375
|
+
expect(newTime.hour).toBe(16);
|
|
376
|
+
expect(newTime.minute).toBe(10);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe("addSeconds()", () => {
|
|
381
|
+
it("Adds positive seconds", () => {
|
|
382
|
+
const time = new Time(15, 30, 45);
|
|
383
|
+
const newTime = time.addSeconds(10);
|
|
384
|
+
|
|
385
|
+
expect(newTime.hour).toBe(15);
|
|
386
|
+
expect(newTime.minute).toBe(30);
|
|
387
|
+
expect(newTime.second).toBe(55);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("Adds negative seconds (subtraction)", () => {
|
|
391
|
+
const time = new Time(15, 30, 45);
|
|
392
|
+
const newTime = time.addSeconds(-10);
|
|
393
|
+
|
|
394
|
+
expect(newTime.second).toBe(35);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("Increases minute if exceeds 60 seconds", () => {
|
|
398
|
+
const time = new Time(15, 30, 50);
|
|
399
|
+
const newTime = time.addSeconds(20);
|
|
400
|
+
|
|
401
|
+
expect(newTime.minute).toBe(31);
|
|
402
|
+
expect(newTime.second).toBe(10);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
describe("addMilliseconds()", () => {
|
|
407
|
+
it("Adds positive milliseconds", () => {
|
|
408
|
+
const time = new Time(15, 30, 45, 100);
|
|
409
|
+
const newTime = time.addMilliseconds(50);
|
|
410
|
+
|
|
411
|
+
expect(newTime.millisecond).toBe(150);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("Adds negative milliseconds (subtraction)", () => {
|
|
415
|
+
const time = new Time(15, 30, 45, 100);
|
|
416
|
+
const newTime = time.addMilliseconds(-50);
|
|
417
|
+
|
|
418
|
+
expect(newTime.millisecond).toBe(50);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("Increases second if exceeds 1000 milliseconds", () => {
|
|
422
|
+
const time = new Time(15, 30, 45, 900);
|
|
423
|
+
const newTime = time.addMilliseconds(200);
|
|
424
|
+
|
|
425
|
+
expect(newTime.second).toBe(46);
|
|
426
|
+
expect(newTime.millisecond).toBe(100);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
//#endregion
|
|
431
|
+
|
|
432
|
+
//#region Negative operations (24-hour boundary handling)
|
|
433
|
+
|
|
434
|
+
describe("Negative operations (24-hour boundary)", () => {
|
|
435
|
+
it("addHours(-25) is same time yesterday (23 hours ago)", () => {
|
|
436
|
+
// Subtracting 25 hours from 10:00 = previous day 9:00 = 24 - 25 + 10 = 9:00
|
|
437
|
+
const time = new Time(10, 0, 0);
|
|
438
|
+
const newTime = time.addHours(-25);
|
|
439
|
+
|
|
440
|
+
expect(newTime.hour).toBe(9);
|
|
441
|
+
expect(newTime.minute).toBe(0);
|
|
442
|
+
expect(newTime.second).toBe(0);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it("addHours(-10) crosses midnight to previous day", () => {
|
|
446
|
+
// Subtracting 10 hours from 5:00 = 19:00
|
|
447
|
+
const time = new Time(5, 0, 0);
|
|
448
|
+
const newTime = time.addHours(-10);
|
|
449
|
+
|
|
450
|
+
expect(newTime.hour).toBe(19);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("addMinutes(-90) is 1 hour 30 minutes ago", () => {
|
|
454
|
+
// Subtracting 90 minutes from 1:30 = 0:00
|
|
455
|
+
const time = new Time(1, 30, 0);
|
|
456
|
+
const newTime = time.addMinutes(-90);
|
|
457
|
+
|
|
458
|
+
expect(newTime.hour).toBe(0);
|
|
459
|
+
expect(newTime.minute).toBe(0);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("addMinutes(-90) crosses midnight to previous day", () => {
|
|
463
|
+
// Subtracting 90 minutes from 0:30 = previous day 23:00
|
|
464
|
+
const time = new Time(0, 30, 0);
|
|
465
|
+
const newTime = time.addMinutes(-90);
|
|
466
|
+
|
|
467
|
+
expect(newTime.hour).toBe(23);
|
|
468
|
+
expect(newTime.minute).toBe(0);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("addSeconds(-3700) is about 1 hour ago", () => {
|
|
472
|
+
// Subtracting 3700 seconds (1 hour 1 minute 40 seconds) from 1:00:00 = 23:58:20
|
|
473
|
+
const time = new Time(1, 0, 0);
|
|
474
|
+
const newTime = time.addSeconds(-3700);
|
|
475
|
+
|
|
476
|
+
expect(newTime.hour).toBe(23);
|
|
477
|
+
expect(newTime.minute).toBe(58);
|
|
478
|
+
expect(newTime.second).toBe(20);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("addMilliseconds(-1000) crosses midnight to previous day", () => {
|
|
482
|
+
// Subtracting 1000ms from 0:00:00.500ms = previous day 23:59:59.500ms
|
|
483
|
+
const time = new Time(0, 0, 0, 500);
|
|
484
|
+
const newTime = time.addMilliseconds(-1000);
|
|
485
|
+
|
|
486
|
+
expect(newTime.hour).toBe(23);
|
|
487
|
+
expect(newTime.minute).toBe(59);
|
|
488
|
+
expect(newTime.second).toBe(59);
|
|
489
|
+
expect(newTime.millisecond).toBe(500);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
//#endregion
|
|
494
|
+
|
|
495
|
+
//#region isValid
|
|
496
|
+
|
|
497
|
+
describe("isValid", () => {
|
|
498
|
+
it("Valid time returns true", () => {
|
|
499
|
+
const time = new Time(15, 30, 45);
|
|
500
|
+
expect(time.isValid).toBe(true);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("Default constructor is valid time", () => {
|
|
504
|
+
const time = new Time();
|
|
505
|
+
expect(time.isValid).toBe(true);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("Time created with tick is valid", () => {
|
|
509
|
+
const tick = (15 * 60 * 60 + 30 * 60 + 45) * 1000;
|
|
510
|
+
const time = new Time(tick);
|
|
511
|
+
expect(time.isValid).toBe(true);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("Time created with NaN tick has isValid false", () => {
|
|
515
|
+
const time = new Time(NaN);
|
|
516
|
+
expect(time.isValid).toBe(false);
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
//#endregion
|
|
521
|
+
|
|
522
|
+
//#region Formatting
|
|
523
|
+
|
|
524
|
+
describe("toFormatString()", () => {
|
|
525
|
+
it("Formats to HH:mm:ss format", () => {
|
|
526
|
+
const time = new Time(15, 30, 45);
|
|
527
|
+
expect(time.toFormatString("HH:mm:ss")).toBe("15:30:45");
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("Formats to HH:mm:ss.fff format", () => {
|
|
531
|
+
const time = new Time(15, 30, 45, 123);
|
|
532
|
+
expect(time.toFormatString("HH:mm:ss.fff")).toBe("15:30:45.123");
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("Formats to tt hh:mm:ss format (AM)", () => {
|
|
536
|
+
const time = new Time(9, 30, 45);
|
|
537
|
+
expect(time.toFormatString("tt hh:mm:ss")).toBe("AM 09:30:45");
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("Formats to tt hh:mm:ss format (PM)", () => {
|
|
541
|
+
const time = new Time(15, 30, 45);
|
|
542
|
+
expect(time.toFormatString("tt hh:mm:ss")).toBe("PM 03:30:45");
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it("Formats to H:m:s format (no padding)", () => {
|
|
546
|
+
const time = new Time(9, 5, 3);
|
|
547
|
+
expect(time.toFormatString("H:m:s")).toBe("9:5:3");
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
describe("toString()", () => {
|
|
552
|
+
it("Returns default format HH:mm:ss.fff", () => {
|
|
553
|
+
const time = new Time(15, 30, 45, 123);
|
|
554
|
+
expect(time.toString()).toBe("15:30:45.123");
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
//#endregion
|
|
559
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Uuid } from "@simplysm/core-common";
|
|
3
|
+
|
|
4
|
+
describe("Uuid", () => {
|
|
5
|
+
describe("new()", () => {
|
|
6
|
+
it("Generates valid UUID v4 format", () => {
|
|
7
|
+
const uuid = Uuid.new();
|
|
8
|
+
const str = uuid.toString();
|
|
9
|
+
|
|
10
|
+
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
11
|
+
expect(str).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("Generates new UUID each time", () => {
|
|
15
|
+
const uuid1 = Uuid.new();
|
|
16
|
+
const uuid2 = Uuid.new();
|
|
17
|
+
|
|
18
|
+
expect(uuid1.toString()).not.toBe(uuid2.toString());
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("fromBytes()", () => {
|
|
23
|
+
it("Creates UUID from 16-byte Uint8Array", () => {
|
|
24
|
+
const bytes = new Uint8Array([
|
|
25
|
+
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde,
|
|
26
|
+
0xf0,
|
|
27
|
+
]);
|
|
28
|
+
const uuid = Uuid.fromBytes(bytes);
|
|
29
|
+
|
|
30
|
+
expect(uuid.toString()).toBe("12345678-9abc-def0-1234-56789abcdef0");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("Throws error for non-16-byte input", () => {
|
|
34
|
+
const bytes = new Uint8Array([0x12, 0x34]);
|
|
35
|
+
|
|
36
|
+
expect(() => Uuid.fromBytes(bytes)).toThrow("UUID byte size must be 16");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("toBytes()", () => {
|
|
41
|
+
it("Converts UUID to 16-byte Uint8Array", () => {
|
|
42
|
+
const uuid = new Uuid("12345678-9abc-def0-1234-56789abcdef0");
|
|
43
|
+
const bytes = uuid.toBytes();
|
|
44
|
+
|
|
45
|
+
expect(bytes).toBeInstanceOf(Uint8Array);
|
|
46
|
+
expect(bytes.length).toBe(16);
|
|
47
|
+
expect(Array.from(bytes)).toEqual([
|
|
48
|
+
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde,
|
|
49
|
+
0xf0,
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("fromBytes and toBytes are inverse operations", () => {
|
|
54
|
+
const originalBytes = new Uint8Array([
|
|
55
|
+
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde,
|
|
56
|
+
0xf0,
|
|
57
|
+
]);
|
|
58
|
+
const uuid = Uuid.fromBytes(originalBytes);
|
|
59
|
+
const resultBytes = uuid.toBytes();
|
|
60
|
+
|
|
61
|
+
expect(Array.from(resultBytes)).toEqual(Array.from(originalBytes));
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("constructor", () => {
|
|
66
|
+
it("Throws error for invalid UUID format", () => {
|
|
67
|
+
expect(() => new Uuid("invalid-uuid")).toThrow();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("Throws error for mismatched UUID length", () => {
|
|
71
|
+
expect(() => new Uuid("12345678-9abc")).toThrow();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|