next_web_utils/datetime/
date_util.rs

1use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveDateTime, TimeZone, Timelike};
2use once_cell::sync::Lazy;
3
4/// 日期单位枚举
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum DateUnit {
7    MS,     // 毫秒
8    SECOND, // 秒
9    MINUTE, // 分
10    HOUR,   // 小时
11    DAY,    // 天
12    WEEK,   // 周
13    MONTH,  // 月
14    YEAR,   // 年
15}
16
17/// 时间精度
18pub enum Level {
19    YEAR,
20    MONTH,
21    DAY,
22    HOUR,
23    MINUTE,
24    SECOND,
25    MILLISECOND,
26}
27
28/// 中国属相
29pub const CHINESE_ZODIAC: [&str; 12] = [
30    "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪",
31];
32
33/// 星座
34pub const ZODIAC: [&str; 12] = [
35    "水瓶座",
36    "双鱼座",
37    "白羊座",
38    "金牛座",
39    "双子座",
40    "巨蟹座",
41    "狮子座",
42    "处女座",
43    "天秤座",
44    "天蝎座",
45    "射手座",
46    "摩羯座",
47];
48pub const ZODIAC_DATE: [u32; 12] = [20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 23, 22];
49
50static DATE_FORMATS: Lazy<Vec<&'static str>> = Lazy::new(|| {
51    vec![
52        "%Y-%m-%d %H:%M:%S",
53        "%Y/%m/%d %H:%M:%S",
54        "%Y.%m.%d %H:%M:%S",
55        "%Y年%m月%d日 %H时%M分%S秒",
56        "%Y-%m-%d",
57        "%Y/%m/%d",
58        "%Y.%m.%d",
59        "%H:%M:%S",
60        "%H时%M分%S秒",
61        "%Y-%m-%d %H:%M",
62        "%Y-%m-%d %H:%M:%S.%3f",
63        "%Y%m%d%H%M%S",
64        "%Y%m%d%H%M%S%.3f",
65        "%Y%m%d",
66        "%a, %d %b %Y %H:%M:%S %Z",
67        "%a %b %d %H:%M:%S %Z %Y",
68        "%Y-%m-%dT%H:%M:%SZ",
69        "%Y-%m-%dT%H:%M:%S.%3fZ",
70        "%Y-%m-%dT%H:%M:%S%z",
71        "%Y-%m-%dT%H:%M:%S.%3f%z",
72    ]
73});
74
75pub struct DateUtil;
76
77impl DateUtil {
78    /// 获取当前日期时间
79    pub fn date() -> DateTime<Local> {
80        Local::now()
81    }
82
83    /// 从时间戳创建日期(毫秒)
84    pub fn date_ms(timestamp_ms: i64) -> DateTime<Local> {
85        let seconds = timestamp_ms / 1000;
86        let nanos = ((timestamp_ms % 1000) * 1_000_000) as u32;
87        Local.timestamp_opt(seconds, nanos).unwrap()
88    }
89
90    /// 当前时间字符串,格式:yyyy-MM-dd HH:mm:ss
91    pub fn now() -> String {
92        Self::format(Self::date(), "%Y-%m-%d %H:%M:%S")
93    }
94
95    /// 当前日期字符串,格式:yyyy-MM-dd
96    pub fn today() -> String {
97        Self::format(Self::date(), "%Y-%m-%d")
98    }
99
100    /// 自动识别常用格式解析日期字符串
101    pub fn parse(date_str: &str) -> Option<DateTime<Local>> {
102        for format in DATE_FORMATS.iter() {
103            if let Ok(dt) = NaiveDateTime::parse_from_str(date_str, format) {
104                return Some(Local.from_local_datetime(&dt).unwrap());
105            }
106
107            // 尝试只解析日期部分
108            if let Ok(date) = NaiveDate::parse_from_str(date_str, format) {
109                let dt = date.and_hms_opt(0, 0, 0).unwrap();
110                return Some(Local.from_local_datetime(&dt).unwrap());
111            }
112        }
113        None
114    }
115
116    /// 使用指定格式解析日期字符串
117    pub fn parse_with_format(date_str: &str, format: &str) -> Option<DateTime<Local>> {
118        if let Ok(dt) = NaiveDateTime::parse_from_str(date_str, format) {
119            return Some(Local.from_local_datetime(&dt).unwrap());
120        }
121
122        if let Ok(date) = NaiveDate::parse_from_str(date_str, format) {
123            let dt = date.and_hms_opt(0, 0, 0).unwrap();
124            return Some(Local.from_local_datetime(&dt).unwrap());
125        }
126
127        None
128    }
129
130    /// 格式化日期
131    pub fn format(date: DateTime<Local>, fmt: &str) -> String {
132        date.format(fmt).to_string()
133    }
134
135    /// 格式化为标准日期 yyyy-MM-dd
136    pub fn format_date(date: DateTime<Local>) -> String {
137        Self::format(date, "%Y-%m-%d")
138    }
139
140    /// 格式化为标准日期时间 yyyy-MM-dd HH:mm:ss
141    pub fn format_datetime(date: DateTime<Local>) -> String {
142        Self::format(date, "%Y-%m-%d %H:%M:%S")
143    }
144
145    /// 格式化为标准时间 HH:mm:ss
146    pub fn format_time(date: DateTime<Local>) -> String {
147        Self::format(date, "%H:%M:%S")
148    }
149
150    /// 获取年份
151    pub fn year(date: DateTime<Local>) -> i32 {
152        date.year()
153    }
154
155    /// 获取月份 (1-12)
156    pub fn month(date: DateTime<Local>) -> u32 {
157        date.month()
158    }
159
160    /// 获取日期在月中的天数 (1-31)
161    pub fn day(date: DateTime<Local>) -> u32 {
162        date.day()
163    }
164
165    /// 获取小时 (0-23)
166    pub fn hour(date: DateTime<Local>) -> u32 {
167        date.hour()
168    }
169
170    /// 获取分钟 (0-59)
171    pub fn minute(date: DateTime<Local>) -> u32 {
172        date.minute()
173    }
174
175    /// 获取秒 (0-59)
176    pub fn second(date: DateTime<Local>) -> u32 {
177        date.second()
178    }
179
180    /// 获取星期几 (1-7, 周一到周日)
181    pub fn day_of_week(date: DateTime<Local>) -> u32 {
182        date.weekday().number_from_monday()
183    }
184
185    /// 获取一天的开始时间
186    pub fn begin_of_day(date: DateTime<Local>) -> DateTime<Local> {
187        date.date_naive()
188            .and_hms_opt(0, 0, 0)
189            .unwrap()
190            .and_local_timezone(Local)
191            .unwrap()
192    }
193
194    /// 获取一天的结束时间
195    pub fn end_of_day(date: DateTime<Local>) -> DateTime<Local> {
196        date.date_naive()
197            .and_hms_opt(23, 59, 59)
198            .unwrap()
199            .and_local_timezone(Local)
200            .unwrap()
201    }
202
203    /// 获取一月的开始时间
204    pub fn begin_of_month(date: DateTime<Local>) -> DateTime<Local> {
205        let naive_date = NaiveDate::from_ymd_opt(date.year(), date.month(), 1).unwrap();
206        naive_date
207            .and_hms_opt(0, 0, 0)
208            .unwrap()
209            .and_local_timezone(Local)
210            .unwrap()
211    }
212
213    /// 获取一月的结束时间
214    pub fn end_of_month(date: DateTime<Local>) -> DateTime<Local> {
215        let last_day = if date.month() == 12 {
216            NaiveDate::from_ymd_opt(date.year() + 1, 1, 1).unwrap()
217        } else {
218            NaiveDate::from_ymd_opt(date.year(), date.month() + 1, 1).unwrap()
219        }
220        .pred_opt()
221        .unwrap();
222
223        last_day
224            .and_hms_opt(23, 59, 59)
225            .unwrap()
226            .and_local_timezone(Local)
227            .unwrap()
228    }
229
230    /// 日期偏移
231    pub fn offset(date: DateTime<Local>, unit: DateUnit, offset: i64) -> DateTime<Local> {
232        match unit {
233            DateUnit::MS => date + Duration::milliseconds(offset),
234            DateUnit::SECOND => date + Duration::seconds(offset),
235            DateUnit::MINUTE => date + Duration::minutes(offset),
236            DateUnit::HOUR => date + Duration::hours(offset),
237            DateUnit::DAY => date + Duration::days(offset),
238            DateUnit::WEEK => date + Duration::weeks(offset),
239            DateUnit::MONTH => {
240                let month = date.month() as i32 + offset as i32;
241                let year_offset = (month - 1) / 12;
242                let new_month = ((month - 1) % 12 + 1) as u32;
243                let new_year = date.year() + year_offset;
244
245                let new_day = date.day().min(Self::days_in_month(new_year, new_month));
246
247                NaiveDate::from_ymd_opt(new_year, new_month, new_day)
248                    .unwrap()
249                    .and_hms_opt(date.hour(), date.minute(), date.second())
250                    .unwrap()
251                    .and_local_timezone(Local)
252                    .unwrap()
253            }
254            DateUnit::YEAR => {
255                let new_year = date.year() + offset as i32;
256                let naive_date = NaiveDate::from_ymd_opt(
257                    new_year,
258                    date.month(),
259                    date.day().min(Self::days_in_month(new_year, date.month())),
260                )
261                .unwrap();
262
263                naive_date
264                    .and_hms_opt(date.hour(), date.minute(), date.second())
265                    .unwrap()
266                    .and_local_timezone(Local)
267                    .unwrap()
268            }
269        }
270    }
271
272    /// 获取月份天数
273    fn days_in_month(year: i32, month: u32) -> u32 {
274        NaiveDate::from_ymd_opt(
275            if month == 12 { year + 1 } else { year },
276            if month == 12 { 1 } else { month + 1 },
277            1,
278        )
279        .unwrap()
280        .pred_opt()
281        .unwrap()
282        .day()
283    }
284
285    /// 偏移天数
286    pub fn offset_day(date: DateTime<Local>, offset: i64) -> DateTime<Local> {
287        Self::offset(date, DateUnit::DAY, offset)
288    }
289
290    /// 偏移小时
291    pub fn offset_hour(date: DateTime<Local>, offset: i64) -> DateTime<Local> {
292        Self::offset(date, DateUnit::HOUR, offset)
293    }
294
295    /// 偏移分钟
296    pub fn offset_minute(date: DateTime<Local>, offset: i64) -> DateTime<Local> {
297        Self::offset(date, DateUnit::MINUTE, offset)
298    }
299
300    /// 昨天
301    pub fn yesterday() -> DateTime<Local> {
302        Self::offset_day(Self::date(), -1)
303    }
304
305    /// 明天
306    pub fn tomorrow() -> DateTime<Local> {
307        Self::offset_day(Self::date(), 1)
308    }
309
310    /// 上周
311    pub fn last_week() -> DateTime<Local> {
312        Self::offset(Self::date(), DateUnit::WEEK, -1)
313    }
314
315    /// 下周
316    pub fn next_week() -> DateTime<Local> {
317        Self::offset(Self::date(), DateUnit::WEEK, 1)
318    }
319
320    /// 上个月
321    pub fn last_month() -> DateTime<Local> {
322        Self::offset(Self::date(), DateUnit::MONTH, -1)
323    }
324
325    /// 下个月
326    pub fn next_month() -> DateTime<Local> {
327        Self::offset(Self::date(), DateUnit::MONTH, 1)
328    }
329
330    /// 计算两个日期之间的差值
331    pub fn between(start: DateTime<Local>, end: DateTime<Local>, unit: DateUnit) -> i64 {
332        let duration = end.signed_duration_since(start);
333
334        match unit {
335            DateUnit::MS => duration.num_milliseconds(),
336            DateUnit::SECOND => duration.num_seconds(),
337            DateUnit::MINUTE => duration.num_minutes(),
338            DateUnit::HOUR => duration.num_hours(),
339            DateUnit::DAY => duration.num_days(),
340            DateUnit::WEEK => duration.num_days() / 7,
341            DateUnit::MONTH => {
342                let (y1, m1, d1) = (start.year(), start.month(), start.day());
343                let (y2, m2, d2) = (end.year(), end.month(), end.day());
344
345                let months = (y2 - y1) * 12 + (m2 as i32 - m1 as i32);
346
347                let months = if d2 < d1 { months - 1 } else { months };
348                months as i64
349            }
350            DateUnit::YEAR => {
351                let (y1, m1, d1) = (start.year(), start.month(), start.day());
352                let (y2, m2, d2) = (end.year(), end.month(), end.day());
353
354                let years = y2 - y1;
355
356                let years = if m2 < m1 || (m2 == m1 && d2 < d1) {
357                    years - 1
358                } else {
359                    years
360                };
361
362                years as i64
363            }
364        }
365    }
366
367    /// 格式化时间差
368    pub fn format_between(between_ms: i64, level: Level) -> String {
369        let total_seconds = between_ms / 1000;
370        let days = total_seconds / (24 * 3600);
371        let hours = (total_seconds % (24 * 3600)) / 3600;
372        let minutes = (total_seconds % 3600) / 60;
373        let seconds = total_seconds % 60;
374        let ms = between_ms % 1000;
375
376        match level {
377            Level::DAY => format!("{}天", days),
378            Level::HOUR => format!("{}天{}小时", days, hours),
379            Level::MINUTE => format!("{}天{}小时{}分", days, hours, minutes),
380            Level::SECOND => format!("{}天{}小时{}分{}秒", days, hours, minutes, seconds),
381            Level::MILLISECOND => format!(
382                "{}天{}小时{}分{}秒{}毫秒",
383                days, hours, minutes, seconds, ms
384            ),
385            _ => format!("{}天", days),
386        }
387    }
388
389    /// 获取星座
390    pub fn get_zodiac(month: u32, day: u32) -> &'static str {
391        let idx = if day < ZODIAC_DATE[month as usize - 1] {
392            (month - 2 + 12) % 12
393        } else {
394            (month - 1) % 12
395        };
396
397        ZODIAC[idx as usize]
398    }
399
400    /// 获取生肖
401    pub fn get_chinese_zodiac(year: i32) -> &'static str {
402        CHINESE_ZODIAC[((year - 1900) % 12) as usize]
403    }
404
405    /// 计算年龄
406    pub fn age_of_now(birth_date_str: &str) -> Option<u32> {
407        if let Some(birth_date) = Self::parse(birth_date_str) {
408            let now = Self::date();
409            let mut age = now.year() - birth_date.year();
410
411            if now.month() < birth_date.month()
412                || (now.month() == birth_date.month() && now.day() < birth_date.day())
413            {
414                age -= 1;
415            }
416
417            Some(age as u32)
418        } else {
419            None
420        }
421    }
422
423    /// 判断是否闰年
424    pub fn is_leap_year(year: i32) -> bool {
425        (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
426    }
427
428    /// 创建日期范围迭代器
429    pub fn range(start: DateTime<Local>, end: DateTime<Local>, unit: DateUnit) -> DateRange {
430        DateRange {
431            start,
432            end,
433            current: start,
434            unit,
435        }
436    }
437
438    /// 计算两个范围的交集
439    pub fn range_contains(range1: DateRange, range2: DateRange) -> Vec<DateTime<Local>> {
440        let start = if range1.start > range2.start {
441            range1.start
442        } else {
443            range2.start
444        };
445        let end = if range1.end < range2.end {
446            range1.end
447        } else {
448            range2.end
449        };
450
451        if start > end {
452            return vec![];
453        }
454
455        // 使用较小的时间单位
456        let unit = match (range1.unit, range2.unit) {
457            (DateUnit::MS, _) | (_, DateUnit::MS) => DateUnit::MS,
458            (DateUnit::SECOND, _) | (_, DateUnit::SECOND) => DateUnit::SECOND,
459            (DateUnit::MINUTE, _) | (_, DateUnit::MINUTE) => DateUnit::MINUTE,
460            (DateUnit::HOUR, _) | (_, DateUnit::HOUR) => DateUnit::HOUR,
461            (DateUnit::DAY, _) | (_, DateUnit::DAY) => DateUnit::DAY,
462            (DateUnit::WEEK, _) | (_, DateUnit::WEEK) => DateUnit::WEEK,
463            (DateUnit::MONTH, _) | (_, DateUnit::MONTH) => DateUnit::MONTH,
464            (DateUnit::YEAR, DateUnit::YEAR) => DateUnit::YEAR,
465        };
466
467        Self::range_to_list(start, end, unit)
468    }
469
470    /// 计算两个范围的差集 (range2 中有但 range1 中没有的)
471    pub fn range_not_contains(range1: DateRange, range2: DateRange) -> Vec<DateTime<Local>> {
472        let mut result = vec![];
473
474        // 使用范围2的单位
475        let unit = range2.unit;
476
477        // 如果范围2的起始日期早于范围1的起始日期,添加差集部分
478        if range2.start < range1.start {
479            result.append(&mut Self::range_to_list(
480                range2.start,
481                range1.start,
482                unit.clone(),
483            ));
484        }
485
486        // 如果范围2的结束日期晚于范围1的结束日期,添加差集部分
487        if range2.end > range1.end {
488            result.append(&mut Self::range_to_list(range1.end, range2.end, unit));
489        }
490
491        result
492    }
493
494    /// 将日期范围转换为列表
495    pub fn range_to_list(
496        start: DateTime<Local>,
497        end: DateTime<Local>,
498        unit: DateUnit,
499    ) -> Vec<DateTime<Local>> {
500        let mut result = vec![];
501        let mut current = start;
502
503        while current <= end {
504            result.push(current);
505            current = Self::offset(current, unit.clone(), 1);
506        }
507
508        result
509    }
510}
511
512// 为日期范围实现迭代器特性
513impl Iterator for DateRange {
514    type Item = DateTime<Local>;
515
516    fn next(&mut self) -> Option<Self::Item> {
517        if self.current > self.end {
518            return None;
519        }
520
521        let result = self.current;
522        self.current = DateUtil::offset(self.current, self.unit.clone(), 1);
523
524        Some(result)
525    }
526}
527
528/// 创建日期范围 - 返回起始日期和结束日期
529pub struct DateRange {
530    pub start: DateTime<Local>,
531    pub end: DateTime<Local>,
532    pub current: DateTime<Local>,
533    pub unit: DateUnit,
534}