use chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone};
const LEGACY_TIMESTAMP_FORMAT: &str = "%Y-%m-%d %H:%M";
pub fn current_timestamp() -> String {
Local::now().to_rfc3339_opts(SecondsFormat::Millis, false)
}
pub fn format_timestamp(timestamp: &str) -> String {
parse_timestamp(timestamp)
.map(|parsed| parsed.format("%Y-%m-%d %H:%M").to_string())
.unwrap_or_else(|| timestamp.to_string())
}
pub fn session_file_stem(timestamp: &str) -> String {
parse_timestamp(timestamp)
.map(|parsed| format!("session-{}", parsed.format("%Y%m%d-%H%M%S-%3f")))
.unwrap_or_else(|| format!("session-{}", Local::now().format("%Y%m%d-%H%M%S-%3f")))
}
pub fn timestamp_sort_key(timestamp: &str) -> i64 {
parse_timestamp(timestamp)
.map(|parsed| parsed.timestamp_millis())
.unwrap_or(0)
}
pub fn applescript_escape(value: &str) -> String {
value.replace('\\', "\\\\").replace('"', "\\\"")
}
fn parse_timestamp(timestamp: &str) -> Option<DateTime<Local>> {
DateTime::parse_from_rfc3339(timestamp)
.map(|parsed| parsed.with_timezone(&Local))
.ok()
.or_else(|| {
NaiveDateTime::parse_from_str(timestamp, LEGACY_TIMESTAMP_FORMAT)
.ok()
.and_then(|parsed| Local.from_local_datetime(&parsed).single())
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formats_rfc3339_timestamps_for_display() {
let display = format_timestamp("2026-04-15T11:23:45.000+08:00");
assert_eq!(display, "2026-04-15 11:23");
}
#[test]
fn keeps_legacy_timestamps_sortable() {
assert!(timestamp_sort_key("2026-04-15 11:23") > 0);
}
#[test]
fn escapes_double_quotes_for_applescript() {
assert_eq!(
applescript_escape(r#"https://example.com/?q="tabs""#),
r#"https://example.com/?q=\"tabs\""#
);
}
}