Vectrum
Sign In
60 lines · 2.2 KB · Rust
//! 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)
}