notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 9278c90802f059f48be2e92ea86bb704cc82c007
parent 02a90eccd1c62e70094ca5a9921bdba36912b538
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 10 Sep 2025 16:40:13 -0700

time: more time-ago granularity in months/years

before: 1y
after:  1y 8mo

etc

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mcrates/notedeck/src/time.rs | 112++++++++++++++++++++++++++++++++++++++++---------------------------------------
1 file changed, 57 insertions(+), 55 deletions(-)

diff --git a/crates/notedeck/src/time.rs b/crates/notedeck/src/time.rs @@ -10,73 +10,75 @@ const ONE_WEEK_IN_SECONDS: u64 = 604_800; const ONE_MONTH_IN_SECONDS: u64 = 2_592_000; // 30 days const ONE_YEAR_IN_SECONDS: u64 = 31_536_000; // 365 days -// Range boundary constants for match patterns -const MAX_SECONDS: u64 = ONE_MINUTE_IN_SECONDS - 1; -const MAX_SECONDS_FOR_MINUTES: u64 = ONE_HOUR_IN_SECONDS - 1; -const MAX_SECONDS_FOR_HOURS: u64 = ONE_DAY_IN_SECONDS - 1; -const MAX_SECONDS_FOR_DAYS: u64 = ONE_WEEK_IN_SECONDS - 1; -const MAX_SECONDS_FOR_WEEKS: u64 = ONE_MONTH_IN_SECONDS - 1; -const MAX_SECONDS_FOR_MONTHS: u64 = ONE_YEAR_IN_SECONDS - 1; - -/// Calculate relative time between two timestamps +/// Calculate relative time between two timestamps, with two units only +/// when the scale is large enough (e.g., "1y 6m", "5d 4h"), +/// but not for hours/minutes/seconds. fn time_ago_between(i18n: &mut Localization, timestamp: u64, now: u64) -> String { - // Determine if the timestamp is in the future or the past let duration = if now >= timestamp { now.saturating_sub(timestamp) } else { timestamp.saturating_sub(now) }; - let time_str = match duration { - 0..=2 => tr!( + // Special-case: "now" for < 3 seconds + if duration <= 2 { + let s = tr!( i18n, "now", "Relative time for very recent events (less than 3 seconds)" - ), - 3..=MAX_SECONDS => tr!( - i18n, - "{count}s", - "Relative time in seconds", - count = duration - ), - ONE_MINUTE_IN_SECONDS..=MAX_SECONDS_FOR_MINUTES => tr!( - i18n, - "{count}m", - "Relative time in minutes", - count = duration / ONE_MINUTE_IN_SECONDS - ), - ONE_HOUR_IN_SECONDS..=MAX_SECONDS_FOR_HOURS => tr!( - i18n, - "{count}h", - "Relative time in hours", - count = duration / ONE_HOUR_IN_SECONDS - ), - ONE_DAY_IN_SECONDS..=MAX_SECONDS_FOR_DAYS => tr!( - i18n, - "{count}d", - "Relative time in days", - count = duration / ONE_DAY_IN_SECONDS - ), - ONE_WEEK_IN_SECONDS..=MAX_SECONDS_FOR_WEEKS => tr!( - i18n, - "{count}w", - "Relative time in weeks", - count = duration / ONE_WEEK_IN_SECONDS - ), - ONE_MONTH_IN_SECONDS..=MAX_SECONDS_FOR_MONTHS => tr!( - i18n, - "{count}mo", - "Relative time in months", - count = duration / ONE_MONTH_IN_SECONDS - ), - _ => tr!( - i18n, - "{count}y", - "Relative time in years", - count = duration / ONE_YEAR_IN_SECONDS - ), + ); + return if timestamp > now { format!("+{s}") } else { s }; + } + + // Break into buckets + let years = duration / ONE_YEAR_IN_SECONDS; + let rem_y = duration % ONE_YEAR_IN_SECONDS; + + let months = rem_y / ONE_MONTH_IN_SECONDS; + let rem_m = rem_y % ONE_MONTH_IN_SECONDS; + + let weeks = rem_m / ONE_WEEK_IN_SECONDS; + let rem_w = rem_m % ONE_WEEK_IN_SECONDS; + + let days = rem_w / ONE_DAY_IN_SECONDS; + let rem_d = rem_w % ONE_DAY_IN_SECONDS; + + let hours = rem_d / ONE_HOUR_IN_SECONDS; + let rem_h = rem_d % ONE_HOUR_IN_SECONDS; + + let mins = rem_h / ONE_MINUTE_IN_SECONDS; + let secs = rem_h % ONE_MINUTE_IN_SECONDS; + + let mut parts: Vec<String> = Vec::with_capacity(2); + + let mut push_part = |count: u64, key: &str, desc: &str| { + if count > 0 && parts.len() < 2 { + parts.push(tr!(i18n, key, desc, count = count)); + } }; + if years > 0 { + push_part(years, "{count}y", "Relative time in years"); + push_part(months, "{count}mo", "Relative time in months"); + } else if months > 0 { + push_part(months, "{count}mo", "Relative time in months"); + push_part(weeks, "{count}w", "Relative time in weeks"); + } else if weeks > 0 { + push_part(weeks, "{count}w", "Relative time in weeks"); + push_part(days, "{count}d", "Relative time in days"); + } else if days > 0 { + push_part(days, "{count}d", "Relative time in days"); + push_part(hours, "{count}h", "Relative time in hours"); + } else if hours > 0 { + push_part(hours, "{count}h", "Relative time in hours"); + } else if mins > 0 { + push_part(mins, "{count}m", "Relative time in minutes"); + } else { + push_part(secs.max(1), "{count}s", "Relative time in seconds"); + } + + let time_str = parts.join(" "); + if timestamp > now { format!("+{time_str}") } else {