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