You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

305 lines
9.9 KiB

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<typeof datetimeInputSchema>;
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<DatetimeToolConfig> = {
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<ToolResult> {
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 },
};
}
},
};