Relative timestamps go stale unless they are refreshed
Aliases: stale time ago · relative time drift · midnight rollover
What it is
“Just now”, “three minutes ago”, “yesterday” are all said relative to now. Now moves; if the label does not, the sentence goes from true to false. Relative time needs refresh means the interface recomputes those labels at a grain that matches them: second-grain every few seconds, minute-grain every minute, and “today” becoming “yesterday” across local midnight. Whether to use relative or absolute is a prior question. Here relative is already in use; the only issue is how it stays true.
Why it happens
A relative label is a function that drifts with time, not a frozen string. Finer grain, faster drift: “just now” is true for about a minute; “5 minutes ago” should become “6 minutes ago” on the next minute. Without recompute, a list stays pinned to the “now” of the moment it was opened — someone leaves for a glass of water and comes back to a timeline that still says these messages are immediate, against perception.
Some boundaries are calendar edges, not uniform flow. At local midnight, “today 22:00” becomes “yesterday”, and “yesterday” becomes a date. Refreshing only as “add one minute every 60 seconds” misses that jump. When the page is frozen in the background, timers do not run; the first job on returning to the foreground is to recompute every visible relative label against the current instant, not to continue from an expired clock.
Where it stops holding
Positions that have already switched to absolute time (an archive, a statement, a full instant in detail) do not need this refresh; forcing it only flickers. A long list off-screen does not need a per-row second-grain timer; compute when the row enters the viewport. If a screen reader announces every jump of “n minutes ago”, the refresh itself becomes noise — stay silent on already-read static regions, or coarsen the grain. A legally meaningful “time of occurrence” cannot be expressed by refreshing a relative label: refresh corrects a distance description, not the event’s own coordinate.
Applying it
- Refresh by grain: recompute second- and minute-grain labels on a timer while they are visible; recompute hour-grain and “yesterday” at least on foregrounding and at local midnight.
- On returning from background, and on crossing local midnight, recompute the current viewport immediately rather than waiting for the next tick.
- Refresh visible rows only. Do not run relative recompute on fields that already show an absolute instant.
- How to check: sit on a timeline showing “just now” past its grain; the label should advance. Move the system clock across midnight; former “today” should become “yesterday” or a date. Switch to another app for several minutes and come back; the first glance of relative labels should already be against the new “now”, not stuck at departure.