import { z } from "zod"; import type { JSONSchema7 } from "json-schema"; import type { ToolExecutor, ToolContext, ToolResult } from "../../registry"; import type { DatetimeToolConfig } from "./config"; export const datetimeInputSchema = z.object({ operation: z .enum(["now", "format", "parse", "diff", "add", "convert"]) .describe("操作类型: now=获取当前时间, format=格式化, parse=解析时间字符串, diff=计算时间差, add=时间加减, convert=时区转换"), datetime: z .string() .optional() .describe("时间字符串 (ISO 8601 或自定义格式),now 操作时不需要"), format: z .string() .optional() .describe("输出格式 (YYYY-MM-DD HH:mm:ss 等),默认 ISO 8601"), timezone: z .string() .optional() .describe("时区 (如 Asia/Shanghai, UTC, America/New_York)"), targetTimezone: z .string() .optional() .describe("目标时区 (convert 操作使用)"), value: z .number() .optional() .describe("加减的数值 (add 操作使用)"), unit: z .enum(["years", "months", "days", "hours", "minutes", "seconds"]) .optional() .describe("时间单位 (add/diff 操作使用)"), startDatetime: z .string() .optional() .describe("起始时间 (diff 操作使用)"), endDatetime: z .string() .optional() .describe("结束时间 (diff 操作使用)"), }); type DatetimeInput = z.infer; function parseDate(input: string): Date { const asDate = new Date(input); if (!Number.isNaN(asDate.getTime())) return asDate; const normalized = input.replace(/-/g, "/").replace("T", " "); const fallback = new Date(normalized); if (!Number.isNaN(fallback.getTime())) return fallback; throw new Error(`无法解析时间字符串: ${input}`); } function formatDate(date: Date, format: string, timezone?: string): string { const opts: Intl.DateTimeFormatOptions = {}; const hasTz = !!timezone; if (hasTz) opts.timeZone = timezone; if (format === "ISO" || format === "iso") { return date.toISOString(); } const year = date.toLocaleString("en-US", { ...opts, year: "numeric" }); const month = date.toLocaleString("en-US", { ...opts, month: "2-digit" }); const day = date.toLocaleString("en-US", { ...opts, day: "2-digit" }); const hour = date.toLocaleString("en-US", { ...opts, hour: "2-digit", hour12: false }); const minute = date.toLocaleString("en-US", { ...opts, minute: "2-digit" }); const second = date.toLocaleString("en-US", { ...opts, second: "2-digit" }); const y = year; const mo = month; const d = day; const h = hour === "24" ? "00" : hour; const mi = minute; const s = second; return format .replace(/YYYY/g, y) .replace(/MM/g, mo) .replace(/DD/g, d) .replace(/HH/g, h) .replace(/mm/g, mi) .replace(/ss/g, s); } function addTime(date: Date, value: number, unit: string): Date { const result = new Date(date); switch (unit) { case "years": result.setFullYear(result.getFullYear() + value); break; case "months": result.setMonth(result.getMonth() + value); break; case "days": result.setDate(result.getDate() + value); break; case "hours": result.setHours(result.getHours() + value); break; case "minutes": result.setMinutes(result.getMinutes() + value); break; case "seconds": result.setSeconds(result.getSeconds() + value); break; default: throw new Error(`未知时间单位: ${unit}`); } return result; } function diffTime(start: Date, end: Date, unit: string): number { const diffMs = end.getTime() - start.getTime(); switch (unit) { case "years": return diffMs / (365.25 * 24 * 60 * 60 * 1000); case "months": return diffMs / (30.44 * 24 * 60 * 60 * 1000); case "days": return diffMs / (24 * 60 * 60 * 1000); case "hours": return diffMs / (60 * 60 * 1000); case "minutes": return diffMs / (60 * 1000); case "seconds": return diffMs / 1000; default: throw new Error(`未知时间单位: ${unit}`); } } export const datetimeExecutor: ToolExecutor = { buildInputSchema(_config: DatetimeToolConfig): JSONSchema7 { return { type: "object", properties: { operation: { type: "string", enum: ["now", "format", "parse", "diff", "add", "convert"], description: "操作类型", }, datetime: { type: "string", description: "时间字符串" }, format: { type: "string", description: "输出格式" }, timezone: { type: "string", description: "时区" }, targetTimezone: { type: "string", description: "目标时区 (convert)" }, value: { type: "number", description: "加减数值 (add)" }, unit: { type: "string", enum: ["years", "months", "days", "hours", "minutes", "seconds"], description: "时间单位", }, startDatetime: { type: "string", description: "起始时间 (diff)" }, endDatetime: { type: "string", description: "结束时间 (diff)" }, }, required: ["operation"], }; }, buildDescription(config: DatetimeToolConfig): string { return `日期时间工具。支持获取当前时间、格式化、解析、时间差计算、时间加减、时区转换。默认时区: ${config.defaultTimezone}。`; }, async execute( input: unknown, config: DatetimeToolConfig, _ctx: ToolContext, ): Promise { const start = Date.now(); const parsed = datetimeInputSchema.safeParse(input); if (!parsed.success) { return { success: false, data: null, error: `输入参数校验失败: ${parsed.error.message}`, metadata: { durationMs: Date.now() - start }, }; } const inp = parsed.data as DatetimeInput; const tz = inp.timezone ?? config.defaultTimezone; try { switch (inp.operation) { case "now": { const now = new Date(); const formatted = formatDate(now, inp.format ?? "YYYY-MM-DD HH:mm:ss", tz); return { success: true, data: { iso: now.toISOString(), formatted, timezone: tz, timestamp: now.getTime(), }, metadata: { durationMs: Date.now() - start }, }; } case "format": { if (!inp.datetime) throw new Error("format 操作需要 datetime 参数"); const date = parseDate(inp.datetime); const formatted = formatDate(date, inp.format ?? "YYYY-MM-DD HH:mm:ss", tz); return { success: true, data: { original: inp.datetime, formatted, timezone: tz, iso: date.toISOString(), }, metadata: { durationMs: Date.now() - start }, }; } case "parse": { if (!inp.datetime) throw new Error("parse 操作需要 datetime 参数"); const date = parseDate(inp.datetime); return { success: true, data: { original: inp.datetime, iso: date.toISOString(), timestamp: date.getTime(), valid: true, }, metadata: { durationMs: Date.now() - start }, }; } case "diff": { if (!inp.startDatetime || !inp.endDatetime) { throw new Error("diff 操作需要 startDatetime 和 endDatetime"); } if (!inp.unit) throw new Error("diff 操作需要 unit 参数"); const startDate = parseDate(inp.startDatetime); const endDate = parseDate(inp.endDatetime); const diff = diffTime(startDate, endDate, inp.unit); return { success: true, data: { start: inp.startDatetime, end: inp.endDatetime, unit: inp.unit, diff: Number(diff.toFixed(6)), diffMs: endDate.getTime() - startDate.getTime(), }, metadata: { durationMs: Date.now() - start }, }; } case "add": { if (!inp.datetime) throw new Error("add 操作需要 datetime 参数"); if (inp.value === undefined) throw new Error("add 操作需要 value 参数"); if (!inp.unit) throw new Error("add 操作需要 unit 参数"); const date = parseDate(inp.datetime); const result = addTime(date, inp.value, inp.unit); return { success: true, data: { original: inp.datetime, result: result.toISOString(), formatted: formatDate(result, "YYYY-MM-DD HH:mm:ss", tz), value: inp.value, unit: inp.unit, }, metadata: { durationMs: Date.now() - start }, }; } case "convert": { if (!inp.datetime) throw new Error("convert 操作需要 datetime 参数"); if (!inp.targetTimezone) throw new Error("convert 操作需要 targetTimezone 参数"); const date = parseDate(inp.datetime); const sourceFormatted = formatDate(date, inp.format ?? "YYYY-MM-DD HH:mm:ss", tz); const targetFormatted = formatDate(date, inp.format ?? "YYYY-MM-DD HH:mm:ss", inp.targetTimezone); return { success: true, data: { original: inp.datetime, sourceTimezone: tz, targetTimezone: inp.targetTimezone, sourceFormatted, targetFormatted, iso: date.toISOString(), }, metadata: { durationMs: Date.now() - start }, }; } default: throw new Error(`未知操作: ${inp.operation}`); } } catch (e) { return { success: false, data: null, error: e instanceof Error ? e.message : String(e), metadata: { durationMs: Date.now() - start }, }; } }, };