safari 1.0.0

Terminal UI for capturing and restoring Safari sessions on macOS
Documentation
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\""#
        );
    }
}