//! 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)
}