Rustelo/crates/templates/core-lib-template/src/time.rs

243 lines
8.2 KiB
Rust
Raw Normal View History

//! Time utilities (enabled with "time" feature)
#[cfg(feature = "time")]
use chrono::{DateTime, Utc, TimeZone};
/// Time formatting utilities
#[cfg(feature = "time")]
pub mod format {
use super::*;
/// Format timestamp for display in different languages
pub fn display_date(timestamp: &str, language: &str) -> Result<String, chrono::ParseError> {
let dt = DateTime::parse_from_rfc3339(timestamp)?
.with_timezone(&Utc);
match language {
"es" => Ok(dt.format("%d de %B de %Y").to_string()),
_ => Ok(dt.format("%B %d, %Y").to_string()),
}
}
/// Format timestamp with time for display
pub fn display_datetime(timestamp: &str, language: &str) -> Result<String, chrono::ParseError> {
let dt = DateTime::parse_from_rfc3339(timestamp)?
.with_timezone(&Utc);
match language {
"es" => Ok(dt.format("%d de %B de %Y a las %H:%M UTC").to_string()),
_ => Ok(dt.format("%B %d, %Y at %H:%M UTC").to_string()),
}
}
/// Format relative time (e.g., "2 hours ago")
pub fn relative_time(timestamp: &str, language: &str) -> Result<String, chrono::ParseError> {
let dt = DateTime::parse_from_rfc3339(timestamp)?
.with_timezone(&Utc);
let now = Utc::now();
let diff = now.signed_duration_since(dt);
let (value, unit) = if diff.num_seconds() < 60 {
(diff.num_seconds(), match language {
"es" => if diff.num_seconds() == 1 { "segundo" } else { "segundos" },
_ => if diff.num_seconds() == 1 { "second" } else { "seconds" },
})
} else if diff.num_minutes() < 60 {
(diff.num_minutes(), match language {
"es" => if diff.num_minutes() == 1 { "minuto" } else { "minutos" },
_ => if diff.num_minutes() == 1 { "minute" } else { "minutes" },
})
} else if diff.num_hours() < 24 {
(diff.num_hours(), match language {
"es" => if diff.num_hours() == 1 { "hora" } else { "horas" },
_ => if diff.num_hours() == 1 { "hour" } else { "hours" },
})
} else {
(diff.num_days(), match language {
"es" => if diff.num_days() == 1 { "día" } else { "días" },
_ => if diff.num_days() == 1 { "day" } else { "days" },
})
};
let ago = match language {
"es" => "hace",
_ => "ago",
};
Ok(format!("{} {} {}", value, unit, ago))
}
}
/// Time zone utilities
#[cfg(feature = "time")]
pub mod timezone {
use super::*;
/// Convert UTC timestamp to local timezone display
pub fn to_local_display(timestamp: &str) -> Result<String, chrono::ParseError> {
let dt = DateTime::parse_from_rfc3339(timestamp)?;
// In WASM, we can't access system timezone easily, so return UTC
Ok(dt.format("%Y-%m-%d %H:%M UTC").to_string())
}
/// Get common timezone offsets
pub fn common_timezones() -> Vec<(&'static str, i32)> {
vec![
("UTC", 0),
("EST", -5),
("PST", -8),
("CET", 1),
("JST", 9),
]
}
}
/// Duration utilities
#[cfg(feature = "time")]
pub mod duration {
use chrono::Duration;
/// Parse duration from string (e.g., "1h30m", "45s")
pub fn parse_duration(input: &str) -> Result<Duration, String> {
let mut total_seconds = 0i64;
let mut current_number = String::new();
for ch in input.chars() {
if ch.is_ascii_digit() {
current_number.push(ch);
} else if ch.is_alphabetic() {
if current_number.is_empty() {
return Err("No number before unit".to_string());
}
let number: i64 = current_number.parse()
.map_err(|_| "Invalid number")?;
let seconds = match ch {
's' => number,
'm' => number * 60,
'h' => number * 3600,
'd' => number * 86400,
_ => return Err(format!("Unknown unit: {}", ch)),
};
total_seconds += seconds;
current_number.clear();
} else if ch.is_whitespace() {
continue;
} else {
return Err(format!("Invalid character: {}", ch));
}
}
if !current_number.is_empty() {
// Assume seconds if no unit specified
let number: i64 = current_number.parse()
.map_err(|_| "Invalid number")?;
total_seconds += number;
}
Ok(Duration::seconds(total_seconds))
}
/// Format duration for display
pub fn format_duration(duration: Duration, language: &str) -> String {
let total_seconds = duration.num_seconds();
if total_seconds < 60 {
match language {
"es" => format!("{} segundos", total_seconds),
_ => format!("{} seconds", total_seconds),
}
} else if total_seconds < 3600 {
let minutes = total_seconds / 60;
match language {
"es" => format!("{} minutos", minutes),
_ => format!("{} minutes", minutes),
}
} else if total_seconds < 86400 {
let hours = total_seconds / 3600;
let remaining_minutes = (total_seconds % 3600) / 60;
if remaining_minutes == 0 {
match language {
"es" => format!("{} horas", hours),
_ => format!("{} hours", hours),
}
} else {
match language {
"es" => format!("{} horas {} minutos", hours, remaining_minutes),
_ => format!("{} hours {} minutes", hours, remaining_minutes),
}
}
} else {
let days = total_seconds / 86400;
match language {
"es" => format!("{} días", days),
_ => format!("{} days", days),
}
}
}
}
// No-op implementations for when time feature is not enabled
#[cfg(not(feature = "time"))]
pub mod format {
pub fn display_date(_timestamp: &str, _language: &str) -> Result<String, &'static str> {
Err("Time feature not enabled")
}
pub fn display_datetime(_timestamp: &str, _language: &str) -> Result<String, &'static str> {
Err("Time feature not enabled")
}
pub fn relative_time(_timestamp: &str, _language: &str) -> Result<String, &'static str> {
Err("Time feature not enabled")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "time")]
#[test]
fn test_duration_parsing() {
use duration::parse_duration;
assert_eq!(parse_duration("30s").unwrap().num_seconds(), 30);
assert_eq!(parse_duration("5m").unwrap().num_seconds(), 300);
assert_eq!(parse_duration("2h").unwrap().num_seconds(), 7200);
assert_eq!(parse_duration("1d").unwrap().num_seconds(), 86400);
assert_eq!(parse_duration("1h30m").unwrap().num_seconds(), 5400);
assert_eq!(parse_duration("120").unwrap().num_seconds(), 120); // No unit = seconds
assert!(parse_duration("invalid").is_err());
assert!(parse_duration("5x").is_err()); // Invalid unit
}
#[cfg(feature = "time")]
#[test]
fn test_duration_formatting() {
use duration::{parse_duration, format_duration};
let dur = parse_duration("30s").unwrap();
assert_eq!(format_duration(dur, "en"), "30 seconds");
assert_eq!(format_duration(dur, "es"), "30 segundos");
let dur = parse_duration("5m").unwrap();
assert_eq!(format_duration(dur, "en"), "5 minutes");
assert_eq!(format_duration(dur, "es"), "5 minutos");
let dur = parse_duration("2h30m").unwrap();
assert_eq!(format_duration(dur, "en"), "2 hours 30 minutes");
assert_eq!(format_duration(dur, "es"), "2 horas 30 minutos");
}
#[cfg(not(feature = "time"))]
#[test]
fn test_time_feature_disabled() {
let result = format::display_date("2023-01-01T00:00:00Z", "en");
assert!(result.is_err());
}
2026-02-08 20:37:49 +00:00
}