1use 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#[derive(Debug, Deserialize, JsonSchema)]
15pub struct DateTimeInput {
16 pub operation: String,
18
19 pub datetime: Option<String>,
21
22 pub unit: Option<String>,
24
25 pub value: Option<i64>,
27
28 pub target: Option<String>,
30}
31
32#[derive(Debug, Serialize)]
34pub struct DateTimeOutput {
35 pub result: String,
37
38 pub operation: String,
40
41 pub details: Option<String>,
43}
44
45pub struct DateTimeTool;
47
48impl DateTimeTool {
49 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("invalid datetime".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("invalid date".to_string()))?;
66 return Local
67 .from_local_datetime(&dt)
68 .single()
69 .ok_or_else(|| ToolError::ExecutionFailed("invalid datetime".to_string()));
70 }
71
72 Err(ToolError::ExecutionFailed(format!(
73 "failed to parse datetime: {}, use format YYYY-MM-DD or 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("month calculation failed".to_string()))?
140 }
141 "years" => dt
142 .with_year(dt.year() + value as i32)
143 .ok_or_else(|| ToolError::ExecutionFailed("year calculation failed".to_string()))?,
144 _ => {
145 return Err(ToolError::ExecutionFailed(format!(
146 "unsupported time unit: {}, use: 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(
255 "format operation requires a datetime parameter".to_string(),
256 )
257 })?;
258 self.format_datetime(&dt)
259 }
260 "add" => {
261 let dt = input.datetime.ok_or_else(|| {
262 ToolError::InvalidInput(
263 "add operation requires a datetime parameter".to_string(),
264 )
265 })?;
266 let value = input.value.ok_or_else(|| {
267 ToolError::InvalidInput("add operation requires a value parameter".to_string())
268 })?;
269 let unit = input.unit.ok_or_else(|| {
270 ToolError::InvalidInput("add operation requires a unit parameter".to_string())
271 })?;
272 self.add_time(&dt, value, &unit)
273 }
274 "subtract" => {
275 let dt = input.datetime.ok_or_else(|| {
276 ToolError::InvalidInput(
277 "subtract operation requires a datetime parameter".to_string(),
278 )
279 })?;
280 let value = input.value.ok_or_else(|| {
281 ToolError::InvalidInput(
282 "subtract operation requires a value parameter".to_string(),
283 )
284 })?;
285 let unit = input.unit.ok_or_else(|| {
286 ToolError::InvalidInput(
287 "subtract operation requires a unit parameter".to_string(),
288 )
289 })?;
290 self.subtract_time(&dt, value, &unit)
291 }
292 "weekday" => {
293 let dt = input.datetime.ok_or_else(|| {
294 ToolError::InvalidInput(
295 "weekday operation requires a datetime parameter".to_string(),
296 )
297 })?;
298 self.get_weekday(&dt)
299 }
300 "diff" => {
301 let dt1 = input.datetime.ok_or_else(|| {
302 ToolError::InvalidInput(
303 "diff operation requires a datetime parameter".to_string(),
304 )
305 })?;
306 let dt2 = input.target.ok_or_else(|| {
307 ToolError::InvalidInput(
308 "diff operation requires a target parameter".to_string(),
309 )
310 })?;
311 self.diff_time(&dt1, &dt2)
312 }
313 _ => Err(ToolError::InvalidInput(format!(
314 "unsupported operation: {}, use: now, format, add, subtract, weekday, diff",
315 input.operation
316 ))),
317 }
318 }
319}
320
321#[async_trait]
322impl BaseTool for DateTimeTool {
323 fn name(&self) -> &str {
324 "datetime"
325 }
326
327 fn description(&self) -> &str {
328 "日期时间工具。支持多种操作:
329
330操作类型:
331- now: 获取当前时间
332- format: 格式化日期时间
333- add: 添加时间
334- subtract: 减去时间
335- weekday: 获取星期几
336- diff: 计算时间差
337
338示例:
339- 获取当前时间: {\"operation\": \"now\"}
340- 格式化日期: {\"operation\": \"format\", \"datetime\": \"2024-01-15\"}
341- 添加3天: {\"operation\": \"add\", \"datetime\": \"2024-01-15\", \"value\": 3, \"unit\": \"days\"}
342- 计算差值: {\"operation\": \"diff\", \"datetime\": \"2024-01-01\", \"target\": \"2024-01-15\"}"
343 }
344
345 async fn run(&self, input: String) -> Result<String, ToolError> {
346 let parsed: DateTimeInput = serde_json::from_str(&input)
347 .map_err(|e| ToolError::InvalidInput(format!("JSON parse failed: {}", e)))?;
348
349 let output = self.invoke(parsed).await?;
350
351 Ok(format!(
352 "{}\n详细信息: {}",
353 output.result,
354 output.details.unwrap_or_default()
355 ))
356 }
357
358 fn args_schema(&self) -> Option<serde_json::Value> {
359 use schemars::schema_for;
360 serde_json::to_value(schema_for!(DateTimeInput)).ok()
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367
368 #[tokio::test]
369 async fn test_datetime_now() {
370 let tool = DateTimeTool::new();
371
372 let input = DateTimeInput {
373 operation: "now".to_string(),
374 datetime: None,
375 unit: None,
376 value: None,
377 target: None,
378 };
379
380 let result = tool.invoke(input).await.unwrap();
381 assert!(!result.result.is_empty());
382 assert!(result.details.is_some());
383 }
384
385 #[tokio::test]
386 async fn test_datetime_format() {
387 let tool = DateTimeTool::new();
388
389 let input = DateTimeInput {
390 operation: "format".to_string(),
391 datetime: Some("2024-01-15".to_string()),
392 unit: None,
393 value: None,
394 target: None,
395 };
396
397 let result = tool.invoke(input).await.unwrap();
398 assert!(result.result.contains("2024年"));
399 assert!(result.result.contains("01月"));
400 assert!(result.result.contains("15日"));
401 }
402
403 #[tokio::test]
404 async fn test_datetime_add_days() {
405 let tool = DateTimeTool::new();
406
407 let input = DateTimeInput {
408 operation: "add".to_string(),
409 datetime: Some("2024-01-15".to_string()),
410 unit: Some("days".to_string()),
411 value: Some(3),
412 target: None,
413 };
414
415 let result = tool.invoke(input).await.unwrap();
416 assert!(result.result.contains("2024-01-18"));
417 }
418
419 #[tokio::test]
420 async fn test_datetime_weekday() {
421 let tool = DateTimeTool::new();
422
423 let input = DateTimeInput {
425 operation: "weekday".to_string(),
426 datetime: Some("2024-01-15".to_string()),
427 unit: None,
428 value: None,
429 target: None,
430 };
431
432 let result = tool.invoke(input).await.unwrap();
433 assert_eq!(result.result, "星期一");
434 }
435
436 #[tokio::test]
437 async fn test_datetime_diff() {
438 let tool = DateTimeTool::new();
439
440 let input = DateTimeInput {
441 operation: "diff".to_string(),
442 datetime: Some("2024-01-01".to_string()),
443 unit: None,
444 value: None,
445 target: Some("2024-01-15".to_string()),
446 };
447
448 let result = tool.invoke(input).await.unwrap();
449 assert!(result.result.contains("14天"));
450 }
451}