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:
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 {