Skip to main content

rucora_tools/system/
datetime.rs

1//! 日期时间工具
2//!
3//! 获取时间、日期、农历、生肖、星座等信息
4
5use async_trait::async_trait;
6use chrono::{Datelike, Local, NaiveDate, Timelike};
7use rucora_core::{
8    error::ToolError,
9    tool::{Tool, ToolCategory},
10};
11use serde_json::{Value, json};
12
13/// 日期时间工具
14///
15/// 获取当前时间信息,包括公历、农历(干支纪年)、生肖、星座等
16pub struct DatetimeTool;
17
18impl DatetimeTool {
19    /// 创建新的日期时间工具
20    pub fn new() -> Self {
21        Self
22    }
23
24    /// 获取时间信息
25    pub fn get_time_info(&self) -> String {
26        let now = Local::now();
27        let mut info = Vec::new();
28
29        // 公历时间
30        let time_info = format!("当前时间:{}", now.format("%Y-%m-%d %H:%M:%S"));
31        info.push(time_info);
32
33        // 公历日期
34        let year = now.year();
35        let month = now.month();
36        let day = now.day();
37        info.push(format!("公历:{year}年{month}月{day}日"));
38
39        // 农历干支纪年(简化计算)
40        let heavenly_stems = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
41        let earthly_branches = [
42            "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥",
43        ];
44        let year_index = ((year - 4) % 60) as usize;
45        let stem = heavenly_stems[year_index % 10];
46        let branch = earthly_branches[year_index % 12];
47        info.push(format!("农历:{stem}{branch}年"));
48
49        // 生肖
50        let zodiacs = [
51            "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪",
52        ];
53        let zodiac = zodiacs[year_index % 12];
54        info.push(format!("生肖:{zodiac}"));
55
56        // 星座
57        let constellation = Self::get_constellation(month, day);
58        info.push(format!("星座:{constellation}"));
59
60        // 星期
61        let weekday = now.weekday();
62        info.push(format!(
63            "星期:{}",
64            match weekday.number_from_monday() {
65                1 => "一",
66                2 => "二",
67                3 => "三",
68                4 => "四",
69                5 => "五",
70                6 => "六",
71                7 => "日",
72                _ => "未知",
73            }
74        ));
75
76        info.join(", ")
77    }
78
79    /// 根据月日获取星座
80    fn get_constellation(month: u32, day: u32) -> &'static str {
81        match (month, day) {
82            (1, 1..=19) => "摩羯座",
83            (1, _) => "水瓶座",
84            (2, 1..=18) => "水瓶座",
85            (2, _) => "双鱼座",
86            (3, 1..=20) => "双鱼座",
87            (3, _) => "白羊座",
88            (4, 1..=19) => "白羊座",
89            (4, _) => "金牛座",
90            (5, 1..=20) => "金牛座",
91            (5, _) => "双子座",
92            (6, 1..=21) => "双子座",
93            (6, _) => "巨蟹座",
94            (7, 1..=22) => "巨蟹座",
95            (7, _) => "狮子座",
96            (8, 1..=22) => "狮子座",
97            (8, _) => "处女座",
98            (9, 1..=22) => "处女座",
99            (9, _) => "天秤座",
100            (10, 1..=23) => "天秤座",
101            (10, _) => "天蝎座",
102            (11, 1..=22) => "天蝎座",
103            (11, _) => "射手座",
104            (12, 1..=21) => "射手座",
105            (12, _) => "摩羯座",
106            _ => "未知",
107        }
108    }
109
110    /// 获取详细的时间信息(JSON 格式)
111    pub fn get_detailed_info(&self) -> Value {
112        let now = Local::now();
113        let year = now.year();
114        let month = now.month();
115        let day = now.day();
116        let year_index = ((year - 4) % 60) as usize;
117
118        let heavenly_stems = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
119        let earthly_branches = [
120            "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥",
121        ];
122        let zodiacs = [
123            "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪",
124        ];
125
126        json!({
127            "timestamp": now.timestamp(),
128            "iso": now.to_rfc3339(),
129            "local": now.format("%Y-%m-%d %H:%M:%S").to_string(),
130            "date": {
131                "year": year,
132                "month": month,
133                "day": day,
134                "weekday": now.weekday().to_string(),
135                "weekday_num": now.weekday().number_from_monday()
136            },
137            "time": {
138                "hour": now.hour(),
139                "minute": now.minute(),
140                "second": now.second()
141            },
142            "lunar": {
143                "stem": heavenly_stems[year_index % 10],
144                "branch": earthly_branches[year_index % 12],
145                "zodiac": zodiacs[year_index % 12]
146            },
147            "constellation": Self::get_constellation(month, day)
148        })
149    }
150
151    /// 计算两个日期之间的天数。
152    pub fn days_between(&self, date1: &str, date2: &str) -> Result<i32, ToolError> {
153        let d1 = NaiveDate::parse_from_str(date1, "%Y-%m-%d")
154            .map_err(|e| ToolError::Message(format!("解析日期失败:{e}")))?;
155        let d2 = NaiveDate::parse_from_str(date2, "%Y-%m-%d")
156            .map_err(|e| ToolError::Message(format!("解析日期失败:{e}")))?;
157
158        Ok((d2 - d1).num_days() as i32)
159    }
160
161    /// 获取指定日期的详细信息。
162    pub fn get_date_detail(&self, date: &str) -> Result<String, ToolError> {
163        let naive_date = NaiveDate::parse_from_str(date, "%Y-%m-%d")
164            .map_err(|e| ToolError::Message(format!("解析日期失败:{e}")))?;
165
166        let year = naive_date.year();
167        let year_index = ((year - 4) % 60) as usize;
168        let heavenly_stems = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
169        let earthly_branches = [
170            "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥",
171        ];
172        let zodiacs = [
173            "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪",
174        ];
175
176        let weekday = match naive_date.weekday().number_from_monday() {
177            1 => "一",
178            2 => "二",
179            3 => "三",
180            4 => "四",
181            5 => "五",
182            6 => "六",
183            7 => "日",
184            _ => "未知",
185        };
186
187        Ok(format!(
188            "公历:{naive_date}, 农历:{}{}年, 生肖:{}, 星座:{}, 星期:{}",
189            heavenly_stems[year_index % 10],
190            earthly_branches[year_index % 12],
191            zodiacs[year_index % 12],
192            Self::get_constellation(naive_date.month(), naive_date.day()),
193            weekday
194        ))
195    }
196}
197
198impl Default for DatetimeTool {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204#[async_trait]
205impl Tool for DatetimeTool {
206    fn name(&self) -> &str {
207        "datetime"
208    }
209
210    fn description(&self) -> Option<&str> {
211        Some("获取当前日期时间信息、查询指定日期详情、计算两个日期之间的天数")
212    }
213
214    fn categories(&self) -> &'static [ToolCategory] {
215        &[ToolCategory::Basic]
216    }
217
218    fn input_schema(&self) -> Value {
219        json!({
220            "type": "object",
221            "properties": {
222                "format": {
223                    "type": "string",
224                    "description": "输出格式:text(文本)或 json(详细 JSON)",
225                    "enum": ["text", "json"],
226                    "default": "text"
227                },
228                "action": {
229                    "type": "string",
230                    "description": "操作类型:now(当前时间)、detail(指定日期详情)、days_between(日期差)",
231                    "enum": ["now", "detail", "days_between"],
232                    "default": "now"
233                },
234                "date": {
235                    "type": "string",
236                    "description": "日期字符串,格式:YYYY-MM-DD(用于 detail)"
237                },
238                "date1": {
239                    "type": "string",
240                    "description": "第一个日期字符串,格式:YYYY-MM-DD(用于 days_between)"
241                },
242                "date2": {
243                    "type": "string",
244                    "description": "第二个日期字符串,格式:YYYY-MM-DD(用于 days_between)"
245                }
246            }
247        })
248    }
249
250    async fn call(&self, input: Value) -> Result<Value, ToolError> {
251        let format = input
252            .get("format")
253            .and_then(|v| v.as_str())
254            .unwrap_or("text");
255
256        let action = input
257            .get("action")
258            .and_then(|v| v.as_str())
259            .unwrap_or("now");
260
261        match action {
262            "now" => match format {
263                "json" => Ok(self.get_detailed_info()),
264                _ => Ok(json!({
265                    "info": self.get_time_info()
266                })),
267            },
268            "detail" => {
269                let date = input
270                    .get("date")
271                    .and_then(|v| v.as_str())
272                    .ok_or_else(|| ToolError::Message("detail 操作需要 date 参数".to_string()))?;
273                Ok(json!({
274                    "date": date,
275                    "detail": self.get_date_detail(date)?
276                }))
277            }
278            "days_between" => {
279                let date1 = input.get("date1").and_then(|v| v.as_str()).ok_or_else(|| {
280                    ToolError::Message("days_between 操作需要 date1 参数".to_string())
281                })?;
282                let date2 = input.get("date2").and_then(|v| v.as_str()).ok_or_else(|| {
283                    ToolError::Message("days_between 操作需要 date2 参数".to_string())
284                })?;
285
286                Ok(json!({
287                    "date1": date1,
288                    "date2": date2,
289                    "days": self.days_between(date1, date2)?
290                }))
291            }
292            other => Err(ToolError::Message(format!("未知 action:{other}"))),
293        }
294    }
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn test_constellation() {
303        let _tool = DatetimeTool::new();
304
305        // 测试几个已知日期
306        assert_eq!(DatetimeTool::get_constellation(1, 15), "摩羯座");
307        assert_eq!(DatetimeTool::get_constellation(3, 21), "白羊座");
308        assert_eq!(DatetimeTool::get_constellation(6, 22), "巨蟹座");
309        assert_eq!(DatetimeTool::get_constellation(12, 25), "摩羯座");
310    }
311
312    #[test]
313    fn test_time_info() {
314        let tool = DatetimeTool::new();
315        let info = tool.get_time_info();
316
317        // 验证包含关键信息
318        assert!(info.contains("当前时间"));
319        assert!(info.contains("公历"));
320        assert!(info.contains("农历"));
321        assert!(info.contains("生肖"));
322        assert!(info.contains("星座"));
323        assert!(info.contains("星期"));
324    }
325
326    #[test]
327    fn test_days_between() {
328        let tool = DatetimeTool::new();
329        assert_eq!(
330            tool.days_between("2026-05-01", "2026-05-09")
331                .expect("应能计算日期差"),
332            8
333        );
334    }
335
336    #[test]
337    fn test_date_detail() {
338        let tool = DatetimeTool::new();
339        let detail = tool
340            .get_date_detail("2026-05-09")
341            .expect("应能查询日期详情");
342        assert!(detail.contains("公历"));
343        assert!(detail.contains("星期"));
344    }
345}