@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.
Files changed (151) hide show
  1. package/README.md +66 -267
  2. package/dist/common.types.d.ts +14 -14
  3. package/dist/errors/argument-error.d.ts +10 -10
  4. package/dist/errors/argument-error.d.ts.map +1 -1
  5. package/dist/errors/argument-error.js +2 -2
  6. package/dist/errors/argument-error.js.map +1 -1
  7. package/dist/errors/not-implemented-error.d.ts +8 -8
  8. package/dist/errors/not-implemented-error.js +2 -2
  9. package/dist/errors/not-implemented-error.js.map +1 -1
  10. package/dist/errors/sd-error.d.ts +10 -10
  11. package/dist/errors/sd-error.d.ts.map +1 -1
  12. package/dist/errors/timeout-error.d.ts +10 -10
  13. package/dist/errors/timeout-error.js +3 -3
  14. package/dist/errors/timeout-error.js.map +1 -1
  15. package/dist/extensions/arr-ext.d.ts +2 -2
  16. package/dist/extensions/arr-ext.helpers.d.ts +8 -8
  17. package/dist/extensions/arr-ext.helpers.js +1 -1
  18. package/dist/extensions/arr-ext.helpers.js.map +1 -1
  19. package/dist/extensions/arr-ext.js +13 -13
  20. package/dist/extensions/arr-ext.js.map +1 -1
  21. package/dist/extensions/arr-ext.types.d.ts +57 -57
  22. package/dist/extensions/arr-ext.types.d.ts.map +1 -1
  23. package/dist/extensions/map-ext.d.ts +16 -16
  24. package/dist/extensions/set-ext.d.ts +11 -11
  25. package/dist/features/debounce-queue.d.ts +17 -15
  26. package/dist/features/debounce-queue.d.ts.map +1 -1
  27. package/dist/features/debounce-queue.js +6 -6
  28. package/dist/features/debounce-queue.js.map +1 -1
  29. package/dist/features/event-emitter.d.ts +20 -20
  30. package/dist/features/event-emitter.js +17 -17
  31. package/dist/features/serial-queue.d.ts +11 -11
  32. package/dist/features/serial-queue.js +5 -5
  33. package/dist/features/serial-queue.js.map +1 -1
  34. package/dist/globals.d.ts +4 -4
  35. package/dist/types/date-only.d.ts +64 -64
  36. package/dist/types/date-only.d.ts.map +1 -1
  37. package/dist/types/date-only.js +63 -63
  38. package/dist/types/date-time.d.ts +37 -37
  39. package/dist/types/date-time.d.ts.map +1 -1
  40. package/dist/types/date-time.js +54 -37
  41. package/dist/types/date-time.js.map +1 -1
  42. package/dist/types/lazy-gc-map.d.ts +26 -26
  43. package/dist/types/lazy-gc-map.d.ts.map +1 -1
  44. package/dist/types/lazy-gc-map.js +26 -26
  45. package/dist/types/lazy-gc-map.js.map +1 -1
  46. package/dist/types/time.d.ts +25 -25
  47. package/dist/types/time.d.ts.map +1 -1
  48. package/dist/types/time.js +25 -25
  49. package/dist/types/time.js.map +1 -1
  50. package/dist/types/uuid.d.ts +11 -11
  51. package/dist/types/uuid.d.ts.map +1 -1
  52. package/dist/types/uuid.js +12 -12
  53. package/dist/types/uuid.js.map +1 -1
  54. package/dist/utils/bytes.d.ts +17 -17
  55. package/dist/utils/bytes.js +4 -4
  56. package/dist/utils/bytes.js.map +1 -1
  57. package/dist/utils/date-format.d.ts +45 -45
  58. package/dist/utils/date-format.js +1 -1
  59. package/dist/utils/date-format.js.map +1 -1
  60. package/dist/utils/error.d.ts +4 -4
  61. package/dist/utils/json.d.ts +17 -17
  62. package/dist/utils/json.js +3 -3
  63. package/dist/utils/json.js.map +1 -1
  64. package/dist/utils/num.d.ts +23 -23
  65. package/dist/utils/obj.d.ts +111 -111
  66. package/dist/utils/obj.d.ts.map +1 -1
  67. package/dist/utils/obj.js +3 -3
  68. package/dist/utils/obj.js.map +1 -1
  69. package/dist/utils/path.d.ts +10 -10
  70. package/dist/utils/primitive.d.ts +5 -5
  71. package/dist/utils/primitive.js +1 -1
  72. package/dist/utils/primitive.js.map +1 -1
  73. package/dist/utils/str.d.ts +46 -46
  74. package/dist/utils/str.d.ts.map +1 -1
  75. package/dist/utils/str.js +5 -5
  76. package/dist/utils/str.js.map +1 -1
  77. package/dist/utils/template-strings.d.ts +26 -26
  78. package/dist/utils/transferable.d.ts +18 -18
  79. package/dist/utils/transferable.js +1 -1
  80. package/dist/utils/transferable.js.map +1 -1
  81. package/dist/utils/wait.d.ts +9 -9
  82. package/dist/utils/xml.d.ts +13 -13
  83. package/dist/utils/xml.d.ts.map +1 -1
  84. package/dist/utils/xml.js +1 -0
  85. package/dist/utils/xml.js.map +1 -1
  86. package/dist/zip/sd-zip.d.ts +22 -22
  87. package/dist/zip/sd-zip.js +16 -16
  88. package/package.json +4 -4
  89. package/src/common.types.ts +17 -17
  90. package/src/errors/argument-error.ts +15 -15
  91. package/src/errors/not-implemented-error.ts +9 -9
  92. package/src/errors/sd-error.ts +12 -12
  93. package/src/errors/timeout-error.ts +12 -12
  94. package/src/extensions/arr-ext.helpers.ts +10 -10
  95. package/src/extensions/arr-ext.ts +57 -57
  96. package/src/extensions/arr-ext.types.ts +59 -59
  97. package/src/extensions/map-ext.ts +16 -16
  98. package/src/extensions/set-ext.ts +11 -11
  99. package/src/features/debounce-queue.ts +21 -19
  100. package/src/features/event-emitter.ts +25 -25
  101. package/src/features/serial-queue.ts +13 -13
  102. package/src/globals.ts +4 -4
  103. package/src/index.ts +1 -1
  104. package/src/types/date-only.ts +83 -83
  105. package/src/types/date-time.ts +64 -44
  106. package/src/types/lazy-gc-map.ts +45 -45
  107. package/src/types/time.ts +34 -34
  108. package/src/types/uuid.ts +17 -17
  109. package/src/utils/bytes.ts +35 -35
  110. package/src/utils/date-format.ts +65 -65
  111. package/src/utils/error.ts +4 -4
  112. package/src/utils/json.ts +39 -39
  113. package/src/utils/num.ts +23 -23
  114. package/src/utils/obj.ts +138 -138
  115. package/src/utils/path.ts +10 -10
  116. package/src/utils/primitive.ts +6 -6
  117. package/src/utils/str.ts +260 -261
  118. package/src/utils/template-strings.ts +29 -29
  119. package/src/utils/transferable.ts +284 -284
  120. package/src/utils/wait.ts +10 -10
  121. package/src/utils/xml.ts +20 -19
  122. package/src/zip/sd-zip.ts +25 -25
  123. package/tests/errors/errors.spec.ts +80 -0
  124. package/tests/extensions/array-extension.spec.ts +796 -0
  125. package/tests/extensions/map-extension.spec.ts +147 -0
  126. package/tests/extensions/set-extension.spec.ts +74 -0
  127. package/tests/types/date-only.spec.ts +638 -0
  128. package/tests/types/date-time.spec.ts +391 -0
  129. package/tests/types/lazy-gc-map.spec.ts +692 -0
  130. package/tests/types/time.spec.ts +559 -0
  131. package/tests/types/uuid.spec.ts +74 -0
  132. package/tests/utils/bytes-utils.spec.ts +230 -0
  133. package/tests/utils/date-format.spec.ts +373 -0
  134. package/tests/utils/debounce-queue.spec.ts +272 -0
  135. package/tests/utils/json.spec.ts +486 -0
  136. package/tests/utils/number.spec.ts +157 -0
  137. package/tests/utils/object.spec.ts +829 -0
  138. package/tests/utils/path.spec.ts +78 -0
  139. package/tests/utils/primitive.spec.ts +43 -0
  140. package/tests/utils/sd-event-emitter.spec.ts +216 -0
  141. package/tests/utils/serial-queue.spec.ts +365 -0
  142. package/tests/utils/string.spec.ts +281 -0
  143. package/tests/utils/template-strings.spec.ts +57 -0
  144. package/tests/utils/transferable.spec.ts +703 -0
  145. package/tests/utils/wait.spec.ts +145 -0
  146. package/tests/utils/xml.spec.ts +146 -0
  147. package/tests/zip/sd-zip.spec.ts +238 -0
  148. package/docs/extensions.md +0 -503
  149. package/docs/features.md +0 -109
  150. package/docs/types.md +0 -486
  151. package/docs/utils.md +0 -780
@@ -0,0 +1,638 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { DateOnly } from "@simplysm/core-common";
3
+
4
+ describe("DateOnly", () => {
5
+ //#region Constructor
6
+
7
+ describe("constructor", () => {
8
+ it("Returns today's date when created without arguments", () => {
9
+ const now = new Date();
10
+ const dateOnly = new DateOnly();
11
+
12
+ expect(dateOnly.year).toBe(now.getFullYear());
13
+ expect(dateOnly.month).toBe(now.getMonth() + 1);
14
+ expect(dateOnly.day).toBe(now.getDate());
15
+ });
16
+
17
+ it("Creates with year/month/day", () => {
18
+ const dateOnly = new DateOnly(2025, 1, 6);
19
+
20
+ expect(dateOnly.year).toBe(2025);
21
+ expect(dateOnly.month).toBe(1);
22
+ expect(dateOnly.day).toBe(6);
23
+ });
24
+
25
+ it("Creates with tick (millisecond)", () => {
26
+ const tick = new Date(2025, 0, 6).getTime();
27
+ const dateOnly = new DateOnly(tick);
28
+
29
+ expect(dateOnly.year).toBe(2025);
30
+ expect(dateOnly.month).toBe(1);
31
+ expect(dateOnly.day).toBe(6);
32
+ });
33
+
34
+ it("Creates with Date type", () => {
35
+ const date = new Date(2025, 0, 6, 15, 30, 45);
36
+ const dateOnly = new DateOnly(date);
37
+
38
+ expect(dateOnly.year).toBe(2025);
39
+ expect(dateOnly.month).toBe(1);
40
+ expect(dateOnly.day).toBe(6);
41
+ });
42
+
43
+ it("Ignores time when creating from Date type", () => {
44
+ const date1 = new Date(2025, 0, 6, 0, 0, 0);
45
+ const date2 = new Date(2025, 0, 6, 23, 59, 59);
46
+
47
+ const dateOnly1 = new DateOnly(date1);
48
+ const dateOnly2 = new DateOnly(date2);
49
+
50
+ expect(dateOnly1.tick).toBe(dateOnly2.tick);
51
+ });
52
+
53
+ it("Creates February 29 in leap year", () => {
54
+ const dateOnly = new DateOnly(2024, 2, 29);
55
+
56
+ expect(dateOnly.year).toBe(2024);
57
+ expect(dateOnly.month).toBe(2);
58
+ expect(dateOnly.day).toBe(29);
59
+ expect(dateOnly.isValid).toBe(true);
60
+ });
61
+
62
+ it("Adjusts February 29 to March 1 in non-leap year (JS Date behavior)", () => {
63
+ const dateOnly = new DateOnly(2023, 2, 29);
64
+
65
+ expect(dateOnly.year).toBe(2023);
66
+ expect(dateOnly.month).toBe(3);
67
+ expect(dateOnly.day).toBe(1);
68
+ });
69
+
70
+ it("Adjusts invalid month (13) to January next year (JS Date behavior)", () => {
71
+ const dateOnly = new DateOnly(2024, 13, 1);
72
+
73
+ expect(dateOnly.year).toBe(2025);
74
+ expect(dateOnly.month).toBe(1);
75
+ expect(dateOnly.day).toBe(1);
76
+ });
77
+ });
78
+
79
+ //#endregion
80
+
81
+ //#region parse
82
+
83
+ describe("parse()", () => {
84
+ it("Parses yyyy-MM-dd format", () => {
85
+ const dateOnly = DateOnly.parse("2025-01-06");
86
+
87
+ expect(dateOnly.year).toBe(2025);
88
+ expect(dateOnly.month).toBe(1);
89
+ expect(dateOnly.day).toBe(6);
90
+ });
91
+
92
+ it("Parses yyyyMMdd format", () => {
93
+ const dateOnly = DateOnly.parse("20250106");
94
+
95
+ expect(dateOnly.year).toBe(2025);
96
+ expect(dateOnly.month).toBe(1);
97
+ expect(dateOnly.day).toBe(6);
98
+ });
99
+
100
+ it("Parses ISO 8601 format", () => {
101
+ const dateOnly = DateOnly.parse("2025-01-06T00:00:00Z");
102
+
103
+ expect(dateOnly.year).toBe(2025);
104
+ expect(dateOnly.month).toBe(1);
105
+ expect(dateOnly.day).toBe(6);
106
+ });
107
+
108
+ it("Throws error for invalid format", () => {
109
+ expect(() => DateOnly.parse("invalid-date")).toThrow("Failed to parse date format");
110
+ });
111
+
112
+ it("Parses year-end boundary (December 31)", () => {
113
+ const dateOnly = DateOnly.parse("2024-12-31");
114
+
115
+ expect(dateOnly.year).toBe(2024);
116
+ expect(dateOnly.month).toBe(12);
117
+ expect(dateOnly.day).toBe(31);
118
+ });
119
+
120
+ it("Parses year-start boundary (January 1)", () => {
121
+ const dateOnly = DateOnly.parse("2025-01-01");
122
+
123
+ expect(dateOnly.year).toBe(2025);
124
+ expect(dateOnly.month).toBe(1);
125
+ expect(dateOnly.day).toBe(1);
126
+ });
127
+
128
+ it("Parses February 29 in leap year", () => {
129
+ const dateOnly = DateOnly.parse("2024-02-29");
130
+
131
+ expect(dateOnly.year).toBe(2024);
132
+ expect(dateOnly.month).toBe(2);
133
+ expect(dateOnly.day).toBe(29);
134
+ });
135
+
136
+ it("Parses February 28 in leap year", () => {
137
+ const dateOnly = DateOnly.parse("2024-02-28");
138
+
139
+ expect(dateOnly.year).toBe(2024);
140
+ expect(dateOnly.month).toBe(2);
141
+ expect(dateOnly.day).toBe(28);
142
+ });
143
+ });
144
+
145
+ //#endregion
146
+
147
+ //#region Getters
148
+
149
+ describe("Getters", () => {
150
+ it("Returns year", () => {
151
+ const dateOnly = new DateOnly(2025, 1, 6);
152
+ expect(dateOnly.year).toBe(2025);
153
+ });
154
+
155
+ it("Returns month (1-12)", () => {
156
+ const dateOnly = new DateOnly(2025, 1, 6);
157
+ expect(dateOnly.month).toBe(1);
158
+ });
159
+
160
+ it("Returns day", () => {
161
+ const dateOnly = new DateOnly(2025, 1, 6);
162
+ expect(dateOnly.day).toBe(6);
163
+ });
164
+
165
+ it("Returns tick", () => {
166
+ const dateOnly = new DateOnly(2025, 1, 6);
167
+ expect(dateOnly.tick).toBe(new Date(2025, 0, 6).getTime());
168
+ });
169
+
170
+ it("Returns dayOfWeek (Sunday-Saturday: 0-6)", () => {
171
+ // 2025-01-06 is Monday (1)
172
+ const dateOnly = new DateOnly(2025, 1, 6);
173
+ expect(dateOnly.dayOfWeek).toBe(1);
174
+ });
175
+
176
+ it("Returns isValid", () => {
177
+ const dateOnly = new DateOnly(2025, 1, 6);
178
+ expect(dateOnly.isValid).toBe(true);
179
+ });
180
+
181
+ it("Invalid date returns isValid as false", () => {
182
+ const dateOnly = new DateOnly(NaN);
183
+ expect(dateOnly.isValid).toBe(false);
184
+ });
185
+ });
186
+
187
+ //#endregion
188
+
189
+ //#region setX methods (immutable)
190
+
191
+ describe("setYear()", () => {
192
+ it("Returns new instance with year changed", () => {
193
+ const dateOnly = new DateOnly(2025, 1, 6);
194
+ const newDateOnly = dateOnly.setYear(2026);
195
+
196
+ expect(newDateOnly.year).toBe(2026);
197
+ expect(newDateOnly.month).toBe(1);
198
+ expect(newDateOnly.day).toBe(6);
199
+ expect(dateOnly.year).toBe(2025); // original immutable
200
+ });
201
+
202
+ it("Adjusts February 29 from leap year to non-leap year with setYear", () => {
203
+ const dateOnly = new DateOnly(2024, 2, 29); // 2024 is leap year
204
+ const newDateOnly = dateOnly.setYear(2023); // 2023 is non-leap year
205
+
206
+ expect(newDateOnly.year).toBe(2023);
207
+ expect(newDateOnly.month).toBe(3);
208
+ expect(newDateOnly.day).toBe(1); // February 29 → March 1
209
+ });
210
+ });
211
+
212
+ describe("setMonth()", () => {
213
+ it("Returns new instance with month changed", () => {
214
+ const dateOnly = new DateOnly(2025, 1, 6);
215
+ const newDateOnly = dateOnly.setMonth(2);
216
+
217
+ expect(newDateOnly.year).toBe(2025);
218
+ expect(newDateOnly.month).toBe(2);
219
+ expect(newDateOnly.day).toBe(6);
220
+ expect(dateOnly.month).toBe(1); // original immutable
221
+ });
222
+
223
+ it("Adjusts to last day of month if target month has fewer days", () => {
224
+ // January 31 → February (28 days max)
225
+ const dateOnly = new DateOnly(2025, 1, 31);
226
+ const newDateOnly = dateOnly.setMonth(2);
227
+
228
+ expect(newDateOnly.month).toBe(2);
229
+ expect(newDateOnly.day).toBe(28); // February's last day
230
+ });
231
+
232
+ it("setMonth(13) returns January next year", () => {
233
+ const dateOnly = new DateOnly(2025, 6, 15);
234
+ const result = dateOnly.setMonth(13);
235
+
236
+ expect(result.year).toBe(2026);
237
+ expect(result.month).toBe(1);
238
+ expect(result.day).toBe(15);
239
+ });
240
+
241
+ it("setMonth(0) returns December previous year", () => {
242
+ const dateOnly = new DateOnly(2025, 6, 15);
243
+ const result = dateOnly.setMonth(0);
244
+
245
+ expect(result.year).toBe(2024);
246
+ expect(result.month).toBe(12);
247
+ expect(result.day).toBe(15);
248
+ });
249
+
250
+ it("setMonth(-1) returns November previous year", () => {
251
+ const dateOnly = new DateOnly(2025, 6, 15);
252
+ const result = dateOnly.setMonth(-1);
253
+
254
+ expect(result.year).toBe(2024);
255
+ expect(result.month).toBe(11);
256
+ expect(result.day).toBe(15);
257
+ });
258
+ });
259
+
260
+ describe("setDay()", () => {
261
+ it("Returns new instance with day changed", () => {
262
+ const dateOnly = new DateOnly(2025, 1, 6);
263
+ const newDateOnly = dateOnly.setDay(15);
264
+
265
+ expect(newDateOnly.year).toBe(2025);
266
+ expect(newDateOnly.month).toBe(1);
267
+ expect(newDateOnly.day).toBe(15);
268
+ expect(dateOnly.day).toBe(6); // original immutable
269
+ });
270
+ });
271
+
272
+ //#endregion
273
+
274
+ //#region addX methods (immutable)
275
+
276
+ describe("addYears()", () => {
277
+ it("Adds positive years", () => {
278
+ const dateOnly = new DateOnly(2025, 1, 6);
279
+ const newDateOnly = dateOnly.addYears(2);
280
+
281
+ expect(newDateOnly.year).toBe(2027);
282
+ expect(newDateOnly.month).toBe(1);
283
+ expect(newDateOnly.day).toBe(6);
284
+ });
285
+
286
+ it("Adds negative years (subtraction)", () => {
287
+ const dateOnly = new DateOnly(2025, 1, 6);
288
+ const newDateOnly = dateOnly.addYears(-1);
289
+
290
+ expect(newDateOnly.year).toBe(2024);
291
+ });
292
+ });
293
+
294
+ describe("addMonths()", () => {
295
+ it("Adds positive months", () => {
296
+ const dateOnly = new DateOnly(2025, 1, 6);
297
+ const newDateOnly = dateOnly.addMonths(3);
298
+
299
+ expect(newDateOnly.year).toBe(2025);
300
+ expect(newDateOnly.month).toBe(4);
301
+ expect(newDateOnly.day).toBe(6);
302
+ });
303
+
304
+ it("Adds negative months (subtraction)", () => {
305
+ const dateOnly = new DateOnly(2025, 3, 6);
306
+ const newDateOnly = dateOnly.addMonths(-2);
307
+
308
+ expect(newDateOnly.month).toBe(1);
309
+ });
310
+
311
+ it("Handles year boundary when adding months", () => {
312
+ const dateOnly = new DateOnly(2025, 11, 6);
313
+ const newDateOnly = dateOnly.addMonths(3);
314
+
315
+ expect(newDateOnly.year).toBe(2026);
316
+ expect(newDateOnly.month).toBe(2);
317
+ });
318
+ });
319
+
320
+ describe("addDays()", () => {
321
+ it("Adds positive days", () => {
322
+ const dateOnly = new DateOnly(2025, 1, 6);
323
+ const newDateOnly = dateOnly.addDays(10);
324
+
325
+ expect(newDateOnly.year).toBe(2025);
326
+ expect(newDateOnly.month).toBe(1);
327
+ expect(newDateOnly.day).toBe(16);
328
+ });
329
+
330
+ it("Adds negative days (subtraction)", () => {
331
+ const dateOnly = new DateOnly(2025, 1, 16);
332
+ const newDateOnly = dateOnly.addDays(-10);
333
+
334
+ expect(newDateOnly.day).toBe(6);
335
+ });
336
+
337
+ it("Handles month boundary when adding days", () => {
338
+ const dateOnly = new DateOnly(2025, 1, 31);
339
+ const newDateOnly = dateOnly.addDays(1);
340
+
341
+ expect(newDateOnly.month).toBe(2);
342
+ expect(newDateOnly.day).toBe(1);
343
+ });
344
+ });
345
+
346
+ //#endregion
347
+
348
+ //#region Formatting
349
+
350
+ describe("toFormatString()", () => {
351
+ it("Formats to yyyy-MM-dd format", () => {
352
+ const dateOnly = new DateOnly(2025, 1, 6);
353
+ expect(dateOnly.toFormatString("yyyy-MM-dd")).toBe("2025-01-06");
354
+ });
355
+
356
+ it("Formats to yyyyMMdd format", () => {
357
+ const dateOnly = new DateOnly(2025, 1, 6);
358
+ expect(dateOnly.toFormatString("yyyyMMdd")).toBe("20250106");
359
+ });
360
+
361
+ it("Formats with Korean date format pattern (yyyy year M month d day)", () => {
362
+ const dateOnly = new DateOnly(2025, 1, 6);
363
+ expect(dateOnly.toFormatString("yyyy년 M월 d일")).toBe("2025년 1월 6일");
364
+ });
365
+
366
+ it("Formats with day of week", () => {
367
+ // 2025-01-06 is Monday
368
+ const dateOnly = new DateOnly(2025, 1, 6);
369
+ expect(dateOnly.toFormatString("yyyy-MM-dd (ddd)")).toBe("2025-01-06 (월)");
370
+ });
371
+ });
372
+
373
+ describe("toString()", () => {
374
+ it("Returns default format yyyy-MM-dd", () => {
375
+ const dateOnly = new DateOnly(2025, 1, 6);
376
+ expect(dateOnly.toString()).toBe("2025-01-06");
377
+ });
378
+ });
379
+
380
+ //#endregion
381
+
382
+ //#region tick comparison
383
+
384
+ describe("tick comparison", () => {
385
+ it("Same dates have same tick", () => {
386
+ const d1 = new DateOnly(2025, 3, 15);
387
+ const d2 = new DateOnly(2025, 3, 15);
388
+
389
+ expect(d1.tick).toBe(d2.tick);
390
+ });
391
+
392
+ it("Different dates have different ticks", () => {
393
+ const d1 = new DateOnly(2025, 3, 15);
394
+ const d2 = new DateOnly(2025, 3, 16);
395
+
396
+ expect(d1.tick).not.toBe(d2.tick);
397
+ });
398
+
399
+ it("Can compare date order by tick", () => {
400
+ const d1 = new DateOnly(2025, 1, 1);
401
+ const d2 = new DateOnly(2025, 6, 15);
402
+ const d3 = new DateOnly(2025, 12, 31);
403
+
404
+ expect(d1.tick).toBeLessThan(d2.tick);
405
+ expect(d2.tick).toBeLessThan(d3.tick);
406
+ });
407
+
408
+ it("Can compare dates with different years by tick", () => {
409
+ const d2024 = new DateOnly(2024, 12, 31);
410
+ const d2025 = new DateOnly(2025, 1, 1);
411
+
412
+ expect(d2024.tick).toBeLessThan(d2025.tick);
413
+ });
414
+ });
415
+
416
+ //#endregion
417
+
418
+ //#endregion
419
+
420
+ //#region Week calculation
421
+
422
+ describe("getWeekSeqOfYear()", () => {
423
+ describe("ISO 8601 standard (Monday start, first week min 4 days)", () => {
424
+ it("returns week sequence in middle of year", () => {
425
+ // 2025-01-06 (Monday)
426
+ const dateOnly = new DateOnly(2025, 1, 6);
427
+ const result = dateOnly.getWeekSeqOfYear();
428
+
429
+ expect(result.year).toBe(2025);
430
+ expect(result.weekSeq).toBe(2);
431
+ });
432
+
433
+ it("handles year-start when within week 1 of current year", () => {
434
+ // 2025-01-01 (Wednesday) - ISO 8601, January 2 (Thursday) is in same week, so 2025 week 1
435
+ const dateOnly = new DateOnly(2025, 1, 1);
436
+ const result = dateOnly.getWeekSeqOfYear();
437
+
438
+ expect(result.year).toBe(2025);
439
+ expect(result.weekSeq).toBe(1);
440
+ });
441
+
442
+ it("handles year-end when belonging to next year's week", () => {
443
+ // 2024-12-30 (Monday) - Same week has 2025 January 2 (Thursday), so 2025 week 1
444
+ const dateOnly = new DateOnly(2024, 12, 30);
445
+ const result = dateOnly.getWeekSeqOfYear();
446
+
447
+ expect(result.year).toBe(2025);
448
+ expect(result.weekSeq).toBe(1);
449
+ });
450
+ });
451
+
452
+ describe("US style (Sunday start, first week min 1 day)", () => {
453
+ it("year's first day belongs to week 1", () => {
454
+ // 2025-01-01 (Wednesday)
455
+ const dateOnly = new DateOnly(2025, 1, 1);
456
+ const result = dateOnly.getWeekSeqOfYear(0, 1);
457
+
458
+ expect(result.year).toBe(2025);
459
+ expect(result.weekSeq).toBe(1);
460
+ });
461
+
462
+ it("returns week sequence in middle of year", () => {
463
+ // 2025-01-12 (Sunday) - US style week 3 start
464
+ const dateOnly = new DateOnly(2025, 1, 12);
465
+ const result = dateOnly.getWeekSeqOfYear(0, 1);
466
+
467
+ expect(result.year).toBe(2025);
468
+ expect(result.weekSeq).toBe(3);
469
+ });
470
+ });
471
+
472
+ describe("Leap year handling", () => {
473
+ it("handles February 29 in leap year", () => {
474
+ // 2024 is leap year, 2024-02-29 (Thursday)
475
+ const dateOnly = new DateOnly(2024, 2, 29);
476
+ const result = dateOnly.getWeekSeqOfYear();
477
+
478
+ expect(result.year).toBe(2024);
479
+ expect(result.weekSeq).toBe(9);
480
+ });
481
+ });
482
+ });
483
+
484
+ describe("getWeekSeqOfMonth()", () => {
485
+ describe("ISO 8601 standard (Monday start, first week min 4 days)", () => {
486
+ it("returns week sequence in middle of month", () => {
487
+ // 2025-01-15 (Wednesday)
488
+ const dateOnly = new DateOnly(2025, 1, 15);
489
+ const result = dateOnly.getWeekSeqOfMonth();
490
+
491
+ expect(result.year).toBe(2025);
492
+ expect(result.monthSeq).toBe(1);
493
+ expect(result.weekSeq).toBe(3);
494
+ });
495
+
496
+ it("handles month-start when belonging to previous month's week", () => {
497
+ // 2025-02-01 (Saturday) - Belongs to January's last week
498
+ const dateOnly = new DateOnly(2025, 2, 1);
499
+ const result = dateOnly.getWeekSeqOfMonth();
500
+
501
+ // February 1 is Saturday, doesn't meet 4-day minimum, so January week
502
+ expect(result.monthSeq).toBe(1);
503
+ });
504
+
505
+ it("handles month-end when potentially belonging to next month's week", () => {
506
+ // 2025-01-30 (Thursday) - Can belong to February week
507
+ const dateOnly = new DateOnly(2025, 1, 30);
508
+ const result = dateOnly.getWeekSeqOfMonth();
509
+
510
+ expect(result.year).toBe(2025);
511
+ });
512
+ });
513
+
514
+ describe("US style (Sunday start, first week min 1 day)", () => {
515
+ it("month's first day belongs to week 1", () => {
516
+ // 2025-01-01 (Wednesday)
517
+ const dateOnly = new DateOnly(2025, 1, 1);
518
+ const result = dateOnly.getWeekSeqOfMonth(0, 1);
519
+
520
+ expect(result.year).toBe(2025);
521
+ expect(result.monthSeq).toBe(1);
522
+ expect(result.weekSeq).toBe(1);
523
+ });
524
+ });
525
+ });
526
+
527
+ describe("getBaseYearMonthSeqForWeekSeq()", () => {
528
+ it("returns current year-month for general date", () => {
529
+ const dateOnly = new DateOnly(2025, 1, 15);
530
+ const result = dateOnly.getBaseYearMonthSeqForWeekSeq();
531
+
532
+ expect(result.year).toBe(2025);
533
+ expect(result.monthSeq).toBe(1);
534
+ });
535
+
536
+ it("can return previous month at month boundary", () => {
537
+ // May vary based on week start day
538
+ const dateOnly = new DateOnly(2025, 2, 1);
539
+ const result = dateOnly.getBaseYearMonthSeqForWeekSeq();
540
+
541
+ // 2025-02-01 is Saturday, so may belong to January week
542
+ expect(result.year).toBe(2025);
543
+ });
544
+ });
545
+
546
+ describe("getWeekSeqStartDate()", () => {
547
+ describe("ISO 8601 standard (Monday start)", () => {
548
+ it("returns week start date (Monday)", () => {
549
+ // 2025-01-08 (Wednesday)
550
+ const dateOnly = new DateOnly(2025, 1, 8);
551
+ const result = dateOnly.getWeekSeqStartDate();
552
+
553
+ expect(result.year).toBe(2025);
554
+ expect(result.month).toBe(1);
555
+ expect(result.day).toBe(6); // Monday
556
+ expect(result.dayOfWeek).toBe(1);
557
+ });
558
+
559
+ it("returns same date if already Monday", () => {
560
+ // 2025-01-06 (Monday)
561
+ const dateOnly = new DateOnly(2025, 1, 6);
562
+ const result = dateOnly.getWeekSeqStartDate();
563
+
564
+ expect(result.day).toBe(6);
565
+ });
566
+ });
567
+
568
+ describe("US style (Sunday start)", () => {
569
+ it("returns week start date (Sunday)", () => {
570
+ // 2025-01-08 (Wednesday)
571
+ const dateOnly = new DateOnly(2025, 1, 8);
572
+ const result = dateOnly.getWeekSeqStartDate(0, 1);
573
+
574
+ expect(result.year).toBe(2025);
575
+ expect(result.month).toBe(1);
576
+ expect(result.day).toBe(5); // Sunday
577
+ expect(result.dayOfWeek).toBe(0);
578
+ });
579
+ });
580
+ });
581
+
582
+ describe("getDateByYearWeekSeq()", () => {
583
+ describe("ISO 8601 standard", () => {
584
+ it("returns start date from year week sequence", () => {
585
+ // 2025 week 2
586
+ const result = DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 2 });
587
+
588
+ expect(result.year).toBe(2025);
589
+ expect(result.month).toBe(1);
590
+ expect(result.day).toBe(6); // 2025-01-06 (Monday)
591
+ });
592
+
593
+ it("returns start date from year-month week sequence", () => {
594
+ // 2025 January week 3
595
+ const result = DateOnly.getDateByYearWeekSeq({ year: 2025, month: 1, weekSeq: 3 });
596
+
597
+ expect(result.year).toBe(2025);
598
+ expect(result.month).toBe(1);
599
+ expect(result.day).toBe(13); // 2025-01-13 (Monday)
600
+ });
601
+ });
602
+
603
+ describe("US style", () => {
604
+ it("returns start date from year week sequence", () => {
605
+ // 2025 week 1 (US style)
606
+ const result = DateOnly.getDateByYearWeekSeq({ year: 2025, weekSeq: 1 }, 0, 1);
607
+
608
+ expect(result.year).toBe(2024);
609
+ expect(result.month).toBe(12);
610
+ expect(result.day).toBe(29); // 2024-12-29 (Sunday)
611
+ });
612
+ });
613
+
614
+ describe("Year boundary handling", () => {
615
+ it("handles year with 53 weeks", () => {
616
+ // 2020 has 53 weeks (ISO 8601)
617
+ const result = DateOnly.getDateByYearWeekSeq({ year: 2020, weekSeq: 53 });
618
+
619
+ expect(result.year).toBe(2020);
620
+ expect(result.month).toBe(12);
621
+ expect(result.day).toBe(28); // 2020-12-28 (Monday)
622
+ });
623
+ });
624
+
625
+ describe("Leap year handling", () => {
626
+ it("correctly calculates week for leap year", () => {
627
+ // 2024 (leap year) week 10
628
+ const result = DateOnly.getDateByYearWeekSeq({ year: 2024, weekSeq: 10 });
629
+
630
+ expect(result.year).toBe(2024);
631
+ expect(result.month).toBe(3);
632
+ expect(result.day).toBe(4); // 2024-03-04 (Monday)
633
+ });
634
+ });
635
+ });
636
+
637
+ //#endregion
638
+ });