use chrono::{DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
use num_format::{Locale, ToFormattedString};
use crate::Error;
pub fn format_custom_date(date: &DateTime<FixedOffset>, picture: &str) -> Result<String, Error> {
let mut formatted_string = String::new();
let mut inside_brackets = false;
let mut current_pattern = String::new();
let mut i = 0;
let chars: Vec<char> = picture.chars().collect();
while i < chars.len() {
let ch = chars[i];
if ch == '[' && i + 1 < chars.len() && chars[i + 1] == '[' {
formatted_string.push('[');
i += 2; continue;
}
if ch == ']' && i + 1 < chars.len() && chars[i + 1] == ']' {
formatted_string.push(']');
i += 2; continue;
}
if ch == '[' {
inside_brackets = true;
current_pattern.clear(); i += 1;
continue;
}
if ch == ']' {
inside_brackets = false;
let trimmed_pattern = current_pattern
.trim()
.replace("\n", "")
.replace("\t", "")
.replace(" ", "");
formatted_string.push_str(&handle_pattern(&trimmed_pattern, date)?);
current_pattern.clear();
i += 1;
continue;
}
if inside_brackets {
current_pattern.push(ch);
} else {
formatted_string.push(ch);
}
i += 1;
}
Ok(formatted_string)
}
fn handle_pattern(pattern: &str, date: &DateTime<FixedOffset>) -> Result<String, Error> {
match pattern {
"X0001" => Ok(date.iso_week().year().to_string()),
"Y" | "Y0001" | "Y0001,2" => Ok(date.format("%Y").to_string()),
"Y,2" => Ok(date.format("%y").to_string()),
"Y01" => Ok(date.format("%y").to_string()),
"Y0001,2-2" | "Y##01,2-2" => handle_year_last_two_digits(date),
"Y9,999,*" => Ok(date.year().to_formatted_string(&Locale::en)),
"YI" => Ok(to_roman_numerals(date.year())),
"Yi" => Ok(to_roman_numerals_lower(date.year())),
"Yw" => Ok(to_year_in_words(date.year())),
"M01" => Ok(date.format("%m").to_string()),
"m01" => Ok(date.format("%M").to_string()),
"M1,2" => Ok(format!("{:02}", date.month())),
"M" | "M#1" => Ok(date.format("%-m").to_string()),
"MA" => Ok(map_month_to_letter(date.month())),
"MNn" => Ok(date.format("%B").to_string()),
"MNn,3-3" => Ok(date.format("%B").to_string()[..3].to_string()),
"MN" => Ok(date.format("%B").to_string().to_uppercase()),
"D01" | "D#1,2" => Ok(date.format("%d").to_string()),
"D" | "D#1" | "D1" => Ok(date.format("%-d").to_string()),
"Da" => Ok(map_day_to_letter(date.day())),
"Dwo" => Ok(format_day_in_words_with_ordinal(date.day())),
"dwo" => Ok(format_day_in_words_with_ordinal(date.ordinal())),
"D1o" => Ok(format_day_with_ordinal(date.day())),
"d" => Ok(calculate_total_days_in_year(date)),
"W01" => Ok(date.format("%V").to_string()),
"W" => Ok(format!("{}", date.iso_week().week())),
"w" => Ok(handle_week_of_month(date)),
"H01" => Ok(date.format("%H").to_string()),
"h" | "h#1" => Ok(date.format("%-I").to_string()),
"m" => Ok(date.format("%M").to_string()),
"s" | "s01" => Ok(date.format("%S").to_string()),
"f001" => Ok(date.format("%3f").to_string()),
"Z01:01t" | "Z01:01" | "Z0101t" => handle_timezone(date, pattern),
"Z" => Ok(date.format("%:z").to_string()),
"z" => Ok(format!("GMT{}", date.format("%:z"))),
"Z0" => Ok(handle_trimmed_timezone(date)),
s if s.starts_with('Z') && s.chars().filter(|c| c.is_ascii_digit()).count() > 4 => Err(
Error::D3134TooManyTzDigits("Invalid datetime picture string".to_string()),
),
"F0" | "F1" => Ok(date.format("%u").to_string()),
"FNn" => Ok(date.format("%A").to_string()),
"FNn,3-3" => Ok(date.format("%A").to_string()[..3].to_string()),
"F" => Ok(date.format("%A").to_string().to_lowercase()),
"P" | "Pn" => Ok(date.format("%p").to_string().to_lowercase()),
"PN" => Ok(date.format("%p").to_string()),
"E" | "C" => Ok("ISO".to_string()),
"xNn" => Ok(handle_xnn(date)),
"YN" => Err(Error::D3133PictureStringNameModifierError(
"Invalid datetime picture string".to_string(),
)),
s => Err(Error::D3137Error(format!(
"Unsupported datetime picture string: {s}"
))),
}
}
pub fn parse_custom_format(timestamp_str: &str, picture: &str) -> Option<i64> {
match picture {
"" => {
if let Some(millis) = parse_year_only(timestamp_str) {
return Some(millis);
}
if let Some(millis) = parse_date_only(timestamp_str) {
return Some(millis);
}
if let Some(millis) = parse_iso8601_with_timezone(timestamp_str) {
return Some(millis);
}
if let Some(millis) = parse_iso8601_date(timestamp_str) {
return Some(millis);
}
None
}
"[Y1]" => {
if let Ok(year) = timestamp_str.parse::<i32>() {
let parsed_year = NaiveDate::from_ymd_opt(year, 1, 1)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_year, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[YI]" => {
if let Some(year) = roman_to_int(timestamp_str) {
let parsed_year = NaiveDate::from_ymd_opt(year, 1, 1)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_year, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[Yw]" => {
let year = words_to_number(×tamp_str.to_lowercase())?;
let parsed_date = NaiveDate::from_ymd_opt(year, 1, 1)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
Some(Utc.from_utc_datetime(&datetime).timestamp_millis())
}
"[Y]-[M]-[D]" => {
if let Some(millis) = parse_ymd_date(timestamp_str) {
return Some(millis);
}
None
}
"[H]:[m]" => {
let parts: Vec<&str> = timestamp_str.split(':').collect();
if parts.len() == 2 {
let hour: u32 = parts[0].parse().ok()?;
let minute: u32 = parts[1].parse().ok()?;
let now = Utc::now(); let parsed_date = NaiveDate::from_ymd_opt(now.year(), now.month(), now.day())?;
let time = NaiveTime::from_hms_opt(hour, minute, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D1]/[M1]/[Y0001] [h]:[m] [P]" => {
if let Some(parsed_datetime) = parse_custom_date(timestamp_str) {
let utc_datetime = Utc.from_utc_datetime(&parsed_datetime);
return Some(utc_datetime.timestamp_millis());
}
None
}
"[Y0001]-[d001]" => {
if let Some(parsed_datetime) = parse_ordinal_date(timestamp_str) {
let utc_datetime = Utc.from_utc_datetime(&parsed_datetime);
return Some(utc_datetime.timestamp_millis());
}
None
}
"[FNn], [D1o] [MNn] [Y]" => {
if let Some(parsed_datetime) = parse_custom_date_with_weekday(timestamp_str) {
let utc_datetime = Utc.from_utc_datetime(&parsed_datetime);
return Some(utc_datetime.timestamp_millis());
}
None
}
"[FNn,*-3], [DWwo] [MNn] [Y]" => {
if let Some(parsed_datetime) = parse_custom_date_with_weekday_and_ordinal(timestamp_str)
{
let utc_datetime = Utc.from_utc_datetime(&parsed_datetime);
return Some(utc_datetime.timestamp_millis());
}
None
}
"[dwo] day of [Y]" => {
if let Some(parsed_datetime) = parse_ordinal_day_of_year(timestamp_str) {
let utc_datetime = Utc.from_utc_datetime(&parsed_datetime);
return Some(utc_datetime.timestamp_millis());
}
None
}
"[Y]--[d]" => {
if let Some(parsed_datetime) = parse_ordinal_date_with_dashes(timestamp_str) {
let utc_datetime = Utc.from_utc_datetime(&parsed_datetime);
return Some(utc_datetime.timestamp_millis());
}
None
}
"[Dw] [MNn] [Y0001]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() != 3 {
return None;
}
let day_str = remove_day_suffix(parts[0]);
let day = words_to_number(&day_str.to_lowercase())? as u32;
let month = month_name_to_int(parts[1])?;
let year_str = parts[2..].join(" ");
let year = match year_str.parse::<i32>() {
Ok(num) => num,
Err(_) => words_to_number(&year_str)? as i32, };
println!("year {}", year);
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
Some(Utc.from_utc_datetime(&datetime).timestamp_millis())
}
"[D1] [M01] [YI]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() == 3 {
let day: u32 = parts[0].parse().ok()?;
let month: u32 = parts[1].parse().ok()?;
let year = roman_to_int(parts[2])?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D1] [Mi] [YI]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() == 3 {
let day: u32 = parts[0].parse().ok()?;
let month = roman_to_int(parts[1].to_uppercase().as_str())? as u32;
let year = roman_to_int(parts[2])?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[Da] [MA] [Yi]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() == 3 {
let month = roman_month_to_int(parts[1])?;
let year = roman_to_int(parts[2].to_uppercase().as_str())?;
let day = alphabetic_to_day(parts[0])?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D1o] [M#1] [Y0001]" => {
let cleaned_timestamp = timestamp_str
.replace("th", "")
.replace("st", "")
.replace("nd", "")
.replace("rd", "");
let parts: Vec<&str> = cleaned_timestamp.split_whitespace().collect();
if parts.len() == 3 {
let day: u32 = parts[0].parse().ok()?;
let month: u32 = parts[1].parse().ok()?;
let year: i32 = parts[2].parse().ok()?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D1o] [MNn] [Y0001]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() == 3 {
let day = remove_ordinal_suffix(parts[0])?;
let month = month_name_to_int(parts[1])?;
let year = parts[2].parse::<i32>().ok()?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D1] [MNn] [Y0001]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() == 3 {
let day = parts[0].parse::<u32>().ok()?;
let month = month_name_to_int(parts[1])?;
let year = parts[2].parse::<i32>().ok()?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D1] [MNn,3-3] [Y0001]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() == 3 {
let day = parts[0].parse::<u32>().ok()?;
let month = abbreviated_month_to_int(parts[1])?;
let year = parts[2].parse::<i32>().ok()?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D1o] [M01] [Y0001]" => {
let cleaned_timestamp = timestamp_str
.replace("th", "")
.replace("st", "")
.replace("nd", "")
.replace("rd", "");
let parts: Vec<&str> = cleaned_timestamp.split_whitespace().collect();
if parts.len() == 3 {
let day: u32 = parts[0].parse().ok()?;
let month: u32 = parts[1].parse().ok()?;
let year: i32 = parts[2].parse().ok()?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[D01]/[M01]/[Y0001] [H01]:[m01]:[s01]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() != 2 {
return None;
}
let date_part = parts[0];
let time_part = parts[1];
let date_elements: Vec<&str> = date_part.split('/').collect();
if date_elements.len() != 3 {
return None;
}
let day = date_elements[0].parse::<u32>().ok()?;
let month = date_elements[1].parse::<u32>().ok()?;
let year = date_elements[2].parse::<i32>().ok()?;
let time_elements: Vec<&str> = time_part.split(':').collect();
if time_elements.len() != 3 {
return None;
}
let hour = time_elements[0].parse::<u32>().ok()?;
let minute = time_elements[1].parse::<u32>().ok()?;
let second = time_elements[2].parse::<u32>().ok()?;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let parsed_time = NaiveTime::from_hms_opt(hour, minute, second)?;
let datetime = NaiveDateTime::new(parsed_date, parsed_time);
let utc_datetime: DateTime<Utc> = Utc.from_utc_datetime(&datetime);
Some(utc_datetime.timestamp_millis())
}
"[Y0001]-[M01]-[D01]" => {
if let Ok(parsed_date) = NaiveDate::parse_from_str(timestamp_str, "%Y-%m-%d") {
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[Y1]-[M01]-[D01]" => {
if let Ok(parsed_date) = NaiveDate::parse_from_str(timestamp_str, "%Y-%m-%d") {
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
return Some(Utc.from_utc_datetime(&datetime).timestamp_millis());
}
None
}
"[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01].[f001]Z" => {
if let Ok(parsed_datetime) = DateTime::parse_from_rfc3339(timestamp_str) {
return Some(parsed_datetime.timestamp_millis());
}
None
}
"[Dw] [MNn] [Yw]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() < 5 {
return None;
}
let day_str = parse_day_str(parts[0]);
let day = words_to_number(&day_str.to_lowercase())? as u32;
let month = month_name_to_int(parts[1])?;
let year_str = parts[2..].join(" ");
let year = words_to_number(&year_str.to_lowercase())? as i32;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
Some(Utc.from_utc_datetime(&datetime).timestamp_millis())
}
"[DW] [MNn] [Yw]" => {
let parts: Vec<&str> = timestamp_str.split_whitespace().collect();
if parts.len() < 5 {
return None;
}
let day_str = parse_day_str(parts[0]);
let day = words_to_number(&day_str.to_lowercase())? as u32;
let month = month_name_to_int(parts[1])?;
let year_str = parts[2..].join(" ");
let year = words_to_number(&year_str.to_lowercase())? as i32;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
Some(Utc.from_utc_datetime(&datetime).timestamp_millis())
}
"[DW] of [MNn], [Yw]" => {
let cleaned_str = timestamp_str.replace("of", "").replace(",", "");
let parts: Vec<&str> = cleaned_str.split_whitespace().collect();
if parts.len() < 5 {
return None;
}
let day_str = parts[0]; let day = words_to_number(&day_str.to_lowercase())? as u32;
let month = month_name_to_int(parts[1])?;
let year_str = parts[2..].join(" ");
let year = words_to_number(&year_str.to_lowercase())? as i32;
let parsed_date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
let datetime = NaiveDateTime::new(parsed_date, time);
Some(Utc.from_utc_datetime(&datetime).timestamp_millis())
}
_ => None,
}
}
fn parse_day_str(day_str: &str) -> String {
day_str
.split('-')
.map(|part| part.to_lowercase())
.collect::<Vec<_>>()
.join("-")
}
fn handle_year_last_two_digits(date: &DateTime<FixedOffset>) -> Result<String, Error> {
let year = date.year();
let last_two_digits = year % 100; Ok(format!("{:02}", last_two_digits))
}
fn map_day_to_letter(day: u32) -> String {
match day {
1..=26 => (b'a' + (day - 1) as u8) as char,
27..=31 => (b'a' + (day - 27) as u8) as char,
_ => ' ',
}
.to_string()
}
fn map_month_to_letter(month: u32) -> String {
match month {
1..=12 => (b'a' + (month - 1) as u8) as char,
_ => ' ',
}
.to_uppercase()
.to_string()
}
fn calculate_total_days_in_year(date: &DateTime<FixedOffset>) -> String {
let total_days = if date.date_naive().leap_year() {
366 } else {
365 };
total_days.to_string()
}
fn handle_week_of_month(date: &DateTime<FixedOffset>) -> String {
let iso_week = date.iso_week().week();
let month = date.month();
let day_of_month = date.day();
let first_day_of_month = date.with_day(1).unwrap();
let first_weekday_of_month = first_day_of_month.weekday().num_days_from_sunday();
let week_of_month = ((day_of_month + first_weekday_of_month - 1) / 7) + 1;
if (month == 12 && iso_week == 1)
|| (week_of_month == 5 && month == 1 && iso_week == 5)
|| (week_of_month == 1 && first_weekday_of_month == 5 && iso_week == 5)
{
format!("{}", iso_week)
} else if week_of_month == 5 && first_weekday_of_month == 0 {
format!("{}", 1)
} else if month == 1 && iso_week >= 52 && first_weekday_of_month == 0 {
format!("{}", 5)
} else {
format!("{}", week_of_month)
}
}
fn handle_trimmed_timezone(date: &DateTime<FixedOffset>) -> String {
let tz_offset = date.format("%z").to_string();
if tz_offset == "+0000" || tz_offset == "-0000" {
"0".to_string()
} else if tz_offset[3..] == *"00" {
format!(
"{}{}",
&tz_offset[..1],
tz_offset[1..3].trim_start_matches('0')
)
} else {
format!(
"{}{}:{}",
&tz_offset[..1],
tz_offset[1..3].trim_start_matches('0'),
&tz_offset[3..]
)
}
}
fn handle_xnn(date: &DateTime<FixedOffset>) -> String {
let days_from_monday = date.weekday().num_days_from_monday() as i64;
let first_day_of_week = *date - chrono::Duration::days(days_from_monday);
let last_day_of_week = first_day_of_week + chrono::Duration::days(6);
let first_day_month = first_day_of_week.month();
let last_day_month = last_day_of_week.month();
let week_month = if first_day_month != last_day_month {
if last_day_of_week.day() >= 4 {
last_day_month
} else {
first_day_month
}
} else {
first_day_month
};
chrono::NaiveDate::from_ymd_opt(date.year(), week_month, 1)
.expect("Invalid month or day")
.format("%B")
.to_string()
}
fn handle_timezone(date: &DateTime<FixedOffset>, pattern: &str) -> Result<String, Error> {
match pattern {
"Z01:01t" => {
if date.offset().local_minus_utc() == 0 {
Ok("Z".to_string()) } else {
Ok(date.format("%:z").to_string()) }
}
"Z01:01" => {
if date.offset().local_minus_utc() == 0 {
Ok("+00:00".to_string()) } else {
let offset_minutes = date.offset().local_minus_utc() / 60;
let hours = offset_minutes / 60;
let minutes = offset_minutes % 60;
Ok(format!("{:+03}:{:02}", hours, minutes)) }
}
"Z0101t" => {
if date.offset().local_minus_utc() == 0 {
Ok("Z".to_string()) } else {
let offset_minutes = date.offset().local_minus_utc() / 60;
let hours = offset_minutes / 60;
let minutes = offset_minutes % 60;
Ok(format!("{:+03}{:02}", hours, minutes)) }
}
_ => Err(Error::D3134TooManyTzDigits(
"Invalid timezone format".to_string(),
)),
}
}
pub fn format_day_with_ordinal(day: u32) -> String {
match day {
1 | 21 | 31 => format!("{}st", day),
2 | 22 => format!("{}nd", day),
3 | 23 => format!("{}rd", day),
_ => format!("{}th", day),
}
}
fn to_year_in_words(year: i32) -> String {
if year < 0 {
return format!("minus {}", to_year_in_words(-year));
}
let below_20 = [
"",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"ten",
"eleven",
"twelve",
"thirteen",
"fourteen",
"fifteen",
"sixteen",
"seventeen",
"eighteen",
"nineteen",
];
let tens = [
"", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety",
];
let mut result = String::new();
let mut y = year;
if y >= 1000 {
let thousands = y / 1000;
result.push_str(below_20[thousands as usize]);
result.push_str(" thousand");
y %= 1000;
if y > 0 && y < 100 {
result.push_str(" and ");
} else if y > 0 {
result.push(' ');
}
}
if y >= 100 {
let hundreds = y / 100;
result.push_str(below_20[hundreds as usize]);
result.push_str(" hundred");
y %= 100;
if y > 0 {
result.push_str(" and ");
}
}
if y >= 20 {
let t = y / 10;
result.push_str(tens[t as usize]);
y %= 10;
if y > 0 {
result.push('-');
}
}
if y > 0 {
result.push_str(below_20[y as usize]);
}
result.trim().to_string()
}
pub fn to_roman_numerals(year: i32) -> String {
let mut year = year;
let mut roman = String::new();
let numerals = [
(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I"),
];
for &(value, symbol) in &numerals {
while year >= value {
roman.push_str(symbol);
year -= value;
}
}
roman
}
pub fn to_roman_numerals_lower(year: i32) -> String {
to_roman_numerals(year).to_lowercase()
}
pub fn parse_timezone_offset(timezone: &str) -> Option<FixedOffset> {
if timezone == "0000" {
return FixedOffset::east_opt(0); }
if timezone.len() != 5 {
return None;
}
let (hours, minutes) = (
timezone[1..3].parse::<i32>().ok()?,
timezone[3..5].parse::<i32>().ok()?,
);
let total_offset_seconds = (hours * 3600) + (minutes * 60);
match &timezone[0..1] {
"+" => FixedOffset::east_opt(total_offset_seconds),
"-" => FixedOffset::west_opt(total_offset_seconds),
_ => None,
}
}
fn format_day_in_words_with_ordinal(day: u32) -> String {
let word = to_words(day);
if (11..=13).contains(&(day % 100)) {
return format!("{}th", word);
}
if word.ends_with("first") || word.ends_with("second") || word.ends_with("third") {
return word;
}
let suffix = match day % 10 {
1 => "st",
2 => "nd",
3 => "rd",
_ => "th",
};
format!("{}{}", word, suffix)
}
fn to_words(num: u32) -> String {
let below_20 = [
"",
"first",
"second",
"third",
"fourth",
"fifth",
"sixth",
"seventh",
"eighth",
"ninth",
"tenth",
"eleventh",
"twelfth",
"thirteenth",
"fourteenth",
"fifteenth",
"sixteenth",
"seventeenth",
"eighteenth",
"nineteenth",
];
let tens = [
"",
"",
"twentieth",
"thirtieth",
"fortieth",
"fiftieth",
"sixtieth",
"seventieth",
"eightieth",
"ninetieth",
];
let tens_with_units = [
"", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety",
];
if num < 20 {
below_20[num as usize].to_string()
} else if num < 100 {
if num % 10 == 0 {
return tens[(num / 10) as usize].to_string();
}
let ten = tens_with_units[(num / 10) as usize];
let unit = below_20[(num % 10) as usize];
format!("{}-{}", ten, unit)
} else {
num.to_string()
}
}
fn roman_to_int(s: &str) -> Option<i32> {
let mut total = 0;
let mut prev_value = 0;
for c in s.chars().rev() {
let value = match c {
'I' => 1,
'V' => 5,
'X' => 10,
'L' => 50,
'C' => 100,
'D' => 500,
'M' => 1000,
_ => return None, };
if value < prev_value {
total -= value;
} else {
total += value;
}
prev_value = value;
}
Some(total)
}
pub fn roman_month_to_int(month_str: &str) -> Option<u32> {
match month_str.to_uppercase().as_str() {
"I" | "A" => Some(1), "II" | "B" => Some(2), "III" | "C" => Some(3), "IV" | "D" => Some(4), "V" | "E" => Some(5), "VI" | "F" => Some(6), "VII" | "G" => Some(7), "VIII" | "H" => Some(8), "IX" => Some(9), "X" | "J" => Some(10), "XI" | "K" => Some(11), "XII" | "L" => Some(12), _ => None, }
}
fn alphabetic_to_day(s: &str) -> Option<u32> {
let chars: Vec<char> = s.chars().collect();
if chars.len() == 1 {
let day = chars[0].to_ascii_lowercase() as u32 - 'a' as u32 + 1;
return if day <= 31 { Some(day) } else { None };
} else if chars.len() == 2 {
let first = chars[0].to_ascii_lowercase() as u32 - 'a' as u32 + 1;
let second = chars[1].to_ascii_lowercase() as u32 - 'a' as u32 + 1;
let day = first * 26 + second;
return if day <= 31 { Some(day) } else { None };
}
None }
fn remove_day_suffix(day_str: &str) -> String {
if day_str.ends_with("st") {
day_str.trim_end_matches("st").to_string()
} else if day_str.ends_with("nd") {
day_str.trim_end_matches("nd").to_string()
} else if day_str.ends_with("rd") {
day_str.trim_end_matches("rd").to_string()
} else if day_str.ends_with("th") {
day_str.trim_end_matches("th").to_string()
} else {
day_str.to_string()
}
}
fn remove_ordinal_suffix(day_str: &str) -> Option<u32> {
let cleaned_day = day_str.trim_end_matches(|c: char| c.is_alphabetic());
cleaned_day.parse::<u32>().ok()
}
fn month_name_to_int(month_str: &str) -> Option<u32> {
match month_str.to_lowercase().as_str() {
"january" => Some(1),
"february" => Some(2),
"march" => Some(3),
"april" => Some(4),
"may" => Some(5),
"june" => Some(6),
"july" => Some(7),
"august" => Some(8),
"september" => Some(9),
"october" => Some(10),
"november" => Some(11),
"december" => Some(12),
_ => None,
}
}
fn abbreviated_month_to_int(month_str: &str) -> Option<u32> {
match month_str.to_lowercase().as_str() {
"jan" => Some(1),
"feb" => Some(2),
"mar" => Some(3),
"apr" => Some(4),
"may" => Some(5),
"jun" => Some(6),
"jul" => Some(7),
"aug" => Some(8),
"sep" => Some(9),
"oct" => Some(10),
"nov" => Some(11),
"dec" => Some(12),
_ => None,
}
}
fn words_to_number(word_str: &str) -> Option<i32> {
let units = [
("zero", 0),
("one", 1),
("two", 2),
("three", 3),
("four", 4),
("five", 5),
("six", 6),
("seven", 7),
("eight", 8),
("nine", 9),
("ten", 10),
("eleven", 11),
("twelve", 12),
("thirteen", 13),
("fourteen", 14),
("fifteen", 15),
("sixteen", 16),
("seventeen", 17),
("eighteen", 18),
("nineteen", 19),
("first", 1),
("second", 2),
("third", 3),
("fourth", 4),
("fifth", 5),
("sixth", 6),
("seventh", 7),
("eighth", 8),
("ninth", 9),
("tenth", 10),
("eleventh", 11),
("twelfth", 12),
("thirteenth", 13),
("fourteenth", 14),
("fifteenth", 15),
("sixteenth", 16),
("seventeenth", 17),
("eighteenth", 18),
("nineteenth", 19),
("twentieth", 20),
("twenty-first", 21),
("twenty-second", 22),
("twenty-third", 23),
("twenty-fourth", 24),
("twenty-fifth", 25),
("twenty-sixth", 26),
("twenty-seventh", 27),
("twenty-eighth", 28),
("twenty-ninth", 29),
("thirtieth", 30),
("thirty-first", 31),
];
let tens = [
("twenty", 20),
("thirty", 30),
("forty", 40),
("fifty", 50),
("sixty", 60),
("seventy", 70),
("eighty", 80),
("ninety", 90),
];
let scales = [("hundred", 100), ("thousand", 1000)];
let mut result = 0;
let mut current = 0;
let mut last_ten = None;
for word in word_str
.replace(",", "")
.to_lowercase() .split_whitespace()
.flat_map(|w| w.split('-'))
{
if word == "and" {
continue;
}
if let Some(unit) = units.iter().find(|&&(w, _)| w == word).map(|(_, n)| n) {
if let Some(ten) = last_ten {
current += ten + unit;
last_ten = None;
} else {
current += unit;
}
}
else if let Some(ten) = tens.iter().find(|&&(w, _)| w == word).map(|(_, n)| n) {
if let Some(ten_value) = last_ten {
current += ten_value + ten;
} else {
last_ten = Some(ten);
}
}
else if let Some(scale) = scales.iter().find(|&&(w, _)| w == word).map(|(_, n)| n) {
if *scale == 100 {
current *= scale;
} else if *scale == 1000 {
result += current * scale; current = 0; }
}
}
result += current;
Some(result)
}
fn parse_custom_date(date_str: &str) -> Option<NaiveDateTime> {
let parts: Vec<&str> = date_str.split_whitespace().collect();
if parts.len() != 3 {
return None;
}
let date_parts: Vec<&str> = parts[0].split('/').collect();
if date_parts.len() != 3 {
return None;
}
let day: u32 = date_parts[0].parse().ok()?;
let month: u32 = date_parts[1].parse().ok()?;
let year: i32 = date_parts[2].parse().ok()?;
let time_parts: Vec<&str> = parts[1].split(':').collect();
if time_parts.len() != 2 {
return None;
}
let mut hour: u32 = time_parts[0].parse().ok()?;
let minute: u32 = time_parts[1].parse().ok()?;
let am_pm = parts[2].to_lowercase();
if am_pm == "am" {
if hour == 12 {
hour = 0;
}
} else if am_pm == "pm" {
if hour != 12 {
hour += 12;
}
} else {
return None;
}
let date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(hour, minute, 0)?;
Some(NaiveDateTime::new(date, time))
}
fn parse_ordinal_date(date_str: &str) -> Option<NaiveDateTime> {
let parts: Vec<&str> = date_str.split('-').collect();
if parts.len() != 2 {
return None;
}
let year: i32 = parts[0].parse().ok()?;
let ordinal_day: u32 = parts[1].parse().ok()?;
let date = NaiveDate::from_yo_opt(year, ordinal_day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
Some(NaiveDateTime::new(date, time))
}
fn parse_custom_date_with_weekday(date_str: &str) -> Option<NaiveDateTime> {
let parts: Vec<&str> = date_str
.split(|c: char| c == ',' || c.is_whitespace())
.filter(|&x| !x.is_empty())
.collect();
if parts.len() != 4 {
return None;
}
let day_str = parts[1].trim_end_matches(|c: char| c.is_alphabetic());
let day: u32 = day_str.parse().ok()?;
let month = match parts[2].to_lowercase().as_str() {
"january" => 1,
"february" => 2,
"march" => 3,
"april" => 4,
"may" => 5,
"june" => 6,
"july" => 7,
"august" => 8,
"september" => 9,
"october" => 10,
"november" => 11,
"december" => 12,
_ => return None, };
let year: i32 = parts[3].parse().ok()?;
let date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
Some(NaiveDateTime::new(date, time))
}
fn parse_custom_date_with_weekday_and_ordinal(date_str: &str) -> Option<NaiveDateTime> {
let parts: Vec<&str> = date_str
.split(|c: char| c == ',' || c.is_whitespace())
.filter(|&x| !x.is_empty())
.collect();
if parts.len() != 4 {
return None;
}
let day = words_to_number(parts[1])? as u32;
let month = match parts[2].to_lowercase().as_str() {
"january" => 1,
"february" => 2,
"march" => 3,
"april" => 4,
"may" => 5,
"june" => 6,
"july" => 7,
"august" => 8,
"september" => 9,
"october" => 10,
"november" => 11,
"december" => 12,
_ => return None, };
let year: i32 = parts[3].parse().ok()?;
let date = NaiveDate::from_ymd_opt(year, month, day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
Some(NaiveDateTime::new(date, time))
}
fn parse_ordinal_day_of_year(date_str: &str) -> Option<NaiveDateTime> {
let parts: Vec<&str> = date_str.split_whitespace().collect();
if parts.len() < 5 {
return None;
}
let ordinal_day_words = parts[..(parts.len() - 3)].join(" ");
let day_of_year = words_to_number(&ordinal_day_words)? as u32;
let year: i32 = parts.last()?.parse().ok()?;
let parsed_date = NaiveDate::from_yo_opt(year, day_of_year)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
Some(NaiveDateTime::new(parsed_date, time))
}
fn parse_ordinal_date_with_dashes(date_str: &str) -> Option<NaiveDateTime> {
let parts: Vec<&str> = date_str.split("--").collect();
if parts.len() != 2 {
return None;
}
let year: i32 = parts[0].parse().ok()?;
let ordinal_day: u32 = parts[1].parse().ok()?;
let date = NaiveDate::from_yo_opt(year, ordinal_day)?;
let time = NaiveTime::from_hms_opt(0, 0, 0)?;
Some(NaiveDateTime::new(date, time))
}
fn parse_iso8601_date(date_str: &str) -> Option<i64> {
if let Ok(datetime) = DateTime::parse_from_rfc3339(date_str) {
Some(datetime.timestamp_millis())
} else {
None
}
}
fn parse_iso8601_with_timezone(date_str: &str) -> Option<i64> {
let normalized_str = if date_str.ends_with("+0000") {
date_str.replace("+0000", "+00:00")
} else {
date_str.to_string()
};
if let Ok(datetime) = DateTime::parse_from_rfc3339(&normalized_str) {
Some(datetime.timestamp_millis())
} else {
None
}
}
fn parse_date_only(date_str: &str) -> Option<i64> {
if let Ok(naive_date) = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
if let Some(naive_datetime) = naive_date.and_hms_opt(0, 0, 0) {
return Some(Utc.from_utc_datetime(&naive_datetime).timestamp_millis());
}
}
None
}
fn parse_year_only(date_str: &str) -> Option<i64> {
if let Ok(year) = date_str.parse::<i32>() {
if let Some(naive_date) = NaiveDate::from_ymd_opt(year, 1, 1) {
if let Some(naive_datetime) = naive_date.and_hms_opt(0, 0, 0) {
return Some(Utc.from_utc_datetime(&naive_datetime).timestamp_millis());
}
}
}
None
}
fn parse_ymd_date(timestamp_str: &str) -> Option<i64> {
let parts: Vec<&str> = timestamp_str.split('-').collect();
if parts.len() != 3 {
return None; }
let year: i32 = parts[0].parse().ok()?;
let month: u32 = parts[1].parse().ok()?;
let day: u32 = parts[2].parse().ok()?;
if let Some(naive_date) = NaiveDate::from_ymd_opt(year, month, day) {
if let Some(naive_datetime) = naive_date.and_hms_opt(0, 0, 0) {
return Some(Utc.from_utc_datetime(&naive_datetime).timestamp_millis());
}
}
None
}