Skip to main content

lc_tools/
datetime.rs

1// lc-tools/src/datetime.rs
2//! Date and time tool for agents.
3//!
4//! Provides date/time query and calculation functionality.
5
6use async_trait::async_trait;
7use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveDateTime, TimeZone, Weekday};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11use lc_core::tools::{BaseTool, Tool, ToolError};
12
13/// DateTime tool input parameters.
14#[derive(Debug, Deserialize, JsonSchema)]
15pub struct DateTimeInput {
16    /// Operation type: "now", "format", "add", "subtract", "weekday", "diff".
17    pub operation: String,
18
19    /// Date/time string (optional, format: YYYY-MM-DD or YYYY-MM-DD HH:MM:SS).
20    pub datetime: Option<String>,
21
22    /// Time unit: "days", "hours", "minutes", "weeks", "months", "years".
23    pub unit: Option<String>,
24
25    /// Value for add/subtract operations.
26    pub value: Option<i64>,
27
28    /// Target date/time for diff operation.
29    pub target: Option<String>,
30}
31
32/// DateTime tool output result.
33#[derive(Debug, Serialize)]
34pub struct DateTimeOutput {
35    /// Operation result.
36    pub result: String,
37
38    /// Operation type.
39    pub operation: String,
40
41    /// Additional details.
42    pub details: Option<String>,
43}
44
45/// DateTime tool for querying and manipulating dates and times.
46pub struct DateTimeTool;
47
48impl DateTimeTool {
49    /// Creates a new DateTimeTool instance.
50    pub fn new() -> Self {
51        Self
52    }
53
54    fn parse_datetime(&self, dt_str: &str) -> Result<DateTime<Local>, ToolError> {
55        if let Ok(dt) = NaiveDateTime::parse_from_str(dt_str, "%Y-%m-%d %H:%M:%S") {
56            return Local
57                .from_local_datetime(&dt)
58                .single()
59                .ok_or_else(|| ToolError::ExecutionFailed("无效的日期时间".to_string()));
60        }
61
62        if let Ok(date) = NaiveDate::parse_from_str(dt_str, "%Y-%m-%d") {
63            let dt = date
64                .and_hms_opt(0, 0, 0)
65                .ok_or_else(|| ToolError::ExecutionFailed("无效的日期".to_string()))?;
66            return Local
67                .from_local_datetime(&dt)
68                .single()
69                .ok_or_else(|| ToolError::ExecutionFailed("无效的日期时间".to_string()));
70        }
71
72        Err(ToolError::ExecutionFailed(format!(
73            "无法解析日期时间: {},请使用格式 YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS",
74            dt_str
75        )))
76    }
77
78    fn get_now(&self) -> DateTimeOutput {
79        let now = Local::now();
80        DateTimeOutput {
81            result: now.format("%Y-%m-%d %H:%M:%S").to_string(),
82            operation: "now".to_string(),
83            details: Some(format!(
84                "星期{},第{}周",
85                match now.weekday() {
86                    Weekday::Mon => "一",
87                    Weekday::Tue => "二",
88                    Weekday::Wed => "三",
89                    Weekday::Thu => "四",
90                    Weekday::Fri => "五",
91                    Weekday::Sat => "六",
92                    Weekday::Sun => "日",
93                },
94                now.iso_week().week()
95            )),
96        }
97    }
98
99    fn format_datetime(&self, dt_str: &str) -> Result<DateTimeOutput, ToolError> {
100        let dt = self.parse_datetime(dt_str)?;
101
102        Ok(DateTimeOutput {
103            result: dt.format("%Y年%m月%d日 %H时%M分%S秒").to_string(),
104            operation: "format".to_string(),
105            details: Some(format!(
106                "星期{},{}年{}周",
107                match dt.weekday() {
108                    Weekday::Mon => "一",
109                    Weekday::Tue => "二",
110                    Weekday::Wed => "三",
111                    Weekday::Thu => "四",
112                    Weekday::Fri => "五",
113                    Weekday::Sat => "六",
114                    Weekday::Sun => "日",
115                },
116                dt.year(),
117                dt.iso_week().week()
118            )),
119        })
120    }
121
122    fn add_time(&self, dt_str: &str, value: i64, unit: &str) -> Result<DateTimeOutput, ToolError> {
123        let dt = self.parse_datetime(dt_str)?;
124
125        let new_dt = match unit {
126            "seconds" => dt + Duration::seconds(value),
127            "minutes" => dt + Duration::minutes(value),
128            "hours" => dt + Duration::hours(value),
129            "days" => dt + Duration::days(value),
130            "weeks" => dt + Duration::weeks(value),
131            "months" => {
132                let months = value as i32;
133                let new_month = dt.month() as i32 + months;
134                let year = dt.year() + (new_month - 1) / 12;
135                let month = ((new_month - 1) % 12 + 1) as u32;
136
137                dt.with_year(year)
138                    .and_then(|d| d.with_month(month))
139                    .ok_or_else(|| ToolError::ExecutionFailed("月份计算失败".to_string()))?
140            }
141            "years" => dt
142                .with_year(dt.year() + value as i32)
143                .ok_or_else(|| ToolError::ExecutionFailed("年份计算失败".to_string()))?,
144            _ => {
145                return Err(ToolError::ExecutionFailed(format!(
146                "不支持的时间单位: {},请使用: seconds, minutes, hours, days, weeks, months, years",
147                unit
148            )))
149            }
150        };
151
152        Ok(DateTimeOutput {
153            result: new_dt.format("%Y-%m-%d %H:%M:%S").to_string(),
154            operation: "add".to_string(),
155            details: Some(format!("{} {} 后", value, self.unit_to_chinese(unit))),
156        })
157    }
158
159    fn subtract_time(
160        &self,
161        dt_str: &str,
162        value: i64,
163        unit: &str,
164    ) -> Result<DateTimeOutput, ToolError> {
165        self.add_time(dt_str, -value, unit)
166    }
167
168    fn get_weekday(&self, dt_str: &str) -> Result<DateTimeOutput, ToolError> {
169        let dt = self.parse_datetime(dt_str)?;
170
171        let weekday = match dt.weekday() {
172            Weekday::Mon => "星期一",
173            Weekday::Tue => "星期二",
174            Weekday::Wed => "星期三",
175            Weekday::Thu => "星期四",
176            Weekday::Fri => "星期五",
177            Weekday::Sat => "星期六",
178            Weekday::Sun => "星期日",
179        };
180
181        Ok(DateTimeOutput {
182            result: weekday.to_string(),
183            operation: "weekday".to_string(),
184            details: Some(format!("{} 是 {}", dt.format("%Y-%m-%d"), weekday)),
185        })
186    }
187
188    fn diff_time(&self, dt1_str: &str, dt2_str: &str) -> Result<DateTimeOutput, ToolError> {
189        let dt1 = self.parse_datetime(dt1_str)?;
190        let dt2 = self.parse_datetime(dt2_str)?;
191
192        let diff = dt2.signed_duration_since(dt1);
193
194        let days = diff.num_days();
195        let hours = diff.num_hours() % 24;
196        let minutes = diff.num_minutes() % 60;
197
198        Ok(DateTimeOutput {
199            result: format!("{}天 {}小时 {}分钟", days.abs(), hours.abs(), minutes.abs()),
200            operation: "diff".to_string(),
201            details: Some(if diff.num_seconds() >= 0 {
202                format!(
203                    "从 {} 到 {} 相隔 {}天 {}小时 {}分钟",
204                    dt1.format("%Y-%m-%d"),
205                    dt2.format("%Y-%m-%d"),
206                    days,
207                    hours,
208                    minutes
209                )
210            } else {
211                format!(
212                    "从 {} 到 {} 相隔 {}天 {}小时 {}分钟",
213                    dt2.format("%Y-%m-%d"),
214                    dt1.format("%Y-%m-%d"),
215                    days.abs(),
216                    hours.abs(),
217                    minutes.abs()
218                )
219            }),
220        })
221    }
222
223    fn unit_to_chinese(&self, unit: &str) -> String {
224        match unit {
225            "seconds" => "秒",
226            "minutes" => "分钟",
227            "hours" => "小时",
228            "days" => "天",
229            "weeks" => "周",
230            "months" => "月",
231            "years" => "年",
232            _ => unit,
233        }
234        .to_string()
235    }
236}
237
238impl Default for DateTimeTool {
239    fn default() -> Self {
240        Self::new()
241    }
242}
243
244#[async_trait]
245impl Tool for DateTimeTool {
246    type Input = DateTimeInput;
247    type Output = DateTimeOutput;
248
249    async fn invoke(&self, input: Self::Input) -> Result<Self::Output, ToolError> {
250        match input.operation.as_str() {
251            "now" => Ok(self.get_now()),
252            "format" => {
253                let dt = input.datetime.ok_or_else(|| {
254                    ToolError::InvalidInput("format 操作需要 datetime 参数".to_string())
255                })?;
256                self.format_datetime(&dt)
257            }
258            "add" => {
259                let dt = input.datetime.ok_or_else(|| {
260                    ToolError::InvalidInput("add 操作需要 datetime 参数".to_string())
261                })?;
262                let value = input.value.ok_or_else(|| {
263                    ToolError::InvalidInput("add 操作需要 value 参数".to_string())
264                })?;
265                let unit = input
266                    .unit
267                    .ok_or_else(|| ToolError::InvalidInput("add 操作需要 unit 参数".to_string()))?;
268                self.add_time(&dt, value, &unit)
269            }
270            "subtract" => {
271                let dt = input.datetime.ok_or_else(|| {
272                    ToolError::InvalidInput("subtract 操作需要 datetime 参数".to_string())
273                })?;
274                let value = input.value.ok_or_else(|| {
275                    ToolError::InvalidInput("subtract 操作需要 value 参数".to_string())
276                })?;
277                let unit = input.unit.ok_or_else(|| {
278                    ToolError::InvalidInput("subtract 操作需要 unit 参数".to_string())
279                })?;
280                self.subtract_time(&dt, value, &unit)
281            }
282            "weekday" => {
283                let dt = input.datetime.ok_or_else(|| {
284                    ToolError::InvalidInput("weekday 操作需要 datetime 参数".to_string())
285                })?;
286                self.get_weekday(&dt)
287            }
288            "diff" => {
289                let dt1 = input.datetime.ok_or_else(|| {
290                    ToolError::InvalidInput("diff 操作需要 datetime 参数".to_string())
291                })?;
292                let dt2 = input.target.ok_or_else(|| {
293                    ToolError::InvalidInput("diff 操作需要 target 参数".to_string())
294                })?;
295                self.diff_time(&dt1, &dt2)
296            }
297            _ => Err(ToolError::InvalidInput(format!(
298                "不支持的操作: {},请使用: now, format, add, subtract, weekday, diff",
299                input.operation
300            ))),
301        }
302    }
303}
304
305#[async_trait]
306impl BaseTool for DateTimeTool {
307    fn name(&self) -> &str {
308        "datetime"
309    }
310
311    fn description(&self) -> &str {
312        "日期时间工具。支持多种操作:
313
314操作类型:
315- now: 获取当前时间
316- format: 格式化日期时间
317- add: 添加时间
318- subtract: 减去时间
319- weekday: 获取星期几
320- diff: 计算时间差
321
322示例:
323- 获取当前时间: {\"operation\": \"now\"}
324- 格式化日期: {\"operation\": \"format\", \"datetime\": \"2024-01-15\"}
325- 添加3天: {\"operation\": \"add\", \"datetime\": \"2024-01-15\", \"value\": 3, \"unit\": \"days\"}
326- 计算差值: {\"operation\": \"diff\", \"datetime\": \"2024-01-01\", \"target\": \"2024-01-15\"}"
327    }
328
329    async fn run(&self, input: String) -> Result<String, ToolError> {
330        let parsed: DateTimeInput = serde_json::from_str(&input)
331            .map_err(|e| ToolError::InvalidInput(format!("JSON 解析失败: {}", e)))?;
332
333        let output = self.invoke(parsed).await?;
334
335        Ok(format!(
336            "{}\n详细信息: {}",
337            output.result,
338            output.details.unwrap_or_default()
339        ))
340    }
341
342    fn args_schema(&self) -> Option<serde_json::Value> {
343        use schemars::schema_for;
344        serde_json::to_value(schema_for!(DateTimeInput)).ok()
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[tokio::test]
353    async fn test_datetime_now() {
354        let tool = DateTimeTool::new();
355
356        let input = DateTimeInput {
357            operation: "now".to_string(),
358            datetime: None,
359            unit: None,
360            value: None,
361            target: None,
362        };
363
364        let result = tool.invoke(input).await.unwrap();
365        assert!(!result.result.is_empty());
366        assert!(result.details.is_some());
367    }
368
369    #[tokio::test]
370    async fn test_datetime_format() {
371        let tool = DateTimeTool::new();
372
373        let input = DateTimeInput {
374            operation: "format".to_string(),
375            datetime: Some("2024-01-15".to_string()),
376            unit: None,
377            value: None,
378            target: None,
379        };
380
381        let result = tool.invoke(input).await.unwrap();
382        assert!(result.result.contains("2024年"));
383        assert!(result.result.contains("01月"));
384        assert!(result.result.contains("15日"));
385    }
386
387    #[tokio::test]
388    async fn test_datetime_add_days() {
389        let tool = DateTimeTool::new();
390
391        let input = DateTimeInput {
392            operation: "add".to_string(),
393            datetime: Some("2024-01-15".to_string()),
394            unit: Some("days".to_string()),
395            value: Some(3),
396            target: None,
397        };
398
399        let result = tool.invoke(input).await.unwrap();
400        assert!(result.result.contains("2024-01-18"));
401    }
402
403    #[tokio::test]
404    async fn test_datetime_weekday() {
405        let tool = DateTimeTool::new();
406
407        // 2024-01-15 是星期一
408        let input = DateTimeInput {
409            operation: "weekday".to_string(),
410            datetime: Some("2024-01-15".to_string()),
411            unit: None,
412            value: None,
413            target: None,
414        };
415
416        let result = tool.invoke(input).await.unwrap();
417        assert_eq!(result.result, "星期一");
418    }
419
420    #[tokio::test]
421    async fn test_datetime_diff() {
422        let tool = DateTimeTool::new();
423
424        let input = DateTimeInput {
425            operation: "diff".to_string(),
426            datetime: Some("2024-01-01".to_string()),
427            unit: None,
428            value: None,
429            target: Some("2024-01-15".to_string()),
430        };
431
432        let result = tool.invoke(input).await.unwrap();
433        assert!(result.result.contains("14天"));
434    }
435}