@schafevormfenster/rest-commons 0.1.1

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 (264) hide show
  1. package/CONTRIBUTING.md +1190 -0
  2. package/README.md +275 -0
  3. package/bin/setup.js +10 -0
  4. package/dist/api-schemas/error.schema.d.ts +20 -0
  5. package/dist/api-schemas/error.schema.d.ts.map +1 -0
  6. package/dist/api-schemas/error.schema.js +17 -0
  7. package/dist/api-schemas/health.schema.d.ts +497 -0
  8. package/dist/api-schemas/health.schema.d.ts.map +1 -0
  9. package/dist/api-schemas/health.schema.js +33 -0
  10. package/dist/api-schemas/okay.schema.d.ts +13 -0
  11. package/dist/api-schemas/okay.schema.d.ts.map +1 -0
  12. package/dist/api-schemas/okay.schema.js +5 -0
  13. package/dist/api-schemas/paginated-results.schema.d.ts +59 -0
  14. package/dist/api-schemas/paginated-results.schema.d.ts.map +1 -0
  15. package/dist/api-schemas/paginated-results.schema.js +10 -0
  16. package/dist/api-schemas/partial-results.schema.d.ts +30 -0
  17. package/dist/api-schemas/partial-results.schema.d.ts.map +1 -0
  18. package/dist/api-schemas/partial-results.schema.js +10 -0
  19. package/dist/api-schemas/result.schema.d.ts +17 -0
  20. package/dist/api-schemas/result.schema.d.ts.map +1 -0
  21. package/dist/api-schemas/result.schema.js +5 -0
  22. package/dist/api-schemas/results.schema.d.ts +21 -0
  23. package/dist/api-schemas/results.schema.d.ts.map +1 -0
  24. package/dist/api-schemas/results.schema.js +5 -0
  25. package/dist/helpers/correlation/get-correlation-id.d.ts +7 -0
  26. package/dist/helpers/correlation/get-correlation-id.d.ts.map +1 -0
  27. package/dist/helpers/correlation/get-correlation-id.js +16 -0
  28. package/dist/helpers/correlation/get-header.d.ts +7 -0
  29. package/dist/helpers/correlation/get-header.d.ts.map +1 -0
  30. package/dist/helpers/correlation/get-header.js +11 -0
  31. package/dist/helpers/detect-mime-type.d.ts +11 -0
  32. package/dist/helpers/detect-mime-type.d.ts.map +1 -0
  33. package/dist/helpers/detect-mime-type.js +40 -0
  34. package/dist/helpers/detect-suspicious-patterns.d.ts +8 -0
  35. package/dist/helpers/detect-suspicious-patterns.d.ts.map +1 -0
  36. package/dist/helpers/detect-suspicious-patterns.js +55 -0
  37. package/dist/helpers/eventify-constants.types.d.ts +32 -0
  38. package/dist/helpers/eventify-constants.types.d.ts.map +1 -0
  39. package/dist/helpers/eventify-constants.types.js +40 -0
  40. package/dist/helpers/hash-binary.d.ts +21 -0
  41. package/dist/helpers/hash-binary.d.ts.map +1 -0
  42. package/dist/helpers/hash-binary.js +28 -0
  43. package/dist/helpers/mime-types/detect-image-mime-type.d.ts +5 -0
  44. package/dist/helpers/mime-types/detect-image-mime-type.d.ts.map +1 -0
  45. package/dist/helpers/mime-types/detect-image-mime-type.js +41 -0
  46. package/dist/helpers/mime-types/detect-ole-mime-type.d.ts +6 -0
  47. package/dist/helpers/mime-types/detect-ole-mime-type.d.ts.map +1 -0
  48. package/dist/helpers/mime-types/detect-ole-mime-type.js +34 -0
  49. package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts +5 -0
  50. package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts.map +1 -0
  51. package/dist/helpers/mime-types/detect-pdf-mime-type.js +13 -0
  52. package/dist/helpers/mime-types/detect-zip-mime-type.d.ts +6 -0
  53. package/dist/helpers/mime-types/detect-zip-mime-type.d.ts.map +1 -0
  54. package/dist/helpers/mime-types/detect-zip-mime-type.js +23 -0
  55. package/dist/helpers/parameter-validation.d.ts +6 -0
  56. package/dist/helpers/parameter-validation.d.ts.map +1 -0
  57. package/dist/helpers/parameter-validation.js +19 -0
  58. package/dist/helpers/parameter-validation.types.d.ts +16 -0
  59. package/dist/helpers/parameter-validation.types.d.ts.map +1 -0
  60. package/dist/helpers/parameter-validation.types.js +38 -0
  61. package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts +6 -0
  62. package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts.map +1 -0
  63. package/dist/helpers/response-headers/build-api-unauthorized-headers.js +23 -0
  64. package/dist/helpers/response-headers/environment.types.d.ts +2 -0
  65. package/dist/helpers/response-headers/environment.types.d.ts.map +1 -0
  66. package/dist/helpers/response-headers/environment.types.js +1 -0
  67. package/dist/helpers/response-headers/resolve-environment.d.ts +8 -0
  68. package/dist/helpers/response-headers/resolve-environment.d.ts.map +1 -0
  69. package/dist/helpers/response-headers/resolve-environment.js +18 -0
  70. package/dist/helpers/slugify.d.ts +15 -0
  71. package/dist/helpers/slugify.d.ts.map +1 -0
  72. package/dist/helpers/slugify.js +32 -0
  73. package/dist/index.d.ts +36 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +41 -0
  76. package/dist/normalization/normalize-list.d.ts +11 -0
  77. package/dist/normalization/normalize-list.d.ts.map +1 -0
  78. package/dist/normalization/normalize-list.js +19 -0
  79. package/dist/normalization/normalize-location.d.ts +16 -0
  80. package/dist/normalization/normalize-location.d.ts.map +1 -0
  81. package/dist/normalization/normalize-location.js +26 -0
  82. package/dist/primitives/coordinate-precision.d.ts +10 -0
  83. package/dist/primitives/coordinate-precision.d.ts.map +1 -0
  84. package/dist/primitives/coordinate-precision.js +27 -0
  85. package/dist/primitives/geo-point.schema.d.ts +8 -0
  86. package/dist/primitives/geo-point.schema.d.ts.map +1 -0
  87. package/dist/primitives/geo-point.schema.js +10 -0
  88. package/dist/primitives/geoname-id.schema.d.ts +8 -0
  89. package/dist/primitives/geoname-id.schema.d.ts.map +1 -0
  90. package/dist/primitives/geoname-id.schema.js +9 -0
  91. package/dist/primitives/international-zip.schema.d.ts +76 -0
  92. package/dist/primitives/international-zip.schema.d.ts.map +1 -0
  93. package/dist/primitives/international-zip.schema.js +81 -0
  94. package/dist/primitives/latitude.schema.d.ts +9 -0
  95. package/dist/primitives/latitude.schema.d.ts.map +1 -0
  96. package/dist/primitives/latitude.schema.js +13 -0
  97. package/dist/primitives/location.schema.d.ts +8 -0
  98. package/dist/primitives/location.schema.d.ts.map +1 -0
  99. package/dist/primitives/location.schema.js +15 -0
  100. package/dist/primitives/longitude.schema.d.ts +9 -0
  101. package/dist/primitives/longitude.schema.d.ts.map +1 -0
  102. package/dist/primitives/longitude.schema.js +13 -0
  103. package/dist/primitives/numeric-id.schema.d.ts +8 -0
  104. package/dist/primitives/numeric-id.schema.d.ts.map +1 -0
  105. package/dist/primitives/numeric-id.schema.js +10 -0
  106. package/dist/primitives/slug.schema.d.ts +17 -0
  107. package/dist/primitives/slug.schema.d.ts.map +1 -0
  108. package/dist/primitives/slug.schema.js +30 -0
  109. package/dist/primitives/uuid.schema.d.ts +8 -0
  110. package/dist/primitives/uuid.schema.d.ts.map +1 -0
  111. package/dist/primitives/uuid.schema.js +9 -0
  112. package/dist/primitives/wikidata-id.schema.d.ts +9 -0
  113. package/dist/primitives/wikidata-id.schema.d.ts.map +1 -0
  114. package/dist/primitives/wikidata-id.schema.js +10 -0
  115. package/dist/time/boundary-enforcement.d.ts +11 -0
  116. package/dist/time/boundary-enforcement.d.ts.map +1 -0
  117. package/dist/time/boundary-enforcement.js +43 -0
  118. package/dist/time/bounded-time.schema.d.ts +31 -0
  119. package/dist/time/bounded-time.schema.d.ts.map +1 -0
  120. package/dist/time/bounded-time.schema.js +77 -0
  121. package/dist/time/flexible-time-parser.d.ts +12 -0
  122. package/dist/time/flexible-time-parser.d.ts.map +1 -0
  123. package/dist/time/flexible-time-parser.js +94 -0
  124. package/dist/time/flexible-time.schema.d.ts +31 -0
  125. package/dist/time/flexible-time.schema.d.ts.map +1 -0
  126. package/dist/time/flexible-time.schema.js +31 -0
  127. package/dist/time/get-week-end.d.ts +10 -0
  128. package/dist/time/get-week-end.d.ts.map +1 -0
  129. package/dist/time/get-week-end.js +25 -0
  130. package/dist/time/get-week-start.d.ts +10 -0
  131. package/dist/time/get-week-start.d.ts.map +1 -0
  132. package/dist/time/get-week-start.js +25 -0
  133. package/dist/time/is-relative-time.d.ts +8 -0
  134. package/dist/time/is-relative-time.d.ts.map +1 -0
  135. package/dist/time/is-relative-time.js +9 -0
  136. package/dist/time/iso8601.schema.d.ts +14 -0
  137. package/dist/time/iso8601.schema.d.ts.map +1 -0
  138. package/dist/time/iso8601.schema.js +17 -0
  139. package/dist/time/iso8601.types.d.ts +6 -0
  140. package/dist/time/iso8601.types.d.ts.map +1 -0
  141. package/dist/time/iso8601.types.js +11 -0
  142. package/dist/time/parse-relative-time.d.ts +9 -0
  143. package/dist/time/parse-relative-time.d.ts.map +1 -0
  144. package/dist/time/parse-relative-time.js +36 -0
  145. package/dist/time/relative-time.schema.d.ts +23 -0
  146. package/dist/time/relative-time.schema.d.ts.map +1 -0
  147. package/dist/time/relative-time.schema.js +25 -0
  148. package/dist/time/since-parameter.schema.d.ts +8 -0
  149. package/dist/time/since-parameter.schema.d.ts.map +1 -0
  150. package/dist/time/since-parameter.schema.js +56 -0
  151. package/dist/time/time-helpers.d.ts +19 -0
  152. package/dist/time/time-helpers.d.ts.map +1 -0
  153. package/dist/time/time-helpers.js +56 -0
  154. package/dist/time/time-schemas.d.ts +20 -0
  155. package/dist/time/time-schemas.d.ts.map +1 -0
  156. package/dist/time/time-schemas.js +25 -0
  157. package/dist/time/timezone.types.d.ts +17 -0
  158. package/dist/time/timezone.types.d.ts.map +1 -0
  159. package/dist/time/timezone.types.js +15 -0
  160. package/dist/validation/zod-error-handler.d.ts +3 -0
  161. package/dist/validation/zod-error-handler.d.ts.map +1 -0
  162. package/dist/validation/zod-error-handler.js +189 -0
  163. package/dist/validation/zod-utils.d.ts +9 -0
  164. package/dist/validation/zod-utils.d.ts.map +1 -0
  165. package/dist/validation/zod-utils.js +23 -0
  166. package/eslint.config.mjs +16 -0
  167. package/package.json +44 -0
  168. package/src/api-schemas/error.schema.test.ts +27 -0
  169. package/src/api-schemas/error.schema.ts +23 -0
  170. package/src/api-schemas/health.schema.test.ts +104 -0
  171. package/src/api-schemas/health.schema.ts +63 -0
  172. package/src/api-schemas/okay.schema.test.ts +15 -0
  173. package/src/api-schemas/okay.schema.ts +8 -0
  174. package/src/api-schemas/paginated-results.schema.ts +17 -0
  175. package/src/api-schemas/partial-results.schema.ts +13 -0
  176. package/src/api-schemas/result.schema.test.ts +19 -0
  177. package/src/api-schemas/result.schema.ts +9 -0
  178. package/src/api-schemas/results.schema.test.ts +15 -0
  179. package/src/api-schemas/results.schema.ts +9 -0
  180. package/src/helpers/correlation/get-correlation-id.test.ts +126 -0
  181. package/src/helpers/correlation/get-correlation-id.ts +22 -0
  182. package/src/helpers/correlation/get-header.test.ts +179 -0
  183. package/src/helpers/correlation/get-header.ts +21 -0
  184. package/src/helpers/detect-mime-type.test.ts +100 -0
  185. package/src/helpers/detect-mime-type.ts +46 -0
  186. package/src/helpers/detect-suspicious-patterns.test.ts +45 -0
  187. package/src/helpers/detect-suspicious-patterns.ts +57 -0
  188. package/src/helpers/eventify-constants.test.ts +52 -0
  189. package/src/helpers/eventify-constants.types.test.ts +52 -0
  190. package/src/helpers/eventify-constants.types.ts +51 -0
  191. package/src/helpers/hash-binary.test.ts +60 -0
  192. package/src/helpers/hash-binary.ts +30 -0
  193. package/src/helpers/mime-types/detect-image-mime-type.test.ts +73 -0
  194. package/src/helpers/mime-types/detect-image-mime-type.ts +50 -0
  195. package/src/helpers/mime-types/detect-ole-mime-type.test.ts +86 -0
  196. package/src/helpers/mime-types/detect-ole-mime-type.ts +44 -0
  197. package/src/helpers/mime-types/detect-pdf-mime-type.test.ts +39 -0
  198. package/src/helpers/mime-types/detect-pdf-mime-type.ts +15 -0
  199. package/src/helpers/mime-types/detect-zip-mime-type.test.ts +88 -0
  200. package/src/helpers/mime-types/detect-zip-mime-type.ts +28 -0
  201. package/src/helpers/parameter-validation.test.ts +35 -0
  202. package/src/helpers/parameter-validation.ts +32 -0
  203. package/src/helpers/process-eventify-request.ts +146 -0
  204. package/src/helpers/response-headers/build-api-unauthorized-headers.ts +30 -0
  205. package/src/helpers/response-headers/environment.types.ts +1 -0
  206. package/src/helpers/response-headers/resolve-environment.ts +17 -0
  207. package/src/helpers/slugify.test.ts +77 -0
  208. package/src/helpers/slugify.ts +34 -0
  209. package/src/index.ts +46 -0
  210. package/src/normalization/normalize-list.test.ts +43 -0
  211. package/src/normalization/normalize-list.ts +21 -0
  212. package/src/normalization/normalize-location.test.ts +91 -0
  213. package/src/normalization/normalize-location.ts +29 -0
  214. package/src/primitives/coordinate-precision.test.ts +46 -0
  215. package/src/primitives/coordinate-precision.ts +30 -0
  216. package/src/primitives/geo-point.schema.test.ts +70 -0
  217. package/src/primitives/geo-point.schema.ts +14 -0
  218. package/src/primitives/geoname-id.schema.test.ts +60 -0
  219. package/src/primitives/geoname-id.schema.ts +12 -0
  220. package/src/primitives/international-zip.schema.test.ts +212 -0
  221. package/src/primitives/international-zip.schema.ts +103 -0
  222. package/src/primitives/latitude.schema.test.ts +77 -0
  223. package/src/primitives/latitude.schema.ts +20 -0
  224. package/src/primitives/location.schema.test.ts +21 -0
  225. package/src/primitives/location.schema.ts +22 -0
  226. package/src/primitives/longitude.schema.test.ts +77 -0
  227. package/src/primitives/longitude.schema.ts +20 -0
  228. package/src/primitives/numeric-id.schema.test.ts +32 -0
  229. package/src/primitives/numeric-id.schema.ts +13 -0
  230. package/src/primitives/slug.schema.test.ts +101 -0
  231. package/src/primitives/slug.schema.ts +41 -0
  232. package/src/primitives/uuid.schema.test.ts +45 -0
  233. package/src/primitives/uuid.schema.ts +12 -0
  234. package/src/primitives/wikidata-id.schema.test.ts +51 -0
  235. package/src/primitives/wikidata-id.schema.ts +16 -0
  236. package/src/time/README.md +220 -0
  237. package/src/time/boundary-enforcement.test.ts +130 -0
  238. package/src/time/boundary-enforcement.ts +59 -0
  239. package/src/time/bounded-time.schema.test.ts +294 -0
  240. package/src/time/bounded-time.schema.ts +111 -0
  241. package/src/time/flexible-time-parser.test.ts +586 -0
  242. package/src/time/flexible-time-parser.ts +122 -0
  243. package/src/time/flexible-time.schema.test.ts +243 -0
  244. package/src/time/flexible-time.schema.ts +43 -0
  245. package/src/time/is-relative-time.test.ts +23 -0
  246. package/src/time/is-relative-time.ts +9 -0
  247. package/src/time/iso8601.schema.ts +29 -0
  248. package/src/time/iso8601.types.test.ts +112 -0
  249. package/src/time/iso8601.types.ts +21 -0
  250. package/src/time/parse-relative-time.test.ts +49 -0
  251. package/src/time/parse-relative-time.ts +50 -0
  252. package/src/time/relative-time.schema.test.ts +23 -0
  253. package/src/time/relative-time.schema.ts +38 -0
  254. package/src/time/since-parameter.schema.test.ts +59 -0
  255. package/src/time/since-parameter.schema.ts +69 -0
  256. package/src/time/time-helpers.test.ts +263 -0
  257. package/src/time/time-helpers.ts +78 -0
  258. package/src/time/time-schemas.test.ts +181 -0
  259. package/src/time/time-schemas.ts +42 -0
  260. package/src/time/time.schema.test.ts +237 -0
  261. package/src/time/timezone-independence.test.ts +188 -0
  262. package/src/time/timezone.types.test.ts +55 -0
  263. package/src/time/timezone.types.ts +22 -0
  264. package/tsconfig.json +26 -0
@@ -0,0 +1,586 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+
3
+ import {
4
+ parseFlexibleTime,
5
+ createFlexibleTimeParser,
6
+ } from "./flexible-time-parser";
7
+
8
+ describe("flexible-time-parser", () => {
9
+ beforeEach(() => {
10
+ vi.useFakeTimers();
11
+ vi.setSystemTime(new Date("2024-01-15T12:00:00.000Z"));
12
+ });
13
+
14
+ afterEach(() => {
15
+ vi.useRealTimers();
16
+ });
17
+
18
+ describe("parseFlexibleTime", () => {
19
+ const baseTime = new Date("2024-01-15T12:00:00.000Z");
20
+
21
+ describe("ISO 8601 parsing", () => {
22
+ it("should parse ISO 8601 dates unchanged", () => {
23
+ const isoDate = "2024-01-01T10:00:00.000Z";
24
+ expect(parseFlexibleTime(isoDate, baseTime)).toBe(isoDate);
25
+ });
26
+
27
+ it("should preserve different ISO 8601 formats", () => {
28
+ expect(parseFlexibleTime("2024-12-31T23:59:59.999Z", baseTime)).toBe(
29
+ "2024-12-31T23:59:59.999Z"
30
+ );
31
+ expect(parseFlexibleTime("2024-06-15T12:30:45Z", baseTime)).toBe(
32
+ "2024-06-15T12:30:45.000Z"
33
+ );
34
+ });
35
+
36
+ it("should handle ISO 8601 with timezone offsets", () => {
37
+ // ISO 8601 with timezone offset gets normalized to UTC
38
+ const result = parseFlexibleTime("2024-01-15T13:00:00+01:00", baseTime);
39
+ expect(result).toBe("2024-01-15T12:00:00.000Z"); // +01:00 converts to UTC
40
+ });
41
+ });
42
+
43
+ describe("literal parsing", () => {
44
+ it("should parse 'now' to current datetime", () => {
45
+ expect(parseFlexibleTime("now", baseTime)).toBe(
46
+ "2024-01-15T12:00:00.000Z"
47
+ );
48
+ });
49
+
50
+ it("should parse 'today' to current day at midnight UTC", () => {
51
+ expect(parseFlexibleTime("today", baseTime)).toBe(
52
+ "2024-01-15T00:00:00.000Z"
53
+ );
54
+ });
55
+
56
+ it("should handle 'today' for different times of day", () => {
57
+ const morning = new Date("2024-01-15T03:30:45.123Z");
58
+ expect(parseFlexibleTime("today", morning)).toBe(
59
+ "2024-01-15T00:00:00.000Z"
60
+ );
61
+
62
+ const evening = new Date("2024-01-15T23:59:59.999Z");
63
+ expect(parseFlexibleTime("today", evening)).toBe(
64
+ "2024-01-15T00:00:00.000Z"
65
+ );
66
+ });
67
+
68
+ it("should handle 'today' across different dates", () => {
69
+ const date1 = new Date("2024-03-20T15:00:00.000Z");
70
+ expect(parseFlexibleTime("today", date1)).toBe(
71
+ "2024-03-20T00:00:00.000Z"
72
+ );
73
+
74
+ const date2 = new Date("2024-12-25T08:30:00.000Z");
75
+ expect(parseFlexibleTime("today", date2)).toBe(
76
+ "2024-12-25T00:00:00.000Z"
77
+ );
78
+ });
79
+
80
+ it("should parse 'tomorrow' as shortcut for 1d", () => {
81
+ expect(parseFlexibleTime("tomorrow", baseTime)).toBe(
82
+ "2024-01-16T12:00:00.000Z"
83
+ );
84
+ });
85
+
86
+ it("should parse 'yesterday' as shortcut for -1d", () => {
87
+ expect(parseFlexibleTime("yesterday", baseTime)).toBe(
88
+ "2024-01-14T12:00:00.000Z"
89
+ );
90
+ });
91
+ });
92
+
93
+ describe("relative time parsing", () => {
94
+ it("should parse seconds correctly (future)", () => {
95
+ expect(parseFlexibleTime("30s", baseTime)).toBe(
96
+ "2024-01-15T12:00:30.000Z"
97
+ );
98
+ expect(parseFlexibleTime("1s", baseTime)).toBe(
99
+ "2024-01-15T12:00:01.000Z"
100
+ );
101
+ });
102
+
103
+ it("should parse minutes correctly (future)", () => {
104
+ expect(parseFlexibleTime("1m", baseTime)).toBe(
105
+ "2024-01-15T12:01:00.000Z"
106
+ );
107
+ expect(parseFlexibleTime("30m", baseTime)).toBe(
108
+ "2024-01-15T12:30:00.000Z"
109
+ );
110
+ });
111
+
112
+ it("should parse hours correctly (future)", () => {
113
+ expect(parseFlexibleTime("1h", baseTime)).toBe(
114
+ "2024-01-15T13:00:00.000Z"
115
+ );
116
+ expect(parseFlexibleTime("2h", baseTime)).toBe(
117
+ "2024-01-15T14:00:00.000Z"
118
+ );
119
+ expect(parseFlexibleTime("12h", baseTime)).toBe(
120
+ "2024-01-16T00:00:00.000Z"
121
+ );
122
+ });
123
+
124
+ it("should parse days correctly (future)", () => {
125
+ expect(parseFlexibleTime("1d", baseTime)).toBe(
126
+ "2024-01-16T12:00:00.000Z"
127
+ );
128
+ expect(parseFlexibleTime("7d", baseTime)).toBe(
129
+ "2024-01-22T12:00:00.000Z"
130
+ );
131
+ expect(parseFlexibleTime("30d", baseTime)).toBe(
132
+ "2024-02-14T12:00:00.000Z"
133
+ );
134
+ });
135
+
136
+ it("should parse weeks correctly (future)", () => {
137
+ expect(parseFlexibleTime("1w", baseTime)).toBe(
138
+ "2024-01-22T12:00:00.000Z"
139
+ );
140
+ expect(parseFlexibleTime("2w", baseTime)).toBe(
141
+ "2024-01-29T12:00:00.000Z"
142
+ );
143
+ expect(parseFlexibleTime("4w", baseTime)).toBe(
144
+ "2024-02-12T12:00:00.000Z"
145
+ );
146
+ });
147
+
148
+ it("should parse negative seconds correctly (past)", () => {
149
+ expect(parseFlexibleTime("-30s", baseTime)).toBe(
150
+ "2024-01-15T11:59:30.000Z"
151
+ );
152
+ expect(parseFlexibleTime("-1s", baseTime)).toBe(
153
+ "2024-01-15T11:59:59.000Z"
154
+ );
155
+ });
156
+
157
+ it("should parse negative minutes correctly (past)", () => {
158
+ expect(parseFlexibleTime("-1m", baseTime)).toBe(
159
+ "2024-01-15T11:59:00.000Z"
160
+ );
161
+ expect(parseFlexibleTime("-30m", baseTime)).toBe(
162
+ "2024-01-15T11:30:00.000Z"
163
+ );
164
+ });
165
+
166
+ it("should parse negative hours correctly (past)", () => {
167
+ expect(parseFlexibleTime("-1h", baseTime)).toBe(
168
+ "2024-01-15T11:00:00.000Z"
169
+ );
170
+ expect(parseFlexibleTime("-2h", baseTime)).toBe(
171
+ "2024-01-15T10:00:00.000Z"
172
+ );
173
+ expect(parseFlexibleTime("-12h", baseTime)).toBe(
174
+ "2024-01-15T00:00:00.000Z"
175
+ );
176
+ });
177
+
178
+ it("should parse negative days correctly (past)", () => {
179
+ expect(parseFlexibleTime("-1d", baseTime)).toBe(
180
+ "2024-01-14T12:00:00.000Z"
181
+ );
182
+ expect(parseFlexibleTime("-7d", baseTime)).toBe(
183
+ "2024-01-08T12:00:00.000Z"
184
+ );
185
+ expect(parseFlexibleTime("-30d", baseTime)).toBe(
186
+ "2023-12-16T12:00:00.000Z"
187
+ );
188
+ });
189
+
190
+ it("should parse negative weeks correctly (past)", () => {
191
+ expect(parseFlexibleTime("-1w", baseTime)).toBe(
192
+ "2024-01-08T12:00:00.000Z"
193
+ );
194
+ expect(parseFlexibleTime("-2w", baseTime)).toBe(
195
+ "2024-01-01T12:00:00.000Z"
196
+ );
197
+ expect(parseFlexibleTime("-4w", baseTime)).toBe(
198
+ "2023-12-18T12:00:00.000Z"
199
+ );
200
+ });
201
+ });
202
+
203
+ describe("default baseTime", () => {
204
+ it("should use current time as default base for 'now'", () => {
205
+ expect(parseFlexibleTime("now")).toBe("2024-01-15T12:00:00.000Z");
206
+ });
207
+
208
+ it("should use current time as default base for 'today'", () => {
209
+ expect(parseFlexibleTime("today")).toBe("2024-01-15T00:00:00.000Z");
210
+ });
211
+
212
+ it("should use current time as default base for relative times", () => {
213
+ expect(parseFlexibleTime("1h")).toBe("2024-01-15T13:00:00.000Z");
214
+ expect(parseFlexibleTime("1d")).toBe("2024-01-16T12:00:00.000Z");
215
+ expect(parseFlexibleTime("-1h")).toBe("2024-01-15T11:00:00.000Z");
216
+ expect(parseFlexibleTime("-1d")).toBe("2024-01-14T12:00:00.000Z");
217
+ });
218
+ });
219
+
220
+ describe("error handling", () => {
221
+ it("should throw error for invalid formats", () => {
222
+ expect(() => parseFlexibleTime("invalid", baseTime)).toThrow(
223
+ "Invalid time format"
224
+ );
225
+ expect(() => parseFlexibleTime("1x", baseTime)).toThrow(
226
+ "Invalid time format"
227
+ );
228
+ expect(() => parseFlexibleTime("not-a-date", baseTime)).toThrow(
229
+ "Invalid time format"
230
+ );
231
+ });
232
+
233
+ it("should throw error for zero amounts", () => {
234
+ expect(() => parseFlexibleTime("0h", baseTime)).toThrow(
235
+ "Time amount cannot be zero"
236
+ );
237
+ expect(() => parseFlexibleTime("0d", baseTime)).toThrow(
238
+ "Time amount cannot be zero"
239
+ );
240
+ expect(() => parseFlexibleTime("-0h", baseTime)).toThrow(
241
+ "Time amount cannot be zero"
242
+ );
243
+ });
244
+
245
+ it("should provide helpful error messages", () => {
246
+ expect(() => parseFlexibleTime("invalid", baseTime)).toThrow(
247
+ "Expected ISO 8601 date, 'now', 'today', 'tomorrow', 'yesterday', relative format"
248
+ );
249
+ expect(() => parseFlexibleTime("invalid", baseTime)).toThrow(
250
+ "week notation"
251
+ );
252
+ });
253
+ });
254
+
255
+ describe("edge cases", () => {
256
+ it("should handle large relative time values", () => {
257
+ const result = parseFlexibleTime("365d", baseTime);
258
+ expect(result).toBe("2025-01-14T12:00:00.000Z");
259
+ });
260
+
261
+ it("should handle large negative relative time values", () => {
262
+ const result = parseFlexibleTime("-365d", baseTime);
263
+ expect(result).toBe("2023-01-15T12:00:00.000Z");
264
+ });
265
+
266
+ it("should handle boundary cases for 'today'", () => {
267
+ const midnight = new Date("2024-01-15T00:00:00.000Z");
268
+ expect(parseFlexibleTime("today", midnight)).toBe(
269
+ "2024-01-15T00:00:00.000Z"
270
+ );
271
+
272
+ const almostMidnight = new Date("2024-01-15T00:00:00.001Z");
273
+ expect(parseFlexibleTime("today", almostMidnight)).toBe(
274
+ "2024-01-15T00:00:00.000Z"
275
+ );
276
+ });
277
+ });
278
+ });
279
+
280
+ describe("createFlexibleTimeParser", () => {
281
+ it("should create a parser with fixed base time", () => {
282
+ const baseTime = new Date("2024-01-15T12:00:00.000Z");
283
+ const parser = createFlexibleTimeParser(baseTime);
284
+
285
+ expect(parser("1h")).toBe("2024-01-15T13:00:00.000Z");
286
+ expect(parser("2d")).toBe("2024-01-17T12:00:00.000Z");
287
+ });
288
+
289
+ it("should preserve ISO 8601 dates with fixed parser", () => {
290
+ const baseTime = new Date("2024-01-15T12:00:00.000Z");
291
+ const parser = createFlexibleTimeParser(baseTime);
292
+ const isoDate = "2024-01-01T10:00:00.000Z";
293
+
294
+ expect(parser(isoDate)).toBe(isoDate);
295
+ });
296
+
297
+ it("should handle 'now' and 'today' with fixed base time", () => {
298
+ const baseTime = new Date("2024-01-15T12:00:00.000Z");
299
+ const parser = createFlexibleTimeParser(baseTime);
300
+
301
+ expect(parser("now")).toBe("2024-01-15T12:00:00.000Z");
302
+ expect(parser("today")).toBe("2024-01-15T00:00:00.000Z");
303
+ });
304
+
305
+ it("should maintain consistent base time across multiple calls", () => {
306
+ const baseTime = new Date("2024-01-15T12:00:00.000Z");
307
+ const parser = createFlexibleTimeParser(baseTime);
308
+
309
+ const result1 = parser("now");
310
+ const result2 = parser("now");
311
+ const result3 = parser("1h");
312
+
313
+ expect(result1).toBe(result2);
314
+ expect(result1).toBe("2024-01-15T12:00:00.000Z");
315
+ expect(result3).toBe("2024-01-15T13:00:00.000Z");
316
+ });
317
+
318
+ it("should work with different base times", () => {
319
+ const baseTime1 = new Date("2024-01-01T00:00:00.000Z");
320
+ const parser1 = createFlexibleTimeParser(baseTime1);
321
+
322
+ const baseTime2 = new Date("2024-12-31T23:59:59.999Z");
323
+ const parser2 = createFlexibleTimeParser(baseTime2);
324
+
325
+ expect(parser1("now")).toBe("2024-01-01T00:00:00.000Z");
326
+ expect(parser2("now")).toBe("2024-12-31T23:59:59.999Z");
327
+
328
+ expect(parser1("today")).toBe("2024-01-01T00:00:00.000Z");
329
+ expect(parser2("today")).toBe("2024-12-31T00:00:00.000Z");
330
+ });
331
+
332
+ it("should handle negative relative times with fixed parser", () => {
333
+ const baseTime = new Date("2024-01-15T12:00:00.000Z");
334
+ const parser = createFlexibleTimeParser(baseTime);
335
+
336
+ expect(parser("-1h")).toBe("2024-01-15T11:00:00.000Z");
337
+ expect(parser("-2d")).toBe("2024-01-13T12:00:00.000Z");
338
+ expect(parser("-1w")).toBe("2024-01-08T12:00:00.000Z");
339
+ });
340
+ });
341
+
342
+ describe("week notation parsing", () => {
343
+ // Base time: Monday, Jan 15, 2024 at 12:00 PM UTC
344
+ const baseTime = new Date("2024-01-15T12:00:00.000Z");
345
+
346
+ describe("week-start (current week Monday)", () => {
347
+ it("should return Monday 00:00:00 when base is Monday", () => {
348
+ expect(parseFlexibleTime("week-start", baseTime)).toBe(
349
+ "2024-01-15T00:00:00.000Z"
350
+ );
351
+ });
352
+
353
+ it("should return Monday 00:00:00 when base is mid-week", () => {
354
+ const wednesday = new Date("2024-01-17T15:30:00.000Z");
355
+ expect(parseFlexibleTime("week-start", wednesday)).toBe(
356
+ "2024-01-15T00:00:00.000Z"
357
+ );
358
+ });
359
+
360
+ it("should return Monday 00:00:00 when base is Sunday", () => {
361
+ const sunday = new Date("2024-01-21T23:59:59.999Z");
362
+ expect(parseFlexibleTime("week-start", sunday)).toBe(
363
+ "2024-01-15T00:00:00.000Z"
364
+ );
365
+ });
366
+ });
367
+
368
+ describe("week-end (current week Sunday)", () => {
369
+ it("should return Sunday 23:59:59.999 when base is Monday", () => {
370
+ expect(parseFlexibleTime("week-end", baseTime)).toBe(
371
+ "2024-01-21T23:59:59.999Z"
372
+ );
373
+ });
374
+
375
+ it("should return Sunday 23:59:59.999 when base is mid-week", () => {
376
+ const wednesday = new Date("2024-01-17T15:30:00.000Z");
377
+ expect(parseFlexibleTime("week-end", wednesday)).toBe(
378
+ "2024-01-21T23:59:59.999Z"
379
+ );
380
+ });
381
+
382
+ it("should return Sunday 23:59:59.999 when base is Sunday", () => {
383
+ const sunday = new Date("2024-01-21T12:00:00.000Z");
384
+ expect(parseFlexibleTime("week-end", sunday)).toBe(
385
+ "2024-01-21T23:59:59.999Z"
386
+ );
387
+ });
388
+ });
389
+
390
+ describe("week-start with positive offset (future weeks)", () => {
391
+ it("should parse week-start+1w as next Monday", () => {
392
+ expect(parseFlexibleTime("week-start+1w", baseTime)).toBe(
393
+ "2024-01-22T00:00:00.000Z"
394
+ );
395
+ });
396
+
397
+ it("should parse week-start+2w as Monday 2 weeks ahead", () => {
398
+ expect(parseFlexibleTime("week-start+2w", baseTime)).toBe(
399
+ "2024-01-29T00:00:00.000Z"
400
+ );
401
+ });
402
+
403
+ it("should parse week-start+4w as Monday 4 weeks ahead", () => {
404
+ expect(parseFlexibleTime("week-start+4w", baseTime)).toBe(
405
+ "2024-02-12T00:00:00.000Z"
406
+ );
407
+ });
408
+ });
409
+
410
+ describe("week-start with negative offset (past weeks)", () => {
411
+ it("should parse week-start-1w as previous Monday", () => {
412
+ expect(parseFlexibleTime("week-start-1w", baseTime)).toBe(
413
+ "2024-01-08T00:00:00.000Z"
414
+ );
415
+ });
416
+
417
+ it("should parse week-start-2w as Monday 2 weeks ago", () => {
418
+ expect(parseFlexibleTime("week-start-2w", baseTime)).toBe(
419
+ "2024-01-01T00:00:00.000Z"
420
+ );
421
+ });
422
+
423
+ it("should parse week-start-3w as Monday 3 weeks ago", () => {
424
+ expect(parseFlexibleTime("week-start-3w", baseTime)).toBe(
425
+ "2023-12-25T00:00:00.000Z"
426
+ );
427
+ });
428
+ });
429
+
430
+ describe("week-end with positive offset (future weeks)", () => {
431
+ it("should parse week-end+1w as next Sunday", () => {
432
+ expect(parseFlexibleTime("week-end+1w", baseTime)).toBe(
433
+ "2024-01-28T23:59:59.999Z"
434
+ );
435
+ });
436
+
437
+ it("should parse week-end+2w as Sunday 2 weeks ahead", () => {
438
+ expect(parseFlexibleTime("week-end+2w", baseTime)).toBe(
439
+ "2024-02-04T23:59:59.999Z"
440
+ );
441
+ });
442
+
443
+ it("should parse week-end+3w as Sunday 3 weeks ahead", () => {
444
+ expect(parseFlexibleTime("week-end+3w", baseTime)).toBe(
445
+ "2024-02-11T23:59:59.999Z"
446
+ );
447
+ });
448
+ });
449
+
450
+ describe("week-end with negative offset (past weeks)", () => {
451
+ it("should parse week-end-1w as previous Sunday", () => {
452
+ expect(parseFlexibleTime("week-end-1w", baseTime)).toBe(
453
+ "2024-01-14T23:59:59.999Z"
454
+ );
455
+ });
456
+
457
+ it("should parse week-end-2w as Sunday 2 weeks ago", () => {
458
+ expect(parseFlexibleTime("week-end-2w", baseTime)).toBe(
459
+ "2024-01-07T23:59:59.999Z"
460
+ );
461
+ });
462
+
463
+ it("should parse week-end-3w as Sunday 3 weeks ago", () => {
464
+ expect(parseFlexibleTime("week-end-3w", baseTime)).toBe(
465
+ "2023-12-31T23:59:59.999Z"
466
+ );
467
+ });
468
+ });
469
+
470
+ describe("week notation from different weekdays", () => {
471
+ it("should calculate week-start correctly from Tuesday", () => {
472
+ const tuesday = new Date("2024-01-16T10:00:00.000Z");
473
+ expect(parseFlexibleTime("week-start", tuesday)).toBe(
474
+ "2024-01-15T00:00:00.000Z"
475
+ );
476
+ expect(parseFlexibleTime("week-start+1w", tuesday)).toBe(
477
+ "2024-01-22T00:00:00.000Z"
478
+ );
479
+ });
480
+
481
+ it("should calculate week-end correctly from Friday", () => {
482
+ const friday = new Date("2024-01-19T18:00:00.000Z");
483
+ expect(parseFlexibleTime("week-end", friday)).toBe(
484
+ "2024-01-21T23:59:59.999Z"
485
+ );
486
+ expect(parseFlexibleTime("week-end-1w", friday)).toBe(
487
+ "2024-01-14T23:59:59.999Z"
488
+ );
489
+ });
490
+
491
+ it("should calculate correctly from Saturday", () => {
492
+ const saturday = new Date("2024-01-20T20:00:00.000Z");
493
+ expect(parseFlexibleTime("week-start", saturday)).toBe(
494
+ "2024-01-15T00:00:00.000Z"
495
+ );
496
+ expect(parseFlexibleTime("week-end", saturday)).toBe(
497
+ "2024-01-21T23:59:59.999Z"
498
+ );
499
+ });
500
+ });
501
+
502
+ describe("week notation error handling", () => {
503
+ it("should throw error for invalid week boundary", () => {
504
+ expect(() => parseFlexibleTime("week-middle", baseTime)).toThrow(
505
+ "Invalid time format"
506
+ );
507
+ expect(() => parseFlexibleTime("week-begin", baseTime)).toThrow(
508
+ "Invalid time format"
509
+ );
510
+ });
511
+
512
+ it("should throw error for zero offset", () => {
513
+ expect(() => parseFlexibleTime("week-start+0w", baseTime)).toThrow(
514
+ "Week offset cannot be zero"
515
+ );
516
+ expect(() => parseFlexibleTime("week-end-0w", baseTime)).toThrow(
517
+ "Week offset cannot be zero"
518
+ );
519
+ });
520
+
521
+ it("should throw error for invalid offset format", () => {
522
+ expect(() => parseFlexibleTime("week-start+1d", baseTime)).toThrow(
523
+ "Invalid time format"
524
+ );
525
+ expect(() => parseFlexibleTime("week-end+2h", baseTime)).toThrow(
526
+ "Invalid time format"
527
+ );
528
+ expect(() => parseFlexibleTime("week-start1w", baseTime)).toThrow(
529
+ "Invalid time format"
530
+ );
531
+ });
532
+
533
+ it("should throw error for malformed syntax", () => {
534
+ expect(() => parseFlexibleTime("weekstart", baseTime)).toThrow(
535
+ "Invalid time format"
536
+ );
537
+ expect(() => parseFlexibleTime("week-start+", baseTime)).toThrow(
538
+ "Invalid time format"
539
+ );
540
+ expect(() => parseFlexibleTime("week-end-", baseTime)).toThrow(
541
+ "Invalid time format"
542
+ );
543
+ });
544
+ });
545
+
546
+ describe("week notation edge cases", () => {
547
+ it("should handle large positive offsets", () => {
548
+ expect(parseFlexibleTime("week-start+10w", baseTime)).toBe(
549
+ "2024-03-25T00:00:00.000Z"
550
+ );
551
+ expect(parseFlexibleTime("week-end+10w", baseTime)).toBe(
552
+ "2024-03-31T23:59:59.999Z"
553
+ );
554
+ });
555
+
556
+ it("should handle large negative offsets", () => {
557
+ expect(parseFlexibleTime("week-start-10w", baseTime)).toBe(
558
+ "2023-11-06T00:00:00.000Z"
559
+ );
560
+ expect(parseFlexibleTime("week-end-10w", baseTime)).toBe(
561
+ "2023-11-12T23:59:59.999Z"
562
+ );
563
+ });
564
+
565
+ it("should handle year boundary crossings", () => {
566
+ const endOfYear = new Date("2024-12-30T12:00:00.000Z"); // Monday
567
+ expect(parseFlexibleTime("week-start+1w", endOfYear)).toBe(
568
+ "2025-01-06T00:00:00.000Z"
569
+ );
570
+ expect(parseFlexibleTime("week-end+1w", endOfYear)).toBe(
571
+ "2025-01-12T23:59:59.999Z"
572
+ );
573
+ });
574
+
575
+ it("should handle leap year correctly", () => {
576
+ const leapDay = new Date("2024-02-29T12:00:00.000Z"); // Thursday
577
+ expect(parseFlexibleTime("week-start", leapDay)).toBe(
578
+ "2024-02-26T00:00:00.000Z"
579
+ );
580
+ expect(parseFlexibleTime("week-end", leapDay)).toBe(
581
+ "2024-03-03T23:59:59.999Z"
582
+ );
583
+ });
584
+ });
585
+ });
586
+ });