This repository has no description
1const DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [
2 { amount: 60, unit: "seconds" },
3 { amount: 60, unit: "minutes" },
4 { amount: 24, unit: "hours" },
5 { amount: 7, unit: "days" },
6 { amount: 4.34524, unit: "weeks" },
7 { amount: 12, unit: "months" },
8 { amount: Number.POSITIVE_INFINITY, unit: "years" }
9];
10
11const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
12const dtf = new Intl.DateTimeFormat("en", { year: "numeric", month: "short", day: "numeric" });
13// the appview's longTimeFmt ("Jan 2, 2006, 3:04 PM MST")
14const dtfFull = new Intl.DateTimeFormat("en", {
15 year: "numeric",
16 month: "short",
17 day: "numeric",
18 hour: "numeric",
19 minute: "2-digit",
20 timeZoneName: "short"
21});
22
23// "3 days ago", "in 2 hours", etc.
24export const relativeTime = (input: string | Date, now: Date = new Date()): string => {
25 const date = typeof input === "string" ? new Date(input) : input;
26 if (Number.isNaN(date.getTime())) return "";
27 let duration = (date.getTime() - now.getTime()) / 1000;
28 for (const division of DIVISIONS) {
29 if (Math.abs(duration) < division.amount)
30 return rtf.format(Math.round(duration), division.unit);
31 duration /= division.amount;
32 }
33 return rtf.format(Math.round(duration), "years");
34};
35export const compactRelativeTime = (input: string | Date, now: Date = new Date()): string => {
36 const date = typeof input === "string" ? new Date(input) : input;
37 if (Number.isNaN(date.getTime())) return "";
38 let duration = Math.abs((now.getTime() - date.getTime()) / 1000);
39 const suffix = date.getTime() > now.getTime() ? "from now" : "ago";
40
41 if (duration < 60) return `${Math.floor(duration)}s ${suffix}`;
42 duration /= 60;
43 if (duration < 60) return `${Math.floor(duration)}m ${suffix}`;
44 duration /= 60;
45 if (duration < 24) return `${Math.floor(duration)}h ${suffix}`;
46 duration /= 24;
47 if (duration < 7) return `${Math.floor(duration)}d ${suffix}`;
48 duration /= 7;
49 if (duration < 4.34524) return `${Math.floor(duration)}w ${suffix}`;
50 duration /= 4.34524;
51 if (duration < 12) return `${Math.floor(duration)}mo ${suffix}`;
52 duration /= 12;
53 return `${Math.floor(duration)}y ${suffix}`;
54};
55
56// the appview's durationFmt: non-zero chunks, largest first ("1h 3m 20s").
57// under a second reads "0s", so an instant run still differs from one that never ran.
58export const formatDuration = (ms: number): string => {
59 if (!Number.isFinite(ms) || ms < 0) return "";
60 const total = Math.floor(ms / 1000);
61 const chunks: [number, string][] = [
62 [Math.floor(total / 86400), "d"],
63 [Math.floor(total / 3600) % 24, "h"],
64 [Math.floor(total / 60) % 60, "m"],
65 [total % 60, "s"]
66 ];
67 const parts = chunks
68 .filter(([amount]) => amount !== 0)
69 .map(([amount, unit]) => `${amount}${unit}`);
70 return parts.length > 0 ? parts.join(" ") : "0s";
71};
72
73export const formatDate = (input: string | Date): string => {
74 const date = typeof input === "string" ? new Date(input) : input;
75 return Number.isNaN(date.getTime()) ? "" : dtf.format(date);
76};
77
78export const formatDateTime = (input: string | Date): string => {
79 const date = typeof input === "string" ? new Date(input) : input;
80 return Number.isNaN(date.getTime()) ? "" : dtfFull.format(date);
81};