//! Clock times as the user wants to read them: 12- or 24-hour, or whatever
//! the locale prefers.
use std::time::SystemTime;
use chrono::{DateTime, Datelike as _, Local};
use crate::settings::{self, TimeFormat};
/// Whether times show as `2:32 PM` rather than `14:32`.
pub fn uses_12_hours() -> bool {
match settings::get(|settings| settings.time_format) {
TimeFormat::Hour12 => true,
TimeFormat::Hour24 => false,
TimeFormat::System => locale_uses_12_hours(),
}
}
/// The locales that write the time with AM and PM.
fn locale_uses_12_hours() -> bool {
let locale = ["LC_ALL", "LC_TIME", "LANG"]
.iter()
.find_map(|key| std::env::var(key).ok().filter(|value| !value.is_empty()))
.unwrap_or_default();
["en_US", "en_CA", "en_AU", "en_NZ", "en_PH", "en_IN", "es_MX", "es_US", "hi_IN", "ar_", "ko_KR", "fil_PH"]
.iter()
.any(|prefix| locale.starts_with(prefix))
}
/// `14:32` or `2:32 PM`.
pub fn clock(time: SystemTime) -> String {
let time: DateTime<Local> = time.into();
if uses_12_hours() { time.format("%-I:%M %p").to_string() } else { time.format("%H:%M").to_string() }
}
/// A moment near now, as briefly as it reads clearly: `14:32`, `tomorrow
/// 09:00`, `Fri 09:00`, `Oct 21, 09:00`.
pub fn when(time: SystemTime) -> String {
let local: DateTime<Local> = time.into();
let today = Local::now().date_naive();
let days = (local.date_naive() - today).num_days();
let clock = clock(time);
match days {
0 => clock,
1 => format!("tomorrow {clock}"),
-1 => format!("yesterday {clock}"),
2..=6 => format!("{} {clock}", local.format("%a")),
_ if local.year() == today.year() => format!("{}, {clock}", local.format("%b %-d")),
_ => format!("{}, {clock}", local.format("%b %-d %Y")),
}
}
/// The next time the clock shows `hour`:00, `days` days from today.
pub fn at_hour(days: i64, hour: u32) -> SystemTime {
let date = Local::now().date_naive() + chrono::Duration::days(days);
date.and_hms_opt(hour, 0, 0)
.and_then(|naive| naive.and_local_timezone(Local).earliest())
.map(SystemTime::from)
.unwrap_or_else(SystemTime::now)
}
/// A message's time as T3 Code's timeline shows it: `12:34 PM` today,
/// `yesterday at 12:34 PM`, `8/13 12:34 PM`, or `8/13/2025 12:34 PM` in
/// another year.
pub fn day_aware(time: SystemTime) -> String {
let local: DateTime<Local> = time.into();
let today = Local::now().date_naive();
let clock = clock(time);
match (local.date_naive() - today).num_days() {
0 => clock,
-1 => format!("yesterday at {clock}"),
_ if local.year() == today.year() => format!("{} {clock}", local.format("%-m/%-d")),
_ => format!("{} {clock}", local.format("%-m/%-d/%Y")),
}
}
/// The long form for a tooltip: `12:04 PM, 4th June 2026`.
pub fn long_form(time: SystemTime) -> String {
let local: DateTime<Local> = time.into();
let day = local.day();
let suffix = match (day % 10, day % 100) {
(_, 11..=13) => "th",
(1, _) => "st",
(2, _) => "nd",
(3, _) => "rd",
_ => "th",
};
format!("{}, {day}{suffix} {}", clock(time), local.format("%B %Y"))
}