From 2643f9939bb5a5426cc7f3b96a46305d50fa563a Mon Sep 17 00:00:00 2001 From: Lauri Nevala Date: Mon, 28 Dec 2020 09:32:20 +0200 Subject: [PATCH] Unify Age column output with kubectl (#1822) Signed-off-by: Lauri Nevala --- .../utils/__tests__/formatDuration.test.ts | 110 +++++++++++------- src/renderer/utils/formatDuration.ts | 90 +++++++++++--- 2 files changed, 142 insertions(+), 58 deletions(-) diff --git a/src/renderer/utils/__tests__/formatDuration.test.ts b/src/renderer/utils/__tests__/formatDuration.test.ts index e4d57d54f0..7c19ba93f9 100644 --- a/src/renderer/utils/__tests__/formatDuration.test.ts +++ b/src/renderer/utils/__tests__/formatDuration.test.ts @@ -1,57 +1,89 @@ +import moment from "moment"; import { formatDuration } from "../formatDuration"; const second = 1000; const minute = 60 * second; const hour = 60 * minute; const day = 24 * hour; -const week = 7 * day; +const year = 365 * day; describe("human format durations", () => { - test("long formatted durations less than 24 hours long shouldn't have a 'd' component", () => { - const res = formatDuration(19 * 60 * 60 * 1000, false); - - expect(res).not.toContain("d"); - expect(res).toBe("19h"); + test("small duration should output something", () => { + expect(formatDuration(1)).toBe("0s"); + expect(formatDuration(3)).toBe("0s"); }); - test("long formatted durations more than a week have correct day count", () => { - const res = formatDuration(2 * week + 2 * day, false); + test("returns seconds for duration under 1 min", () => { + const res = formatDuration(8 * second); - expect(res).toBe("2w 2d"); - }); - - test("durations > 1/2 week shouldn't show 1w has passed", () => { - const res = formatDuration(5 * 24 * 60 * 60 * 1000, false); - - expect(res).not.toContain("w"); - expect(res).toBe("5d"); - }); - - test("durations shouldn't include zero magnitude parts", () => { - const res = formatDuration(6 * day + 2 * minute, false); - - expect(res).not.toContain("h"); - expect(res).toBe("6d 2m"); - }); - - test("seconds are ignored unless they are significant (< 1m)", () => { - const insignificant = formatDuration(1 * hour + 2 * minute + 31 * second, false); - - expect(insignificant).not.toContain("s"); - expect(insignificant).toBe("1h 2m"); - - const significant = formatDuration(31 * second, false); - - expect(significant).toBe("31s"); + expect(res).toBe("8s"); }); test("zero duration should output something", () => { - expect(formatDuration(0, false)).toBe("0s"); - expect(formatDuration(0, true)).toBe("0s"); + expect(formatDuration(0)).toBe("0s"); }); - test("small duration should output something", () => { - expect(formatDuration(1, false)).toBe("0s"); - expect(formatDuration(3, true)).toBe("0s"); + describe("when compact is true", () => { + + test("duration under 3 hours return minutes", () => { + const res = formatDuration(1 * hour + 35 * minute); + + expect(res).toBe("95m"); + }); + + test("duration under 8 hours return hours and minutes", () => { + const res = formatDuration(6 * hour + 15 * minute + 20 * second); + + expect(res).toBe("6h15m"); + }); + + test("duration under 48 hours return hours", () => { + const res = formatDuration(1 * day + 4 * hour + 15 * minute); + + expect(res).toBe("28h"); + }); + + test("duration under 2 years return days", () => { + const res = formatDuration(400 * day + 4 * hour + 15 * minute); + + expect(res).toBe("400d"); + }); + + test("durations less than 8 years returns years and days", () => { + const timeValue = new Date().getTime() - new Date(moment().subtract(2, "years").subtract(5, "days").subtract(2, "hours").toDate()).getTime(); + + const res = formatDuration(timeValue); + + expect(res).toBe("2y5d"); + }); + + test("durations more than 8 years returns years", () => { + const timeValue = new Date().getTime() - new Date(moment().subtract(9, "years").subtract(5, "days").toDate()).getTime(); + + const res = formatDuration(timeValue); + + expect(res).toBe("9y"); + }); + + test("durations more than 8 years returns years", () => { + const res = formatDuration(10 * year + 25 * day); + + expect(res).toBe("10y"); + }); + test("durations shouldn't include zero magnitude parts", () => { + const zeroSeconds = formatDuration(8 * minute); + + expect(zeroSeconds).toBe("8m"); + + const zeroMinutes = formatDuration(8 * hour + 15 * minute); + + expect(zeroMinutes).toBe("8h"); + + const zeroHours = formatDuration(6 * day + 2 * minute); + + expect(zeroHours).toBe("6d"); + + }); }); + }); diff --git a/src/renderer/utils/formatDuration.ts b/src/renderer/utils/formatDuration.ts index 4527945211..8864aba393 100644 --- a/src/renderer/utils/formatDuration.ts +++ b/src/renderer/utils/formatDuration.ts @@ -1,34 +1,86 @@ import moment from "moment"; -const suffixes = ["w", "d", "h", "m", "s"]; - /** * This function formats durations in a more human readable form. * @param timeValue the duration in milliseconds to format - * @param compact when true, only the largest non-zero time frame will be returned */ -export function formatDuration(timeValue: number, compact: boolean) { +export function formatDuration(timeValue: number, compact = true) { const duration = moment.duration(timeValue, "milliseconds"); - const durationValues = [ - Math.floor(duration.asWeeks()), - Math.floor(duration.asDays()) % 7, - duration.hours(), - duration.minutes(), - duration.seconds(), - ]; - const meaningfulValues = durationValues - .map((a, i): [number, string] => [a, suffixes[i]]) - .filter(([dur]) => dur > 0) - .filter(([, suf], i) => i === 0 || suf !== "s") // remove seconds, unless it is the only one - .map(([dur, suf]) => dur + suf); + const seconds = Math.floor(duration.asSeconds()); + const separator = compact ? "": " "; - if (meaningfulValues.length === 0) { + if (seconds < 0) { return "0s"; + } else if (seconds < 60*2 ) { + return `${seconds}s`; + } + + const minutes = Math.floor(duration.asMinutes()); + + if (minutes < 10) { + const seconds = duration.seconds(); + + return getMeaningfulValues([minutes, seconds], ["m", "s"], separator); + } else if (minutes < 60 * 3) { + if (!compact) { + return getMeaningfulValues([minutes, duration.seconds()], ["m", "s"]); + } + + return `${minutes}m`; + } + + const hours = Math.floor(duration.asHours()); + + if(hours < 8) { + const minutes = duration.minutes(); + + return getMeaningfulValues([hours, minutes], ["h", "m"], separator); + } else if (hours < 48) { + if (compact) { + return `${hours}h`; + } else { + return getMeaningfulValues([hours, duration.minutes()], ["h", "m"]); + } + } + + const days = Math.floor(duration.asDays()); + + if (days < 8) { + const hours = duration.hours(); + + if (compact) { + return getMeaningfulValues([days, hours], ["d", "h"], separator); + } else { + return getMeaningfulValues([days, hours, duration.minutes()], ["d", "h", "m"]); + } + } + const years = Math.floor(duration.asYears()); + + if (years < 2) { + if (compact) { + return `${days}d`; + } else { + return getMeaningfulValues([days, duration.hours(), duration.minutes()], ["d", "h", "m"]); + } + } else if (years < 8) { + const days = duration.days(); + + if (compact) { + return getMeaningfulValues([years, days], ["y", "d"], separator); + } } if (compact) { - return meaningfulValues[0]; + return `${years}y`; } - return meaningfulValues.join(" "); + return getMeaningfulValues([years, duration.days(), duration.hours(), duration.minutes()], ["y", "d", "h", "m"]); } + +function getMeaningfulValues(values: number[], suffixes: string[], separator = " ") { + return values + .map((a, i): [number, string] => [a, suffixes[i]]) + .filter(([dur]) => dur > 0) + .map(([dur, suf]) => dur + suf) + .join(separator); +} \ No newline at end of file