authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-03-28 16:03:22-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-03-28 16:04:24-07:00
loge14fa6bdce7e5ab857860af2962d89e2ed9a92f4
tree0d308d5f920a359f4cbc4c5299760815e4bc3a51
parent8a3ea71b9f6ecd237d57fe181e2a7b8659ad7844
signature Signed by SSH key SHA256:xbd+BjjhyBfwk7GVoURf9Yx0gzDerHbvYv7SddNWmAs

feat: expand `string.formatDurationLetters` cases

resolves #100

2 files changed, 49 insertions(+), 12 deletions(-)

lib/string.test.ts+18-3
...@@ -29,22 +29,37 @@ test("escapeShellArgument", () => {...@@ -29,22 +29,37 @@ test("escapeShellArgument", () => {
29 assert.equal(string.escapeShellArgument("'string"), "\"'string\"");29 assert.equal(string.escapeShellArgument("'string"), "\"'string\"");
30 assert.equal(string.escapeShellArgument("'str\"ing"), "\\''str\"ing'");30 assert.equal(string.escapeShellArgument("'str\"ing"), "\\''str\"ing'");
31});31});
32test.todo("formatByteSize", () => {32test("formatByteSize", () => {
33 assert.equal(string.formatByteSize(0), "0B");33 assert.equal(string.formatByteSize(0), "0B");
34 assert.equal(string.formatByteSize(1), "1B");34 assert.equal(string.formatByteSize(1), "1B");
35 assert.equal(string.formatByteSize(1000), "1kB");35 assert.equal(string.formatByteSize(1000), "1kB");
36 assert.equal(string.formatByteSize(-1000), "-1kB");36 assert.equal(string.formatByteSize(-1000), "-1kB");
37 assert.equal(string.formatByteSize(1500), "1.5kB");37 assert.equal(string.formatByteSize(1500), "1.5kB");
38 assert.equal(string.formatByteSize(1578), "1.58kB");38 assert.equal(string.formatByteSize(1578), "1.58kB");
39 assert.equal(string.formatByteSize(5_100_000), "5.1MB");39 assert.equal(string.formatByteSize(5_100_000), "5.10MB");
40 assert.equal(string.formatByteSize(-7_231_692_881), "-7.23GB");40 assert.equal(string.formatByteSize(-7_231_692_881), "-7.23GB");
41 assert.equal(string.formatByteSize(-4_294_967_296), "-7.23GB");41 assert.equal(string.formatByteSize(-4_294_967_296), "-4.29GB");
4242
43 assert.equal(string.formatByteSize(53.251), "53.25B");43 assert.equal(string.formatByteSize(53.251), "53.25B");
44 assert.equal(string.formatByteSize(Infinity), "Infinity");44 assert.equal(string.formatByteSize(Infinity), "Infinity");
45 assert.equal(string.formatByteSize(-Infinity), "-Infinity");45 assert.equal(string.formatByteSize(-Infinity), "-Infinity");
46 assert.equal(string.formatByteSize(NaN), "NaN");46 assert.equal(string.formatByteSize(NaN), "NaN");
47});47});
48test("formatDurationLetters", () => {
49 assert.equal(string.formatDurationLetters(1), "1s");
50 assert.equal(string.formatDurationLetters(1.542), "1.54s");
51 assert.equal(string.formatDurationLetters(1.5), "1.5s");
52 assert.equal(string.formatDurationLetters(60), "1m");
53 assert.equal(string.formatDurationLetters(65), "1m5s");
54 assert.equal(string.formatDurationLetters(10000), "2h46m");
55 assert.equal(string.formatDurationLetters(421321378), "13y131d");
56
57 assert.equal(string.formatDurationLetters(0), "now");
58 assert.equal(string.formatDurationLetters(-3000), "50m ago");
59 assert.equal(string.formatDurationLetters(Infinity), "forever");
60 assert.equal(string.formatDurationLetters(-Infinity), "forever ago");
61 assert.equal(string.formatDurationLetters(NaN), "NaN");
62});
4863
49import assert from "node:assert";64import assert from "node:assert";
50import { test } from "vitest";65import { test } from "vitest";
lib/string.ts+31-9
...@@ -53,10 +53,11 @@ export function formatByteSize(bytes: number): string {...@@ -53,10 +53,11 @@ export function formatByteSize(bytes: number): string {
53 bytes /= 1000;53 bytes /= 1000;
54 unit += 1;54 unit += 1;
55 } while (bytes >= 1000);55 } while (bytes >= 1000);
56 return prefix56 return prefix + (unit === 1
57 + bytes.toFixed(57 ? String(+bytes.toFixed(bytes > 100 ? 1 : 2))
58 Math.floor(bytes) === bytes || unit === 1 ? 0 : bytes > 100 ? 1 : 2,58 : bytes.toFixed(
59 )59 Math.floor(bytes) === bytes ? 0 : bytes > 100 ? 1 : 2,
60 ))
60 + byteUnits.charAt(unit - 1) + "B";61 + byteUnits.charAt(unit - 1) + "B";
61}62}
6263
...@@ -73,10 +74,31 @@ export function formatBinaryByteSize(bytes: number): string {...@@ -73,10 +74,31 @@ export function formatBinaryByteSize(bytes: number): string {
73 + byteUnits.charAt(unit - 1) + "iB";74 + byteUnits.charAt(unit - 1) + "iB";
74}75}
7576
76// TODO:
77export function formatDurationLetters(seconds: number): string {77export function formatDurationLetters(seconds: number): string {
78 const minutes = Math.floor(seconds / 60);78 if (seconds === 0) return "now";
79 const remainingSeconds = seconds % 60;79 if (!Number.isFinite(seconds)) {
80 if (minutes < 1) return `${remainingSeconds}s`;80 if (seconds === Infinity) return "forever";
81 return `${minutes}m${remainingSeconds}s`;81 if (seconds === -Infinity) return "forever ago";
82 return "NaN";
83 }
84 let ago = "";
85 if (seconds < 0) seconds = -seconds, ago = " ago";
86 const units = [
87 [31536000, "y"],
88 [86400, "d"],
89 [3600, "h"],
90 [60, "m"],
91 ] as const;
92 let out = "";
93 let remain = seconds;
94 let u = 0;
95 for (const [size, label] of units) {
96 if (remain < size) continue;
97 const value = Math.floor(remain / size);
98 remain -= value * size;
99 out += `${value}${label}`;
100 if ((u += 1) === 2) return out + ago;
101 }
102 if (remain > 0 || out === "") out += `${+remain.toFixed(2)}s`;
103 return out + ago;
82}104}