smart_date_time 0.1.2

A smart date time parser and formatter for rust
Documentation
#![doc = include_str!("../README.md")]

use chrono::{Datelike,Local, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
use regex::Regex;
use std::collections::HashMap;

// Language-specific keywords for "today" and "now"
lazy_static::lazy_static! {
    static ref TODAY_KEYWORDS: HashMap<&'static str, Vec<&'static str>> = {
        let mut m = HashMap::new();
        m.insert("chinese", vec
!["今天"]);
        m.insert("english", vec!["today", "doday"]);
        m.insert("spanish", vec!["hoy"]);
        m.insert("french", vec!["aujourd'hui"]);
        m.insert("german", vec!["heute"]);
        m.insert("japanese", vec!["今日"]);
        m.insert("korean", vec!["오늘"]);
        m.insert("italian", vec!["oggi"]);
        m.insert("portuguese", vec!["hoje"]);
        m.insert("russian", vec!["сегодня"]);
        m
    };
    
    static ref NOW_KEYWORDS: HashMap<&'static str, Vec<&'static str>> = {
        let mut m = HashMap::new();
        m.insert("chinese", vec!["现在"]);
        m.insert("english", vec!["now"]);
        m.insert("spanish", vec!["ahora"]);
        m.insert("french", vec!["maintenant"]);
        m.insert("german", vec!["jetzt"]);
        m.insert("japanese", vec![""]);
        m.insert("korean", vec!["지금"]);
        m.insert("italian", vec!["ora"]);
        m.insert("portuguese", vec!["agora"]);
        m.insert("russian", vec!["сейчас"]);
        m
    };
}

pub fn format(input: &str, format_str: &str) -> String {
    let now = Local::now();
    let current_date = now.date_naive();
    let current_time = now.time();
    
    // 处理特殊关键词 - 检查所有语言
    let input_lower = input.to_lowercase();
    
    // 检查 "today" 关键词
    for (_, keywords) in TODAY_KEYWORDS.iter() {
        for keyword in keywords {
            if input_lower == *keyword {
                return format_date_time(current_date, current_time, format_str);
            }
        }
    }
    
    // 检查 "now" 关键词
    for (_, keywords) in NOW_KEYWORDS.iter() {
        for keyword in keywords {
            if input_lower == *keyword {
                return format_date_time(current_date, current_time, format_str);
            }
        }
    }
    
    // 尝试解析各种日期时间格式
    if let Some((date, time)) = parse_date_time(input, current_date, current_time) {
        return format_date_time(date, time, format_str);
    }
    
    // 如果无法解析,返回当前日期时间
    format_date_time(current_date, current_time, format_str)
}

fn parse_date_time(
    input: &str,
    default_date: NaiveDate,
    default_time: NaiveTime,
) -> Option<(NaiveDate, NaiveTime)> {
    // 尝试解析纯时间格式
    if let Some(time) = parse_time_only(input) {
        return Some((default_date, time));
    }
    
    // 尝试解析日期格式
    if let Some(date) = parse_date_only(input, default_date) {
        return Some((date, default_time));
    }
    
    // 尝试解析完整日期时间格式
    if let Some((date, time)) = parse_full_date_time(input, default_date, default_time) {
        return Some((date, time));
    }
    
    None
}

fn parse_time_only(input: &str) -> Option<NaiveTime> {
    // 13:42:52 格式
    if let Ok(time) = NaiveTime::parse_from_str(input, "%H:%M:%S") {
        return Some(time);
    }
    
    // 13:42 格式
    if let Ok(time) = NaiveTime::parse_from_str(input, "%H:%M") {
        return Some(time);
    }
    
    // 13点 格式
    let re = Regex::new(r"^(\d{1,2})点$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let Some(hour_match) = captures.get(1) {
            if let Ok(hour) = hour_match.as_str().parse::<u32>() {
                return NaiveTime::from_hms_opt(hour, 0, 0);
            }
        }
    }
    
    // 13点半 格式
    let re = Regex::new(r"^(\d{1,2})点半$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let Some(hour_match) = captures.get(1) {
            if let Ok(hour) = hour_match.as_str().parse::<u32>() {
                return NaiveTime::from_hms_opt(hour, 30, 0);
            }
        }
    }
    
    None
}

fn parse_date_only(input: &str, default_date: NaiveDate) -> Option<NaiveDate> {
    let now = Local::now();
    let current_year = now.year();
    
    // 20号 格式
    let re = Regex::new(r"^(\d{1,2})号$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let Some(day_match) = captures.get(1) {
            if let Ok(day) = day_match.as_str().parse::<u32>() {
                return NaiveDate::from_ymd_opt(current_year, default_date.month(), day);
            }
        }
    }
    
    // 01-01 格式
    if let Ok(date) = NaiveDate::parse_from_str(&format!("{}-{}", current_year, input), "%Y-%m-%d") {
        return Some(date);
    }
    
    // 1.2 格式
    let re = Regex::new(r"^(\d{1,2})\.(\d{1,2})$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let (Some(month_match), Some(day_match)) = (captures.get(1), captures.get(2)) {
            if let (Ok(month), Ok(day)) = (
                month_match.as_str().parse::<u32>(),
                day_match.as_str().parse::<u32>(),
            ) {
                return NaiveDate::from_ymd_opt(current_year, month, day);
            }
        }
    }
    
    // 11.20 格式
    let re = Regex::new(r"^(\d{1,2})\.(\d{1,2})$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let (Some(month_match), Some(day_match)) = (captures.get(1), captures.get(2)) {
            if let (Ok(month), Ok(day)) = (
                month_match.as_str().parse::<u32>(),
                day_match.as_str().parse::<u32>(),
            ) {
                return NaiveDate::from_ymd_opt(current_year, month, day);
            }
        }
    }
    
    None
}

fn parse_full_date_time(
    input: &str,
    _default_date: NaiveDate,
    default_time: NaiveTime,
) -> Option<(NaiveDate, NaiveTime)> {
    // 2025-1-20 格式
    if let Ok(date) = NaiveDate::parse_from_str(input, "%Y-%m-%d") {
        return Some((date, default_time));
    }
    
    // 2025.1.20 格式
    let re = Regex::new(r"^(\d{4})\.(\d{1,2})\.(\d{1,2})$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let (Some(year_match), Some(month_match), Some(day_match)) = 
            (captures.get(1), captures.get(2), captures.get(3)) {
            if let (Ok(year), Ok(month), Ok(day)) = (
                year_match.as_str().parse::<i32>(),
                month_match.as_str().parse::<u32>(),
                day_match.as_str().parse::<u32>(),
            ) {
                if let Some(date) = NaiveDate::from_ymd_opt(year, month, day) {
                    return Some((date, default_time));
                }
            }
        }
    }
    
    // 2025/1/20 格式
    if let Ok(date) = NaiveDate::parse_from_str(input, "%Y/%m/%d") {
        return Some((date, default_time));
    }
    
    // 2025年1月20 格式
    let re = Regex::new(r"^(\d{4})年(\d{1,2})月(\d{1,2})$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let (Some(year_match), Some(month_match), Some(day_match)) = 
            (captures.get(1), captures.get(2), captures.get(3)) {
            if let (Ok(year), Ok(month), Ok(day)) = (
                year_match.as_str().parse::<i32>(),
                month_match.as_str().parse::<u32>(),
                day_match.as_str().parse::<u32>(),
            ) {
                if let Some(date) = NaiveDate::from_ymd_opt(year, month, day) {
                    return Some((date, default_time));
                }
            }
        }
    }
    
    // 2025年1月20日 格式
    let re = Regex::new(r"^(\d{4})年(\d{1,2})月(\d{1,2})日$").unwrap();
    if let Some(captures) = re.captures(input) {
        if let (Some(year_match), Some(month_match), Some(day_match)) = 
            (captures.get(1), captures.get(2), captures.get(3)) {
            if let (Ok(year), Ok(month), Ok(day)) = (
                year_match.as_str().parse::<i32>(),
                month_match.as_str().parse::<u32>(),
                day_match.as_str().parse::<u32>(),
            ) {
                if let Some(date) = NaiveDate::from_ymd_opt(year, month, day) {
                    return Some((date, default_time));
                }
            }
        }
    }
    
    None
}

fn format_date_time(date: NaiveDate, time: NaiveTime, format_str: &str) -> String {
    let _datetime = NaiveDateTime::new(date, time);
    
    // 简单的格式替换
    let result = format_str
        .replace("YYYY", &format!("{:04}", date.year()))
        .replace("MM", &format!("{:02}", date.month()))
        .replace("DD", &format!("{:02}", date.day()))
        .replace("HH", &format!("{:02}", time.hour()))
        .replace("mm", &format!("{:02}", time.minute()))
        .replace("ss", &format!("{:02}", time.second()));
    
    result
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{Datelike, Local, Timelike};
    
    #[test]
    fn test_format() {
        // 这些测试会在实际运行时根据当前日期时间调整
        // 在 main.rs 中有更完整的测试
        let result = format("2025-01-01", "YYYY-MM-DD");
        assert!(result.contains("2025-01-01"));
    }
    
    #[test]
    fn test_multilingual_today_keywords() {
        let now = Local::now();
        let current_date = now.date_naive();
        let expected_date = format!("{:04}-{:02}-{:02}", current_date.year(), current_date.month(), current_date.day());
        
        // 测试所有语言的 "today" 关键词
        let today_keywords = vec![
            "今天",        // Chinese
            "today",       // English
            "doday",       // English (alternative)
            "hoy",         // Spanish
            "aujourd'hui", // French
            "heute",       // German
            "今日",         // Japanese
            "오늘",         // Korean
            "oggi",        // Italian
            "hoje",        // Portuguese
            "сегодня",     // Russian
        ];
        
        for keyword in today_keywords {
            let result = format(keyword, "YYYY-MM-DD");
            assert_eq!(result, expected_date, "Failed for keyword: {}", keyword);
        }
    }
    
    #[test]
    fn test_multilingual_now_keywords() {
        let now = Local::now();
        let current_date = now.date_naive();
        let current_time = now.time();
        let expected_datetime = format!("{:04}-{:02}-{:02} {:02}:{:02}", current_date.year(), current_date.month(), current_date.day(), current_time.hour(), current_time.minute());
        
        // 测试所有语言的 "now" 关键词
        let now_keywords = vec![
            "现在",        // Chinese
            "now",         // English
            "ahora",       // Spanish
            "maintenant",  // French
            "jetzt",       // German
            "",          // Japanese
            "지금",         // Korean
            "ora",         // Italian
            "agora",       // Portuguese
            "сейчас",      // Russian
        ];
        
        for keyword in now_keywords {
            let result = format(keyword, "YYYY-MM-DD HH:mm");
            assert_eq!(result, expected_datetime, "Failed for keyword: {}", keyword);
        }
    }
    
    #[test]
    fn test_date_formats() {
        // 测试各种日期格式
        assert_eq!(format("2025-01-20", "YYYY-MM-DD"), "2025-01-20");
        assert_eq!(format("2025/1/20", "YYYY-MM-DD"), "2025-01-20");
        assert_eq!(format("2025.1.20", "YYYY-MM-DD"), "2025-01-20");
        assert_eq!(format("2025年1月20日", "YYYY-MM-DD"), "2025-01-20");
    }
    
    #[test]
    fn test_time_formats() {
        let now = Local::now();
        let current_date = now.date_naive();
        let expected_prefix = format!("{:04}-{:02}-{:02}", current_date.year(), current_date.month(), current_date.day());
        
        // 测试各种时间格式
        assert_eq!(format("13:42:52", "YYYY-MM-DD HH:mm:ss"), format!("{} 13:42:52", expected_prefix));
        assert_eq!(format("13:42", "YYYY-MM-DD HH:mm"), format!("{} 13:42", expected_prefix));
        assert_eq!(format("13点", "YYYY-MM-DD HH:mm"), format!("{} 13:00", expected_prefix));
        assert_eq!(format("13点半", "YYYY-MM-DD HH:mm"), format!("{} 13:30", expected_prefix));
    }
    
    #[test]
    fn test_custom_formatting() {
        // 测试自定义格式化
        let result = format("2025-01-20", "YYYY年MM月DD日");
        assert_eq!(result, "2025年01月20日");
        
        let result = format("13:42:52", "HH时mm分ss秒");
        assert_eq!(result, "13时42分52秒");
    }
}