Files
Jyotisha/frontend/src/lib/location-contract.ts
T
2026-07-25 01:17:14 +08:00

54 lines
2.3 KiB
TypeScript

import { z } from "zod";
function isIsoCalendarDate(value: string) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
if (!match) return false;
const date = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
return date.getUTCFullYear() === Number(match[1])
&& date.getUTCMonth() === Number(match[2]) - 1
&& date.getUTCDate() === Number(match[3]);
}
export const birthLocationSearchQuerySchema = z.object({
q: z.string().trim().min(2).max(160),
birthDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
birthTime: z.string().regex(/^(?:[01]\d|2[0-3]):[0-5]\d$/).optional(),
locale: z.string().trim().min(2).max(16).default("zh"),
limit: z.coerce.number().int().min(1).max(8).default(5),
}).superRefine((value, context) => {
if (value.birthTime && !value.birthDate) context.addIssue({
code: z.ZodIssueCode.custom,
path: ["birthDate"],
message: "birthTime requires birthDate",
});
if (value.birthDate && !isIsoCalendarDate(value.birthDate)) {
context.addIssue({ code: z.ZodIssueCode.custom, path: ["birthDate"], message: "invalid birthDate" });
}
});
export const normalizedBirthLocationSchema = z.object({
provider: z.enum(["geoapify", "china_locations", "mapbox", "geonames"]),
providerPlaceId: z.string().min(1),
placeType: z.string().min(1),
label: z.string().min(1),
countryCode: z.string().nullable(),
countryName: z.string().nullable(),
regionCode: z.string().nullable(),
regionName: z.string().nullable(),
localityName: z.string().nullable(),
districtName: z.string().nullable(),
latitude: z.number().finite().min(-90).max(90),
longitude: z.number().finite().min(-180).max(180),
timezoneId: z.string().min(1),
timezoneOffset: z.number().finite().min(-12).max(14).nullable(),
timezoneSource: z.literal("iana_historical"),
localTimeStatus: z.enum(["resolved", "not_provided", "ambiguous", "nonexistent"]),
});
export type BirthLocationSearchQuery = z.infer<typeof birthLocationSearchQuerySchema>;
export type NormalizedBirthLocation = z.infer<typeof normalizedBirthLocationSchema>;
export type BirthLocationSearchResult =
| { status: "ok"; locations: NormalizedBirthLocation[] }
| { status: "unavailable"; reason: "geoapify_not_configured" | "timezone_service_unavailable" | "provider_unavailable" };