Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
55 linesCodeBlameRaw
1/**
2 * How long ago something happened, in a few characters, as `TimeAgo`
3 * shows it: "just now", "5m ago", "3h ago", "yesterday", "4d ago",
4 * "3w ago", then the date itself, "Sep 30" this year and "Sep 30, 2023"
5 * before, since "1176d ago" says less than the date does. Only plain
6 * functions, so it is tested on its own.
7 */
8
9const MINUTE = 60_000;
10const HOUR = 60 * MINUTE;
11const DAY = 24 * HOUR;
12
13/** A moment as a number, from an RFC 3339 string or (for a few older ones) a number. */
14function timeOf(at: string | number): number {
15 return typeof at === "number" ? at : Date.parse(at);
16}
17
18export function timeAgo(at: string | number, now: number = Date.now(), timeZone?: string): string {
19 const time = timeOf(at);
20 if (!Number.isFinite(time)) return "";
21 // A moment slightly ahead is a clock that differs, not the future.
22 const elapsed = Math.max(0, now - time);
23 if (elapsed < 45_000) return "just now";
24 if (elapsed < HOUR) return `${Math.max(1, Math.round(elapsed / MINUTE))}m ago`;
25 if (elapsed < DAY) return `${Math.floor(elapsed / HOUR)}h ago`;
26 if (elapsed < 2 * DAY) return "yesterday";
27 if (elapsed < 7 * DAY) return `${Math.floor(elapsed / DAY)}d ago`;
28 if (elapsed < 28 * DAY) return `${Math.floor(elapsed / (7 * DAY))}w ago`;
29 const sameYear = yearIn(time, timeZone) === yearIn(now, timeZone);
30 return new Date(time).toLocaleDateString("en-US", {
31 month: "short",
32 day: "numeric",
33 ...(sameYear ? {} : { year: "numeric" }),
34 timeZone,
35 });
36}
37
38/** The moment in full, for the hint over a `TimeAgo`: "Oct 13, 2026, 3:04 PM". */
39export function exactTime(at: string | number, timeZone?: string): string {
40 const time = timeOf(at);
41 if (!Number.isFinite(time)) return "";
42 return new Date(time).toLocaleString("en-US", {
43 month: "short",
44 day: "numeric",
45 year: "numeric",
46 hour: "numeric",
47 minute: "2-digit",
48 timeZone,
49 timeZoneName: "short",
50 });
51}
52
53function yearIn(time: number, timeZone?: string): string {
54 return new Date(time).toLocaleDateString("en-US", { year: "numeric", timeZone });
55}