package tools import ( "context" "fmt" "strconv" "strings" "time" "unicode" "github.com/yourname/cyrene-ai/tool-engine/internal/model" ) // DateTimeTool provides date/time operations for the LLM. type DateTimeTool struct{} // NewDateTimeTool creates a date/time tool. func NewDateTimeTool() *DateTimeTool { return &DateTimeTool{} } // Definition returns the tool definition for LLM function calling. func (t *DateTimeTool) Definition() model.ToolDefinition { return model.ToolDefinition{ Name: "datetime", Description: "日期时间工具。获取当前时间、格式化日期、日期加减、计算日期差、查看可用时区。", Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "action": map[string]interface{}{ "type": "string", "enum": []string{"now", "format", "add", "diff", "timezone_list"}, "description": "操作类型。now: 获取当前时间;format: 格式化日期;add: 日期加减;diff: 计算两个日期的差值;timezone_list: 列出常用时区", }, "format": map[string]interface{}{ "type": "string", "description": "日期格式串(Go风格)。默认 \"2006-01-02 15:04:05\"。常用: \"2006-01-02\"(仅日期)、\"15:04:05\"(仅时间)", }, "timezone": map[string]interface{}{ "type": "string", "description": "时区标识,如 \"Asia/Shanghai\"、\"America/New_York\"、\"UTC\"。默认使用服务器本地时区", }, "date": map[string]interface{}{ "type": "string", "description": "基准日期,格式为 \"2006-01-02 15:04:05\" 或 \"2006-01-02\"", }, "duration": map[string]interface{}{ "type": "string", "description": "时长字符串,如 \"24h\"、\"7d\"、\"30m\"、\"1h30m\"。支持单位: s(秒), m(分钟), h(小时), d(天), w(周), M(月), y(年)", }, "date2": map[string]interface{}{ "type": "string", "description": "第二个日期(用于 diff 操作),格式同 date", }, }, "required": []string{"action"}, }, } } // Execute performs date/time operations. func (t *DateTimeTool) Execute(ctx context.Context, arguments map[string]interface{}) (*model.ToolResult, error) { action, ok := arguments["action"].(string) if !ok || action == "" { return &model.ToolResult{ ID: "", Error: "缺少 action 参数", }, nil } switch action { case "now": return t.handleNow(arguments) case "format": return t.handleFormat(arguments) case "add": return t.handleAdd(arguments) case "diff": return t.handleDiff(arguments) case "timezone_list": return t.handleTimezoneList() default: return &model.ToolResult{ ID: "", Error: fmt.Sprintf("未知操作: %s,支持: now, format, add, diff, timezone_list", action), }, nil } } func (t *DateTimeTool) handleNow(arguments map[string]interface{}) (*model.ToolResult, error) { tz, err := t.getTimezone(arguments) if err != nil { return &model.ToolResult{ID: "", Error: err.Error()}, nil } format := t.getFormat(arguments) now := time.Now().In(tz) return &model.ToolResult{ ID: "", Output: fmt.Sprintf("当前时间: %s\n时区: %s\nUnix时间戳: %d", now.Format(format), tz.String(), now.Unix()), }, nil } func (t *DateTimeTool) handleFormat(arguments map[string]interface{}) (*model.ToolResult, error) { dateStr, _ := arguments["date"].(string) if dateStr == "" { return &model.ToolResult{ID: "", Error: "format 操作需要 date 参数"}, nil } parsed, err := t.parseDate(dateStr) if err != nil { return &model.ToolResult{ID: "", Error: fmt.Sprintf("日期解析失败: %v", err)}, nil } tz, err := t.getTimezone(arguments) if err != nil { return &model.ToolResult{ID: "", Error: err.Error()}, nil } format := t.getFormat(arguments) formatted := parsed.In(tz).Format(format) return &model.ToolResult{ ID: "", Output: fmt.Sprintf("原始: %s\n格式化: %s\n时区: %s", dateStr, formatted, tz.String()), }, nil } func (t *DateTimeTool) handleAdd(arguments map[string]interface{}) (*model.ToolResult, error) { durationStr, _ := arguments["duration"].(string) if durationStr == "" { return &model.ToolResult{ID: "", Error: "add 操作需要 duration 参数"}, nil } dateStr, _ := arguments["date"].(string) var base time.Time if dateStr != "" { var err error base, err = t.parseDate(dateStr) if err != nil { return &model.ToolResult{ID: "", Error: fmt.Sprintf("日期解析失败: %v", err)}, nil } } else { tz, _ := t.getTimezone(arguments) base = time.Now().In(tz) } dur, err := t.parseDuration(durationStr) if err != nil { return &model.ToolResult{ID: "", Error: fmt.Sprintf("时长解析失败: %v", err)}, nil } tz, _ := t.getTimezone(arguments) result := base.In(tz) months := extractDurationUnit(durationStr, 'M') years := extractDurationUnit(durationStr, 'y') if months != 0 || years != 0 { result = result.AddDate(years, months, 0) } if dur != 0 { result = result.Add(dur) } format := t.getFormat(arguments) return &model.ToolResult{ ID: "", Output: fmt.Sprintf("基准日期: %s\n操作: %s\n结果: %s", base.In(tz).Format(format), durationStr, result.Format(format)), }, nil } func (t *DateTimeTool) handleDiff(arguments map[string]interface{}) (*model.ToolResult, error) { dateStr, _ := arguments["date"].(string) date2Str, _ := arguments["date2"].(string) if dateStr == "" || date2Str == "" { return &model.ToolResult{ID: "", Error: "diff 操作需要 date 和 date2 参数"}, nil } d1, err := t.parseDate(dateStr) if err != nil { return &model.ToolResult{ID: "", Error: fmt.Sprintf("date 解析失败: %v", err)}, nil } d2, err := t.parseDate(date2Str) if err != nil { return &model.ToolResult{ID: "", Error: fmt.Sprintf("date2 解析失败: %v", err)}, nil } diff := d2.Sub(d1) absDiff := diff if absDiff < 0 { absDiff = -absDiff } days := int(absDiff.Hours() / 24) hours := int(absDiff.Hours()) % 24 minutes := int(absDiff.Minutes()) % 60 seconds := int(absDiff.Seconds()) % 60 sign := "" if diff < 0 { sign = "-" } return &model.ToolResult{ ID: "", Output: fmt.Sprintf("日期1: %s\n日期2: %s\n差值: %s%d天 %d小时 %d分钟 %d秒 (总计 %s%.0f秒)", dateStr, date2Str, sign, days, hours, minutes, seconds, sign, absDiff.Seconds()), }, nil } func (t *DateTimeTool) handleTimezoneList() (*model.ToolResult, error) { zones := []string{ "UTC", "Asia/Shanghai (北京时间)", "Asia/Tokyo (东京时间)", "Asia/Seoul (首尔时间)", "Asia/Singapore (新加坡时间)", "Asia/Kolkata (印度时间)", "Asia/Dubai (迪拜时间)", "Europe/London (伦敦时间)", "Europe/Paris (巴黎时间)", "Europe/Berlin (柏林时间)", "Europe/Moscow (莫斯科时间)", "America/New_York (纽约时间)", "America/Chicago (芝加哥时间)", "America/Denver (丹佛时间)", "America/Los_Angeles (洛杉矶时间)", "America/Sao_Paulo (圣保罗时间)", "Australia/Sydney (悉尼时间)", "Pacific/Auckland (奥克兰时间)", } var result strings.Builder result.WriteString("常用时区列表:\n\n") for i, z := range zones { result.WriteString(fmt.Sprintf(" %2d. %s\n", i+1, z)) } return &model.ToolResult{ ID: "", Output: result.String(), }, nil } func (t *DateTimeTool) getTimezone(arguments map[string]interface{}) (*time.Location, error) { tzName, _ := arguments["timezone"].(string) if tzName == "" { return time.Local, nil } loc, err := time.LoadLocation(tzName) if err != nil { return nil, fmt.Errorf("无效时区: %s", tzName) } return loc, nil } func (t *DateTimeTool) getFormat(arguments map[string]interface{}) string { format, _ := arguments["format"].(string) if format == "" { return "2006-01-02 15:04:05" } return format } func (t *DateTimeTool) parseDate(s string) (time.Time, error) { formats := []string{ "2006-01-02 15:04:05", "2006-01-02T15:04:05Z", "2006-01-02T15:04:05", "2006-01-02", "2006/01/02 15:04:05", "2006/01/02", time.RFC3339, time.RFC3339Nano, } for _, f := range formats { if t, err := time.Parse(f, s); err == nil { return t, nil } } return time.Time{}, fmt.Errorf("无法解析日期: %s", s) } func (t *DateTimeTool) parseDuration(s string) (time.Duration, error) { if d, err := time.ParseDuration(s); err == nil { return d, nil } var total time.Duration remaining := s for len(remaining) > 0 { numStart := 0 for numStart < len(remaining) && !unicode.IsDigit(rune(remaining[numStart])) && remaining[numStart] != '-' { numStart++ } if numStart >= len(remaining) { break } numEnd := numStart for numEnd < len(remaining) && (unicode.IsDigit(rune(remaining[numEnd])) || remaining[numEnd] == '.') { numEnd++ } val, err := strconv.ParseFloat(remaining[numStart:numEnd], 64) if err != nil { return 0, fmt.Errorf("无效时长数字: %s", remaining[numStart:numEnd]) } unitEnd := numEnd for unitEnd < len(remaining) && unicode.IsLetter(rune(remaining[unitEnd])) { unitEnd++ } unit := remaining[numEnd:unitEnd] switch unit { case "s": total += time.Duration(val * float64(time.Second)) case "m": total += time.Duration(val * float64(time.Minute)) case "h": total += time.Duration(val * float64(time.Hour)) case "d": total += time.Duration(val * 24 * float64(time.Hour)) case "w": total += time.Duration(val * 7 * 24 * float64(time.Hour)) } remaining = remaining[unitEnd:] } return total, nil } func extractDurationUnit(s string, unit byte) int { for i := 0; i < len(s); i++ { if s[i] == unit { j := i - 1 for j >= 0 && (unicode.IsDigit(rune(s[j])) || s[j] == '.') { j-- } numStr := s[j+1 : i] val, err := strconv.Atoi(numStr) if err != nil { return 0 } return val } } return 0 }