#![doc = include_str!("../README.md")]
use chrono::{Datelike,Local, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
use regex::Regex;
use std::collections::HashMap;
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();
for (_, keywords) in TODAY_KEYWORDS.iter() {
for keyword in keywords {
if input_lower == *keyword {
return format_date_time(current_date, current_time, format_str);
}
}
}
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> {
if let Ok(time) = NaiveTime::parse_from_str(input, "%H:%M:%S") {
return Some(time);
}
if let Ok(time) = NaiveTime::parse_from_str(input, "%H:%M") {
return Some(time);
}
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);
}
}
}
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();
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);
}
}
}
if let Ok(date) = NaiveDate::parse_from_str(&format!("{}-{}", current_year, input), "%Y-%m-%d") {
return Some(date);
}
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);
}
}
}
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)> {
if let Ok(date) = NaiveDate::parse_from_str(input, "%Y-%m-%d") {
return Some((date, default_time));
}
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));
}
}
}
}
if let Ok(date) = NaiveDate::parse_from_str(input, "%Y/%m/%d") {
return Some((date, default_time));
}
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));
}
}
}
}
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() {
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());
let today_keywords = vec![
"今天", "today", "doday", "hoy", "aujourd'hui", "heute", "今日", "오늘", "oggi", "hoje", "сегодня", ];
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());
let now_keywords = vec![
"现在", "now", "ahora", "maintenant", "jetzt", "今", "지금", "ora", "agora", "сейчас", ];
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秒");
}
}