@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.
- package/CONTRIBUTING.md +1190 -0
- package/README.md +275 -0
- package/bin/setup.js +10 -0
- package/dist/api-schemas/error.schema.d.ts +20 -0
- package/dist/api-schemas/error.schema.d.ts.map +1 -0
- package/dist/api-schemas/error.schema.js +17 -0
- package/dist/api-schemas/health.schema.d.ts +497 -0
- package/dist/api-schemas/health.schema.d.ts.map +1 -0
- package/dist/api-schemas/health.schema.js +33 -0
- package/dist/api-schemas/okay.schema.d.ts +13 -0
- package/dist/api-schemas/okay.schema.d.ts.map +1 -0
- package/dist/api-schemas/okay.schema.js +5 -0
- package/dist/api-schemas/paginated-results.schema.d.ts +59 -0
- package/dist/api-schemas/paginated-results.schema.d.ts.map +1 -0
- package/dist/api-schemas/paginated-results.schema.js +10 -0
- package/dist/api-schemas/partial-results.schema.d.ts +30 -0
- package/dist/api-schemas/partial-results.schema.d.ts.map +1 -0
- package/dist/api-schemas/partial-results.schema.js +10 -0
- package/dist/api-schemas/result.schema.d.ts +17 -0
- package/dist/api-schemas/result.schema.d.ts.map +1 -0
- package/dist/api-schemas/result.schema.js +5 -0
- package/dist/api-schemas/results.schema.d.ts +21 -0
- package/dist/api-schemas/results.schema.d.ts.map +1 -0
- package/dist/api-schemas/results.schema.js +5 -0
- package/dist/helpers/correlation/get-correlation-id.d.ts +7 -0
- package/dist/helpers/correlation/get-correlation-id.d.ts.map +1 -0
- package/dist/helpers/correlation/get-correlation-id.js +16 -0
- package/dist/helpers/correlation/get-header.d.ts +7 -0
- package/dist/helpers/correlation/get-header.d.ts.map +1 -0
- package/dist/helpers/correlation/get-header.js +11 -0
- package/dist/helpers/detect-mime-type.d.ts +11 -0
- package/dist/helpers/detect-mime-type.d.ts.map +1 -0
- package/dist/helpers/detect-mime-type.js +40 -0
- package/dist/helpers/detect-suspicious-patterns.d.ts +8 -0
- package/dist/helpers/detect-suspicious-patterns.d.ts.map +1 -0
- package/dist/helpers/detect-suspicious-patterns.js +55 -0
- package/dist/helpers/eventify-constants.types.d.ts +32 -0
- package/dist/helpers/eventify-constants.types.d.ts.map +1 -0
- package/dist/helpers/eventify-constants.types.js +40 -0
- package/dist/helpers/hash-binary.d.ts +21 -0
- package/dist/helpers/hash-binary.d.ts.map +1 -0
- package/dist/helpers/hash-binary.js +28 -0
- package/dist/helpers/mime-types/detect-image-mime-type.d.ts +5 -0
- package/dist/helpers/mime-types/detect-image-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-image-mime-type.js +41 -0
- package/dist/helpers/mime-types/detect-ole-mime-type.d.ts +6 -0
- package/dist/helpers/mime-types/detect-ole-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-ole-mime-type.js +34 -0
- package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts +5 -0
- package/dist/helpers/mime-types/detect-pdf-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-pdf-mime-type.js +13 -0
- package/dist/helpers/mime-types/detect-zip-mime-type.d.ts +6 -0
- package/dist/helpers/mime-types/detect-zip-mime-type.d.ts.map +1 -0
- package/dist/helpers/mime-types/detect-zip-mime-type.js +23 -0
- package/dist/helpers/parameter-validation.d.ts +6 -0
- package/dist/helpers/parameter-validation.d.ts.map +1 -0
- package/dist/helpers/parameter-validation.js +19 -0
- package/dist/helpers/parameter-validation.types.d.ts +16 -0
- package/dist/helpers/parameter-validation.types.d.ts.map +1 -0
- package/dist/helpers/parameter-validation.types.js +38 -0
- package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts +6 -0
- package/dist/helpers/response-headers/build-api-unauthorized-headers.d.ts.map +1 -0
- package/dist/helpers/response-headers/build-api-unauthorized-headers.js +23 -0
- package/dist/helpers/response-headers/environment.types.d.ts +2 -0
- package/dist/helpers/response-headers/environment.types.d.ts.map +1 -0
- package/dist/helpers/response-headers/environment.types.js +1 -0
- package/dist/helpers/response-headers/resolve-environment.d.ts +8 -0
- package/dist/helpers/response-headers/resolve-environment.d.ts.map +1 -0
- package/dist/helpers/response-headers/resolve-environment.js +18 -0
- package/dist/helpers/slugify.d.ts +15 -0
- package/dist/helpers/slugify.d.ts.map +1 -0
- package/dist/helpers/slugify.js +32 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/normalization/normalize-list.d.ts +11 -0
- package/dist/normalization/normalize-list.d.ts.map +1 -0
- package/dist/normalization/normalize-list.js +19 -0
- package/dist/normalization/normalize-location.d.ts +16 -0
- package/dist/normalization/normalize-location.d.ts.map +1 -0
- package/dist/normalization/normalize-location.js +26 -0
- package/dist/primitives/coordinate-precision.d.ts +10 -0
- package/dist/primitives/coordinate-precision.d.ts.map +1 -0
- package/dist/primitives/coordinate-precision.js +27 -0
- package/dist/primitives/geo-point.schema.d.ts +8 -0
- package/dist/primitives/geo-point.schema.d.ts.map +1 -0
- package/dist/primitives/geo-point.schema.js +10 -0
- package/dist/primitives/geoname-id.schema.d.ts +8 -0
- package/dist/primitives/geoname-id.schema.d.ts.map +1 -0
- package/dist/primitives/geoname-id.schema.js +9 -0
- package/dist/primitives/international-zip.schema.d.ts +76 -0
- package/dist/primitives/international-zip.schema.d.ts.map +1 -0
- package/dist/primitives/international-zip.schema.js +81 -0
- package/dist/primitives/latitude.schema.d.ts +9 -0
- package/dist/primitives/latitude.schema.d.ts.map +1 -0
- package/dist/primitives/latitude.schema.js +13 -0
- package/dist/primitives/location.schema.d.ts +8 -0
- package/dist/primitives/location.schema.d.ts.map +1 -0
- package/dist/primitives/location.schema.js +15 -0
- package/dist/primitives/longitude.schema.d.ts +9 -0
- package/dist/primitives/longitude.schema.d.ts.map +1 -0
- package/dist/primitives/longitude.schema.js +13 -0
- package/dist/primitives/numeric-id.schema.d.ts +8 -0
- package/dist/primitives/numeric-id.schema.d.ts.map +1 -0
- package/dist/primitives/numeric-id.schema.js +10 -0
- package/dist/primitives/slug.schema.d.ts +17 -0
- package/dist/primitives/slug.schema.d.ts.map +1 -0
- package/dist/primitives/slug.schema.js +30 -0
- package/dist/primitives/uuid.schema.d.ts +8 -0
- package/dist/primitives/uuid.schema.d.ts.map +1 -0
- package/dist/primitives/uuid.schema.js +9 -0
- package/dist/primitives/wikidata-id.schema.d.ts +9 -0
- package/dist/primitives/wikidata-id.schema.d.ts.map +1 -0
- package/dist/primitives/wikidata-id.schema.js +10 -0
- package/dist/time/boundary-enforcement.d.ts +11 -0
- package/dist/time/boundary-enforcement.d.ts.map +1 -0
- package/dist/time/boundary-enforcement.js +43 -0
- package/dist/time/bounded-time.schema.d.ts +31 -0
- package/dist/time/bounded-time.schema.d.ts.map +1 -0
- package/dist/time/bounded-time.schema.js +77 -0
- package/dist/time/flexible-time-parser.d.ts +12 -0
- package/dist/time/flexible-time-parser.d.ts.map +1 -0
- package/dist/time/flexible-time-parser.js +94 -0
- package/dist/time/flexible-time.schema.d.ts +31 -0
- package/dist/time/flexible-time.schema.d.ts.map +1 -0
- package/dist/time/flexible-time.schema.js +31 -0
- package/dist/time/get-week-end.d.ts +10 -0
- package/dist/time/get-week-end.d.ts.map +1 -0
- package/dist/time/get-week-end.js +25 -0
- package/dist/time/get-week-start.d.ts +10 -0
- package/dist/time/get-week-start.d.ts.map +1 -0
- package/dist/time/get-week-start.js +25 -0
- package/dist/time/is-relative-time.d.ts +8 -0
- package/dist/time/is-relative-time.d.ts.map +1 -0
- package/dist/time/is-relative-time.js +9 -0
- package/dist/time/iso8601.schema.d.ts +14 -0
- package/dist/time/iso8601.schema.d.ts.map +1 -0
- package/dist/time/iso8601.schema.js +17 -0
- package/dist/time/iso8601.types.d.ts +6 -0
- package/dist/time/iso8601.types.d.ts.map +1 -0
- package/dist/time/iso8601.types.js +11 -0
- package/dist/time/parse-relative-time.d.ts +9 -0
- package/dist/time/parse-relative-time.d.ts.map +1 -0
- package/dist/time/parse-relative-time.js +36 -0
- package/dist/time/relative-time.schema.d.ts +23 -0
- package/dist/time/relative-time.schema.d.ts.map +1 -0
- package/dist/time/relative-time.schema.js +25 -0
- package/dist/time/since-parameter.schema.d.ts +8 -0
- package/dist/time/since-parameter.schema.d.ts.map +1 -0
- package/dist/time/since-parameter.schema.js +56 -0
- package/dist/time/time-helpers.d.ts +19 -0
- package/dist/time/time-helpers.d.ts.map +1 -0
- package/dist/time/time-helpers.js +56 -0
- package/dist/time/time-schemas.d.ts +20 -0
- package/dist/time/time-schemas.d.ts.map +1 -0
- package/dist/time/time-schemas.js +25 -0
- package/dist/time/timezone.types.d.ts +17 -0
- package/dist/time/timezone.types.d.ts.map +1 -0
- package/dist/time/timezone.types.js +15 -0
- package/dist/validation/zod-error-handler.d.ts +3 -0
- package/dist/validation/zod-error-handler.d.ts.map +1 -0
- package/dist/validation/zod-error-handler.js +189 -0
- package/dist/validation/zod-utils.d.ts +9 -0
- package/dist/validation/zod-utils.d.ts.map +1 -0
- package/dist/validation/zod-utils.js +23 -0
- package/eslint.config.mjs +16 -0
- package/package.json +44 -0
- package/src/api-schemas/error.schema.test.ts +27 -0
- package/src/api-schemas/error.schema.ts +23 -0
- package/src/api-schemas/health.schema.test.ts +104 -0
- package/src/api-schemas/health.schema.ts +63 -0
- package/src/api-schemas/okay.schema.test.ts +15 -0
- package/src/api-schemas/okay.schema.ts +8 -0
- package/src/api-schemas/paginated-results.schema.ts +17 -0
- package/src/api-schemas/partial-results.schema.ts +13 -0
- package/src/api-schemas/result.schema.test.ts +19 -0
- package/src/api-schemas/result.schema.ts +9 -0
- package/src/api-schemas/results.schema.test.ts +15 -0
- package/src/api-schemas/results.schema.ts +9 -0
- package/src/helpers/correlation/get-correlation-id.test.ts +126 -0
- package/src/helpers/correlation/get-correlation-id.ts +22 -0
- package/src/helpers/correlation/get-header.test.ts +179 -0
- package/src/helpers/correlation/get-header.ts +21 -0
- package/src/helpers/detect-mime-type.test.ts +100 -0
- package/src/helpers/detect-mime-type.ts +46 -0
- package/src/helpers/detect-suspicious-patterns.test.ts +45 -0
- package/src/helpers/detect-suspicious-patterns.ts +57 -0
- package/src/helpers/eventify-constants.test.ts +52 -0
- package/src/helpers/eventify-constants.types.test.ts +52 -0
- package/src/helpers/eventify-constants.types.ts +51 -0
- package/src/helpers/hash-binary.test.ts +60 -0
- package/src/helpers/hash-binary.ts +30 -0
- package/src/helpers/mime-types/detect-image-mime-type.test.ts +73 -0
- package/src/helpers/mime-types/detect-image-mime-type.ts +50 -0
- package/src/helpers/mime-types/detect-ole-mime-type.test.ts +86 -0
- package/src/helpers/mime-types/detect-ole-mime-type.ts +44 -0
- package/src/helpers/mime-types/detect-pdf-mime-type.test.ts +39 -0
- package/src/helpers/mime-types/detect-pdf-mime-type.ts +15 -0
- package/src/helpers/mime-types/detect-zip-mime-type.test.ts +88 -0
- package/src/helpers/mime-types/detect-zip-mime-type.ts +28 -0
- package/src/helpers/parameter-validation.test.ts +35 -0
- package/src/helpers/parameter-validation.ts +32 -0
- package/src/helpers/process-eventify-request.ts +146 -0
- package/src/helpers/response-headers/build-api-unauthorized-headers.ts +30 -0
- package/src/helpers/response-headers/environment.types.ts +1 -0
- package/src/helpers/response-headers/resolve-environment.ts +17 -0
- package/src/helpers/slugify.test.ts +77 -0
- package/src/helpers/slugify.ts +34 -0
- package/src/index.ts +46 -0
- package/src/normalization/normalize-list.test.ts +43 -0
- package/src/normalization/normalize-list.ts +21 -0
- package/src/normalization/normalize-location.test.ts +91 -0
- package/src/normalization/normalize-location.ts +29 -0
- package/src/primitives/coordinate-precision.test.ts +46 -0
- package/src/primitives/coordinate-precision.ts +30 -0
- package/src/primitives/geo-point.schema.test.ts +70 -0
- package/src/primitives/geo-point.schema.ts +14 -0
- package/src/primitives/geoname-id.schema.test.ts +60 -0
- package/src/primitives/geoname-id.schema.ts +12 -0
- package/src/primitives/international-zip.schema.test.ts +212 -0
- package/src/primitives/international-zip.schema.ts +103 -0
- package/src/primitives/latitude.schema.test.ts +77 -0
- package/src/primitives/latitude.schema.ts +20 -0
- package/src/primitives/location.schema.test.ts +21 -0
- package/src/primitives/location.schema.ts +22 -0
- package/src/primitives/longitude.schema.test.ts +77 -0
- package/src/primitives/longitude.schema.ts +20 -0
- package/src/primitives/numeric-id.schema.test.ts +32 -0
- package/src/primitives/numeric-id.schema.ts +13 -0
- package/src/primitives/slug.schema.test.ts +101 -0
- package/src/primitives/slug.schema.ts +41 -0
- package/src/primitives/uuid.schema.test.ts +45 -0
- package/src/primitives/uuid.schema.ts +12 -0
- package/src/primitives/wikidata-id.schema.test.ts +51 -0
- package/src/primitives/wikidata-id.schema.ts +16 -0
- package/src/time/README.md +220 -0
- package/src/time/boundary-enforcement.test.ts +130 -0
- package/src/time/boundary-enforcement.ts +59 -0
- package/src/time/bounded-time.schema.test.ts +294 -0
- package/src/time/bounded-time.schema.ts +111 -0
- package/src/time/flexible-time-parser.test.ts +586 -0
- package/src/time/flexible-time-parser.ts +122 -0
- package/src/time/flexible-time.schema.test.ts +243 -0
- package/src/time/flexible-time.schema.ts +43 -0
- package/src/time/is-relative-time.test.ts +23 -0
- package/src/time/is-relative-time.ts +9 -0
- package/src/time/iso8601.schema.ts +29 -0
- package/src/time/iso8601.types.test.ts +112 -0
- package/src/time/iso8601.types.ts +21 -0
- package/src/time/parse-relative-time.test.ts +49 -0
- package/src/time/parse-relative-time.ts +50 -0
- package/src/time/relative-time.schema.test.ts +23 -0
- package/src/time/relative-time.schema.ts +38 -0
- package/src/time/since-parameter.schema.test.ts +59 -0
- package/src/time/since-parameter.schema.ts +69 -0
- package/src/time/time-helpers.test.ts +263 -0
- package/src/time/time-helpers.ts +78 -0
- package/src/time/time-schemas.test.ts +181 -0
- package/src/time/time-schemas.ts +42 -0
- package/src/time/time.schema.test.ts +237 -0
- package/src/time/timezone-independence.test.ts +188 -0
- package/src/time/timezone.types.test.ts +55 -0
- package/src/time/timezone.types.ts +22 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getLogger } from "@schafevormfenster/logging";
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import utc from "dayjs/plugin/utc.js";
|
|
4
|
+
|
|
5
|
+
dayjs.extend(utc);
|
|
6
|
+
|
|
7
|
+
const log = getLogger("rest.helpers.relative-time-parser");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Helper function to convert relative time expressions to ISO8601 timestamps
|
|
11
|
+
* All calculations are performed in UTC timezone
|
|
12
|
+
*
|
|
13
|
+
* @param relativeTime - Relative time expression like "1d", "3h", "30m"
|
|
14
|
+
* @returns ISO8601 timestamp string
|
|
15
|
+
*/
|
|
16
|
+
export const parseRelativeTime = (relativeTime: string): string => {
|
|
17
|
+
const relativePattern = /^(\d+)([mhdwM])$/;
|
|
18
|
+
const match = relativePattern.exec(relativeTime);
|
|
19
|
+
|
|
20
|
+
if (!match) {
|
|
21
|
+
log.error(
|
|
22
|
+
{ relativeTime, error: `Invalid relative time format: ${relativeTime}` },
|
|
23
|
+
`Invalid relative time format: ${relativeTime}`
|
|
24
|
+
);
|
|
25
|
+
throw new Error(`Invalid relative time format: ${relativeTime}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const [, amount, unit] = match;
|
|
29
|
+
const numberAmount = Number.parseInt(amount, 10);
|
|
30
|
+
|
|
31
|
+
// Map unit to Day.js unit names
|
|
32
|
+
const unitMap: Record<string, dayjs.ManipulateType> = {
|
|
33
|
+
m: "minute",
|
|
34
|
+
h: "hour",
|
|
35
|
+
d: "day",
|
|
36
|
+
w: "week",
|
|
37
|
+
M: "month",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const dayjsUnit = unitMap[unit];
|
|
41
|
+
if (!dayjsUnit) {
|
|
42
|
+
log.error(
|
|
43
|
+
{ unit, error: `Invalid time unit: ${unit}` },
|
|
44
|
+
`Invalid time unit: ${unit}`
|
|
45
|
+
);
|
|
46
|
+
throw new Error(`Invalid time unit: ${unit}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return dayjs.utc().subtract(numberAmount, dayjsUnit).toISOString();
|
|
50
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { RelativeTimeSchema } from "./relative-time.schema";
|
|
4
|
+
|
|
5
|
+
describe("RelativeTimeSchema", () => {
|
|
6
|
+
it("should accept valid relative time expressions", () => {
|
|
7
|
+
expect(RelativeTimeSchema.parse("1d")).toBe("1d");
|
|
8
|
+
expect(RelativeTimeSchema.parse("3h")).toBe("3h");
|
|
9
|
+
expect(RelativeTimeSchema.parse("30m")).toBe("30m");
|
|
10
|
+
expect(RelativeTimeSchema.parse("1w")).toBe("1w");
|
|
11
|
+
expect(RelativeTimeSchema.parse("2M")).toBe("2M");
|
|
12
|
+
expect(RelativeTimeSchema.parse("999d")).toBe("999d");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should reject invalid relative time expressions", () => {
|
|
16
|
+
expect(() => RelativeTimeSchema.parse("1")).toThrow();
|
|
17
|
+
expect(() => RelativeTimeSchema.parse("d")).toThrow();
|
|
18
|
+
expect(() => RelativeTimeSchema.parse("1x")).toThrow();
|
|
19
|
+
expect(() => RelativeTimeSchema.parse("1day")).toThrow();
|
|
20
|
+
expect(() => RelativeTimeSchema.parse("")).toThrow();
|
|
21
|
+
expect(() => RelativeTimeSchema.parse("1.5d")).toThrow();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { parseRelativeTime } from "./time-helpers";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Core validation schema for relative time expressions
|
|
7
|
+
* Supports: "1m" (minute), "2h" (hour), "3d" (day), "1w" (week), "2M" (month)
|
|
8
|
+
* Validates format but does not transform
|
|
9
|
+
*/
|
|
10
|
+
export const RelativeTimeSchema = z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Relative time expression")
|
|
13
|
+
.regex(
|
|
14
|
+
/^(\d+)([mhdwM])$/,
|
|
15
|
+
"Must be a relative time expression (e.g., '1d', '3h', '30m', '1w', '2M')"
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export type RelativeTime = z.infer<typeof RelativeTimeSchema>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Transformation schema that converts relative time to ISO8601
|
|
22
|
+
* Direction: past (subtracts from current time)
|
|
23
|
+
* @example
|
|
24
|
+
* RelativeTimePastSchema.parse("1d") // "2024-01-14T12:00:00.000Z" (if now is 2024-01-15T12:00:00Z)
|
|
25
|
+
*/
|
|
26
|
+
export const RelativeTimePastSchema = RelativeTimeSchema.transform((value) =>
|
|
27
|
+
parseRelativeTime(value, new Date(), "past")
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Transformation schema that converts relative time to ISO8601
|
|
32
|
+
* Direction: future (adds to current time)
|
|
33
|
+
* @example
|
|
34
|
+
* RelativeTimeFutureSchema.parse("1d") // "2024-01-16T12:00:00.000Z" (if now is 2024-01-15T12:00:00Z)
|
|
35
|
+
*/
|
|
36
|
+
export const RelativeTimeFutureSchema = RelativeTimeSchema.transform((value) =>
|
|
37
|
+
parseRelativeTime(value, new Date(), "future")
|
|
38
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { SinceParameterSchema } from "./since-parameter.schema";
|
|
4
|
+
|
|
5
|
+
describe("SinceParameterSchema", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Mock the current time to a fixed date for consistent testing
|
|
8
|
+
vi.useFakeTimers();
|
|
9
|
+
vi.setSystemTime(new Date("2024-07-26T15:20:00Z"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should accept valid ISO8601 timestamps", () => {
|
|
17
|
+
const result = SinceParameterSchema.parse("2024-07-26T15:20:00Z");
|
|
18
|
+
expect(result).toBe("2024-07-26T15:20:00.000Z");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should accept ISO8601 timestamps with timezone offsets", () => {
|
|
22
|
+
const result = SinceParameterSchema.parse("2024-07-26T17:20:00+02:00");
|
|
23
|
+
expect(result).toBe("2024-07-26T15:20:00.000Z");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should convert relative time expressions to ISO8601", () => {
|
|
27
|
+
const result = SinceParameterSchema.parse("1d");
|
|
28
|
+
expect(result).toBe("2024-07-25T15:20:00.000Z");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should convert various relative time units", () => {
|
|
32
|
+
expect(SinceParameterSchema.parse("30m")).toBe("2024-07-26T14:50:00.000Z");
|
|
33
|
+
expect(SinceParameterSchema.parse("2h")).toBe("2024-07-26T13:20:00.000Z");
|
|
34
|
+
expect(SinceParameterSchema.parse("3d")).toBe("2024-07-23T15:20:00.000Z");
|
|
35
|
+
expect(SinceParameterSchema.parse("1w")).toBe("2024-07-19T15:20:00.000Z");
|
|
36
|
+
expect(SinceParameterSchema.parse("1M")).toBe("2024-06-26T15:20:00.000Z");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should reject invalid formats", () => {
|
|
40
|
+
expect(() => SinceParameterSchema.parse("invalid")).toThrow(
|
|
41
|
+
/Must be either ISO8601 timestamp or relative time expression/
|
|
42
|
+
);
|
|
43
|
+
expect(() => SinceParameterSchema.parse("2024-13-32")).toThrow(
|
|
44
|
+
/Must be either ISO8601 timestamp or relative time expression/
|
|
45
|
+
);
|
|
46
|
+
expect(() => SinceParameterSchema.parse("1x")).toThrow(
|
|
47
|
+
/Must be either ISO8601 timestamp or relative time expression/
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should reject invalid ISO8601 formats", () => {
|
|
52
|
+
expect(() => SinceParameterSchema.parse("2024-07-26")).toThrow(
|
|
53
|
+
/Must be either ISO8601 timestamp or relative time expression/
|
|
54
|
+
);
|
|
55
|
+
expect(() => SinceParameterSchema.parse("not-a-date")).toThrow(
|
|
56
|
+
/Must be either ISO8601 timestamp or relative time expression/
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import utc from "dayjs/plugin/utc.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
dayjs.extend(utc);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Schema that accepts either ISO8601 timestamps or relative time expressions
|
|
9
|
+
* All calculations are performed in UTC timezone
|
|
10
|
+
*/
|
|
11
|
+
export const SinceParameterSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.describe("Timestamp in ISO8601 format or relative time expression")
|
|
14
|
+
.transform((value, context) => {
|
|
15
|
+
// Try to parse as ISO8601 first
|
|
16
|
+
const iso8601Pattern =
|
|
17
|
+
/^((?:(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2}(?:\.\d+)?))(Z|[\+-]\d{2}:\d{2})?)$/;
|
|
18
|
+
|
|
19
|
+
if (iso8601Pattern.test(value)) {
|
|
20
|
+
try {
|
|
21
|
+
return new Date(value).toISOString();
|
|
22
|
+
} catch {
|
|
23
|
+
context.addIssue({
|
|
24
|
+
code: z.ZodIssueCode.custom,
|
|
25
|
+
message: "Invalid ISO8601 timestamp format",
|
|
26
|
+
});
|
|
27
|
+
return z.NEVER;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Try to parse as relative time
|
|
32
|
+
const relativePattern = /^(\d+)([mhdwM])$/;
|
|
33
|
+
const match = relativePattern.exec(value);
|
|
34
|
+
|
|
35
|
+
if (!match) {
|
|
36
|
+
context.addIssue({
|
|
37
|
+
code: z.ZodIssueCode.custom,
|
|
38
|
+
message:
|
|
39
|
+
"Must be either ISO8601 timestamp or relative time expression (e.g., '1d', '3h', '30m')",
|
|
40
|
+
});
|
|
41
|
+
return z.NEVER;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const [, amount, unit] = match;
|
|
45
|
+
const numberAmount = Number.parseInt(amount, 10);
|
|
46
|
+
|
|
47
|
+
// Map unit to Day.js unit names
|
|
48
|
+
const unitMap: Record<string, dayjs.ManipulateType> = {
|
|
49
|
+
m: "minute",
|
|
50
|
+
h: "hour",
|
|
51
|
+
d: "day",
|
|
52
|
+
w: "week",
|
|
53
|
+
M: "month",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const dayjsUnit = unitMap[unit];
|
|
57
|
+
if (!dayjsUnit) {
|
|
58
|
+
context.addIssue({
|
|
59
|
+
code: z.ZodIssueCode.custom,
|
|
60
|
+
message:
|
|
61
|
+
"Invalid time unit. Use 'm' (minutes), 'h' (hours), 'd' (days), 'w' (weeks), or 'M' (months)",
|
|
62
|
+
});
|
|
63
|
+
return z.NEVER;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return dayjs.utc().subtract(numberAmount, dayjsUnit).toISOString();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export type SinceParameter = z.infer<typeof SinceParameterSchema>;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { parseFlexibleTime, parseRelativeTime } from "./time-helpers";
|
|
4
|
+
|
|
5
|
+
describe("parseRelativeTime", () => {
|
|
6
|
+
const baseDate = new Date("2024-01-15T12:00:00.000Z");
|
|
7
|
+
|
|
8
|
+
describe("past direction", () => {
|
|
9
|
+
it("should subtract minutes", () => {
|
|
10
|
+
const result = parseRelativeTime("30m", baseDate, "past");
|
|
11
|
+
expect(result).toBe("2024-01-15T11:30:00.000Z");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should subtract hours", () => {
|
|
15
|
+
const result = parseRelativeTime("3h", baseDate, "past");
|
|
16
|
+
expect(result).toBe("2024-01-15T09:00:00.000Z");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should subtract days", () => {
|
|
20
|
+
const result = parseRelativeTime("2d", baseDate, "past");
|
|
21
|
+
expect(result).toBe("2024-01-13T12:00:00.000Z");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should subtract weeks", () => {
|
|
25
|
+
const result = parseRelativeTime("1w", baseDate, "past");
|
|
26
|
+
expect(result).toBe("2024-01-08T12:00:00.000Z");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should subtract months", () => {
|
|
30
|
+
const result = parseRelativeTime("1M", baseDate, "past");
|
|
31
|
+
expect(result).toBe("2023-12-15T12:00:00.000Z");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should default to past direction", () => {
|
|
35
|
+
const result = parseRelativeTime("1d", baseDate);
|
|
36
|
+
expect(result).toBe("2024-01-14T12:00:00.000Z");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("future direction", () => {
|
|
41
|
+
it("should add minutes", () => {
|
|
42
|
+
const result = parseRelativeTime("30m", baseDate, "future");
|
|
43
|
+
expect(result).toBe("2024-01-15T12:30:00.000Z");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should add hours", () => {
|
|
47
|
+
const result = parseRelativeTime("3h", baseDate, "future");
|
|
48
|
+
expect(result).toBe("2024-01-15T15:00:00.000Z");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should add days", () => {
|
|
52
|
+
const result = parseRelativeTime("2d", baseDate, "future");
|
|
53
|
+
expect(result).toBe("2024-01-17T12:00:00.000Z");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should add weeks", () => {
|
|
57
|
+
const result = parseRelativeTime("1w", baseDate, "future");
|
|
58
|
+
expect(result).toBe("2024-01-22T12:00:00.000Z");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should add months", () => {
|
|
62
|
+
const result = parseRelativeTime("1M", baseDate, "future");
|
|
63
|
+
expect(result).toBe("2024-02-15T12:00:00.000Z");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("various time amounts", () => {
|
|
68
|
+
it("should handle single digit amounts", () => {
|
|
69
|
+
const result = parseRelativeTime("1d", baseDate, "past");
|
|
70
|
+
expect(result).toBe("2024-01-14T12:00:00.000Z");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should handle double digit amounts", () => {
|
|
74
|
+
const result = parseRelativeTime("15d", baseDate, "past");
|
|
75
|
+
expect(result).toBe("2023-12-31T12:00:00.000Z");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should handle large amounts", () => {
|
|
79
|
+
const result = parseRelativeTime("365d", baseDate, "past");
|
|
80
|
+
expect(result).toBe("2023-01-15T12:00:00.000Z");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("default parameters", () => {
|
|
85
|
+
it("should use current time when 'from' is not provided", () => {
|
|
86
|
+
const result = parseRelativeTime("1d");
|
|
87
|
+
expect(result).toBeTruthy();
|
|
88
|
+
expect(typeof result).toBe("string");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("error handling", () => {
|
|
93
|
+
it("should throw error for invalid format", () => {
|
|
94
|
+
expect(() => parseRelativeTime("invalid", baseDate)).toThrow(
|
|
95
|
+
/Invalid relative time format: invalid/
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should throw error for missing unit", () => {
|
|
100
|
+
expect(() => parseRelativeTime("5", baseDate)).toThrow(
|
|
101
|
+
/Invalid relative time format/
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should throw error for missing amount", () => {
|
|
106
|
+
expect(() => parseRelativeTime("d", baseDate)).toThrow(
|
|
107
|
+
/Invalid relative time format/
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should throw error for invalid unit", () => {
|
|
112
|
+
expect(() => parseRelativeTime("5x", baseDate)).toThrow(
|
|
113
|
+
/Invalid relative time format/
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should throw error for spaces in input", () => {
|
|
118
|
+
expect(() => parseRelativeTime("5 d", baseDate)).toThrow(
|
|
119
|
+
/Invalid relative time format/
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("UTC timezone handling", () => {
|
|
125
|
+
it("should return ISO8601 strings in UTC", () => {
|
|
126
|
+
const result = parseRelativeTime("1d", baseDate, "past");
|
|
127
|
+
expect(result).toMatch(/Z$/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should maintain UTC regardless of input timezone", () => {
|
|
131
|
+
const localDate = new Date("2024-01-15T12:00:00+05:00");
|
|
132
|
+
const result = parseRelativeTime("1d", localDate, "past");
|
|
133
|
+
expect(result).toMatch(/Z$/);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("parseFlexibleTime", () => {
|
|
139
|
+
const baseDate = new Date("2024-01-15T12:00:00.000Z");
|
|
140
|
+
|
|
141
|
+
describe("ISO8601 input", () => {
|
|
142
|
+
it("should parse valid ISO8601 datetime", () => {
|
|
143
|
+
const input = "2024-01-15T12:00:00Z";
|
|
144
|
+
const result = parseFlexibleTime(input, baseDate);
|
|
145
|
+
expect(result).toBe("2024-01-15T12:00:00.000Z");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should normalize ISO8601 to standard format", () => {
|
|
149
|
+
const input = "2024-01-15T12:00:00+00:00";
|
|
150
|
+
const result = parseFlexibleTime(input, baseDate);
|
|
151
|
+
expect(result).toBe("2024-01-15T12:00:00.000Z");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should handle ISO8601 with milliseconds", () => {
|
|
155
|
+
const input = "2024-01-15T12:00:00.123Z";
|
|
156
|
+
const result = parseFlexibleTime(input, baseDate);
|
|
157
|
+
expect(result).toBe("2024-01-15T12:00:00.123Z");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should handle ISO8601 with different timezones", () => {
|
|
161
|
+
const input = "2024-01-15T12:00:00+05:00";
|
|
162
|
+
const result = parseFlexibleTime(input, baseDate);
|
|
163
|
+
expect(result).toBe("2024-01-15T07:00:00.000Z");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("relative time input", () => {
|
|
168
|
+
it("should parse relative time in past direction (default)", () => {
|
|
169
|
+
const result = parseFlexibleTime("1d", baseDate);
|
|
170
|
+
expect(result).toBe("2024-01-14T12:00:00.000Z");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should parse relative time with explicit past direction", () => {
|
|
174
|
+
const result = parseFlexibleTime("2h", baseDate, "past");
|
|
175
|
+
expect(result).toBe("2024-01-15T10:00:00.000Z");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should parse relative time in future direction", () => {
|
|
179
|
+
const result = parseFlexibleTime("3d", baseDate, "future");
|
|
180
|
+
expect(result).toBe("2024-01-18T12:00:00.000Z");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should handle minutes", () => {
|
|
184
|
+
const result = parseFlexibleTime("30m", baseDate, "past");
|
|
185
|
+
expect(result).toBe("2024-01-15T11:30:00.000Z");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should handle hours", () => {
|
|
189
|
+
const result = parseFlexibleTime("6h", baseDate, "past");
|
|
190
|
+
expect(result).toBe("2024-01-15T06:00:00.000Z");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should handle weeks", () => {
|
|
194
|
+
const result = parseFlexibleTime("2w", baseDate, "past");
|
|
195
|
+
expect(result).toBe("2024-01-01T12:00:00.000Z");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should handle months", () => {
|
|
199
|
+
const result = parseFlexibleTime("1M", baseDate, "past");
|
|
200
|
+
expect(result).toBe("2023-12-15T12:00:00.000Z");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("default parameters", () => {
|
|
205
|
+
it("should use current time when 'from' is not provided", () => {
|
|
206
|
+
const result = parseFlexibleTime("1d");
|
|
207
|
+
expect(result).toBeTruthy();
|
|
208
|
+
expect(typeof result).toBe("string");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should default to past direction", () => {
|
|
212
|
+
const result = parseFlexibleTime("1h", baseDate);
|
|
213
|
+
expect(result).toBe("2024-01-15T11:00:00.000Z");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("error handling", () => {
|
|
218
|
+
it("should throw error for invalid input", () => {
|
|
219
|
+
expect(() => parseFlexibleTime("invalid", baseDate)).toThrow(
|
|
220
|
+
/Invalid flexible time format: invalid/
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should throw error for malformed ISO8601", () => {
|
|
225
|
+
expect(() => parseFlexibleTime("2024-13-01", baseDate)).toThrow(
|
|
226
|
+
/Invalid flexible time format/
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should throw error for malformed relative time", () => {
|
|
231
|
+
expect(() => parseFlexibleTime("5x", baseDate)).toThrow(
|
|
232
|
+
/Invalid flexible time format/
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should throw error for empty string", () => {
|
|
237
|
+
expect(() => parseFlexibleTime("", baseDate)).toThrow(
|
|
238
|
+
/Invalid flexible time format/
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("prioritization (ISO8601 first)", () => {
|
|
244
|
+
it("should prioritize ISO8601 parsing over relative time", () => {
|
|
245
|
+
const iso = "2024-01-15T12:00:00Z";
|
|
246
|
+
const result = parseFlexibleTime(iso, baseDate);
|
|
247
|
+
// Should be parsed as ISO8601, not as relative time
|
|
248
|
+
expect(result).toBe("2024-01-15T12:00:00.000Z");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("UTC timezone handling", () => {
|
|
253
|
+
it("should return ISO8601 strings in UTC", () => {
|
|
254
|
+
const result = parseFlexibleTime("1d", baseDate, "past");
|
|
255
|
+
expect(result).toMatch(/Z$/);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should normalize all outputs to UTC", () => {
|
|
259
|
+
const result = parseFlexibleTime("2024-01-15T12:00:00+05:00", baseDate);
|
|
260
|
+
expect(result).toMatch(/Z$/);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import utc from "dayjs/plugin/utc.js";
|
|
3
|
+
|
|
4
|
+
import { ISO8601Schema } from "./iso8601.schema";
|
|
5
|
+
import { RelativeTimeSchema } from "./relative-time.schema";
|
|
6
|
+
|
|
7
|
+
dayjs.extend(utc);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Helper function to parse relative time and calculate the datetime
|
|
11
|
+
* All calculations are performed in UTC timezone
|
|
12
|
+
* @param relativeTime - Relative time string like "1d", "3h", "30m"
|
|
13
|
+
* @param from - Base datetime to calculate from (defaults to now)
|
|
14
|
+
* @param direction - Whether to subtract (past) or add (future)
|
|
15
|
+
* @returns ISO8601 datetime string
|
|
16
|
+
*/
|
|
17
|
+
export function parseRelativeTime(
|
|
18
|
+
relativeTime: string,
|
|
19
|
+
from: Date = new Date(),
|
|
20
|
+
direction: "past" | "future" = "past"
|
|
21
|
+
): string {
|
|
22
|
+
const match = relativeTime.match(/^(\d+)([mhdwM])$/);
|
|
23
|
+
if (!match) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Invalid relative time format: ${relativeTime}. Expected format like '1d', '3h', '30m'`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const [, amount, unit] = match;
|
|
30
|
+
const numberAmount = Number.parseInt(amount, 10);
|
|
31
|
+
|
|
32
|
+
// Map unit to Day.js unit names
|
|
33
|
+
const unitMap: Record<string, dayjs.ManipulateType> = {
|
|
34
|
+
m: "minute",
|
|
35
|
+
h: "hour",
|
|
36
|
+
d: "day",
|
|
37
|
+
w: "week",
|
|
38
|
+
M: "month",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const dayjsUnit = unitMap[unit];
|
|
42
|
+
if (!dayjsUnit) {
|
|
43
|
+
throw new Error(`Invalid time unit: ${unit}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const operation = direction === "past" ? "subtract" : "add";
|
|
47
|
+
return dayjs.utc(from)[operation](numberAmount, dayjsUnit).toISOString();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Helper function to parse flexible time (ISO8601 or relative) to ISO8601
|
|
52
|
+
* All calculations are performed in UTC timezone
|
|
53
|
+
* @param flexibleTime - ISO8601 string or relative time expression
|
|
54
|
+
* @param from - Base datetime for relative time calculations (defaults to now)
|
|
55
|
+
* @param direction - Whether relative times are past or future (defaults to past)
|
|
56
|
+
* @returns ISO8601 datetime string
|
|
57
|
+
*/
|
|
58
|
+
export function parseFlexibleTime(
|
|
59
|
+
flexibleTime: string,
|
|
60
|
+
from: Date = new Date(),
|
|
61
|
+
direction: "past" | "future" = "past"
|
|
62
|
+
): string {
|
|
63
|
+
// Try to parse as ISO8601 first
|
|
64
|
+
const isoResult = ISO8601Schema.safeParse(flexibleTime);
|
|
65
|
+
if (isoResult.success) {
|
|
66
|
+
return new Date(flexibleTime).toISOString();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Try to parse as relative time
|
|
70
|
+
const relativeResult = RelativeTimeSchema.safeParse(flexibleTime);
|
|
71
|
+
if (relativeResult.success) {
|
|
72
|
+
return parseRelativeTime(flexibleTime, from, direction);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Invalid flexible time format: ${flexibleTime}. Expected ISO8601 datetime or relative time expression`
|
|
77
|
+
);
|
|
78
|
+
}
|