Deduplicate duration and number formatting internals
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
This commit is contained in:
@@ -6,6 +6,8 @@
|
|||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
|
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
|
||||||
/** @module abbreviateNumber Duration abbreviation formatting. */
|
/** @module abbreviateNumber Duration abbreviation formatting. */
|
||||||
|
|
||||||
|
import formatDuration from "./formatDuration.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a total number of seconds into a human-readable abbreviated
|
* Converts a total number of seconds into a human-readable abbreviated
|
||||||
* duration string using days, hours, minutes, and seconds.
|
* duration string using days, hours, minutes, and seconds.
|
||||||
@@ -15,27 +17,7 @@
|
|||||||
* or "0" if totalSeconds is zero.
|
* or "0" if totalSeconds is zero.
|
||||||
*/
|
*/
|
||||||
export default function abbreviateNumber(totalSeconds) {
|
export default function abbreviateNumber(totalSeconds) {
|
||||||
let seconds = 0;
|
const parsed = Number.parseInt(totalSeconds);
|
||||||
let minutes = 0;
|
if (parsed === 0) return "0";
|
||||||
let hours = 0;
|
return formatDuration(parsed, { separator: " " });
|
||||||
let days = 0;
|
|
||||||
let result = "";
|
|
||||||
let symbols = ["d", "h", "m", "s"];
|
|
||||||
|
|
||||||
totalSeconds = Number.parseInt(totalSeconds);
|
|
||||||
if (!Number.isNaN(totalSeconds)) {
|
|
||||||
seconds = totalSeconds % 60;
|
|
||||||
minutes = (Math.floor(totalSeconds - seconds) / 60) % 60;
|
|
||||||
hours = Math.floor(totalSeconds / 3600) % 24;
|
|
||||||
days = Math.floor(totalSeconds / (3600 * 24));
|
|
||||||
}
|
|
||||||
|
|
||||||
const units = [days, hours, minutes, seconds];
|
|
||||||
for (let i = 0; i < units.length; i++) {
|
|
||||||
if (units[i] > 0) result += units[i] + symbols[i] + " ";
|
|
||||||
}
|
|
||||||
result = result.trim();
|
|
||||||
if (totalSeconds === 0) result = "0";
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/7/5
|
||||||
|
// AI assistance: Claude Code (Anthropic)
|
||||||
|
/** @module formatDuration Shared duration formatting internals. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The duration units from the largest to the smallest.
|
||||||
|
*
|
||||||
|
* @constant {Array<{symbol: string, seconds: number}>}
|
||||||
|
*/
|
||||||
|
export const DURATION_UNITS = [
|
||||||
|
{ symbol: "d", seconds: 24 * 60 * 60 },
|
||||||
|
{ symbol: "h", seconds: 60 * 60 },
|
||||||
|
{ symbol: "m", seconds: 60 },
|
||||||
|
{ symbol: "s", seconds: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Picks the largest duration unit suitable for a number of seconds.
|
||||||
|
*
|
||||||
|
* @param {number} seconds - The number of seconds to pick a unit for.
|
||||||
|
* @param {boolean} [exclusive=false] - Whether a unit is chosen only
|
||||||
|
* when the value is strictly greater than one unit (true), or when
|
||||||
|
* it is greater than or equal to one unit (false).
|
||||||
|
* @returns {{symbol: string, seconds: number}} The chosen unit; falls
|
||||||
|
* back to seconds when no larger unit qualifies.
|
||||||
|
*/
|
||||||
|
export function pickDurationUnit(seconds, exclusive = false) {
|
||||||
|
return DURATION_UNITS.find(
|
||||||
|
(unit) =>
|
||||||
|
unit.seconds === 1 ||
|
||||||
|
(exclusive ? seconds > unit.seconds : seconds >= unit.seconds),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a duration in seconds as a day/hour/minute/second cascade.
|
||||||
|
*
|
||||||
|
* Zero-valued units are omitted, except that the seconds unit can be
|
||||||
|
* forced with the alwaysSeconds option. The result is truncated to at
|
||||||
|
* most maxUnits units, starting from the largest.
|
||||||
|
*
|
||||||
|
* @param {number} totalSeconds - The total number of seconds.
|
||||||
|
* @param {Object} [options] - The formatting options.
|
||||||
|
* @param {string} [options.separator=" "] - The string between units.
|
||||||
|
* @param {number} [options.maxUnits=Infinity] - The maximum number of
|
||||||
|
* units to keep, starting from the largest.
|
||||||
|
* @param {boolean} [options.alwaysSeconds=false] - Whether to always
|
||||||
|
* append the seconds unit, even when it is zero.
|
||||||
|
* @returns {string} The formatted duration (e.g. "2d 3h 15m 30s"), or
|
||||||
|
* an empty string when no unit qualifies.
|
||||||
|
*/
|
||||||
|
export default function formatDuration(
|
||||||
|
totalSeconds,
|
||||||
|
{ separator = " ", maxUnits = Infinity, alwaysSeconds = false } = {},
|
||||||
|
) {
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
const minutes = (Math.floor(totalSeconds - seconds) / 60) % 60;
|
||||||
|
const hours = Math.floor(totalSeconds / 3600) % 24;
|
||||||
|
const days = Math.floor(totalSeconds / (3600 * 24));
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
if (days > 0) parts.push(days + "d");
|
||||||
|
if (hours > 0) parts.push(hours + "h");
|
||||||
|
if (minutes > 0) parts.push(minutes + "m");
|
||||||
|
if (seconds > 0 || alwaysSeconds) parts.push(seconds + "s");
|
||||||
|
|
||||||
|
return parts.slice(0, maxUnits).join(separator);
|
||||||
|
}
|
||||||
@@ -6,30 +6,18 @@
|
|||||||
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
|
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
|
||||||
/** @module numberLabel Number formatting with comma separators. */
|
/** @module numberLabel Number formatting with comma separators. */
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats an integer string by inserting commas every three digits.
|
|
||||||
* @param {string} numberStr - A string of digits to format.
|
|
||||||
* @returns {string} The formatted string with commas (e.g. "1,000,000").
|
|
||||||
*/
|
|
||||||
const formatNumberWithCommas = (numberStr) => {
|
|
||||||
let reversedStr = numberStr.split("").reverse().join("");
|
|
||||||
let groupedStr = reversedStr.match(/.{1,3}/g) || [];
|
|
||||||
let joinedStr = groupedStr.join(",");
|
|
||||||
let finalStr = joinedStr.split("").reverse().join("");
|
|
||||||
|
|
||||||
return finalStr;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a number to a string with comma-separated thousands.
|
* Converts a number to a string with comma-separated thousands.
|
||||||
*
|
*
|
||||||
* Handles decimal numbers by formatting only the integer part.
|
* Handles decimal numbers by formatting only the integer part. A comma
|
||||||
|
* is inserted before every group of three characters counted from the
|
||||||
|
* end of the integer part.
|
||||||
*
|
*
|
||||||
* @param {number} num - The number to format.
|
* @param {number} num - The number to format.
|
||||||
* @returns {string} The formatted number string (e.g. "1,234.56").
|
* @returns {string} The formatted number string (e.g. "1,234.56").
|
||||||
*/
|
*/
|
||||||
export default function numberLabel(num) {
|
export default function numberLabel(num) {
|
||||||
let parts = num.toString().split(".");
|
const parts = num.toString().split(".");
|
||||||
parts[0] = formatNumberWithCommas(parts[0]);
|
parts[0] = parts[0].replace(/(.)(?=(?:.{3})+$)/g, "$1,");
|
||||||
return parts.join(".");
|
return parts.join(".");
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-37
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import getMoment from "moment";
|
import getMoment from "moment";
|
||||||
|
|
||||||
|
import formatDuration, { DURATION_UNITS } from "./formatDuration.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends backend chart data with extrapolated boundary points for
|
* Extends backend chart data with extrapolated boundary points for
|
||||||
* line charts. Prepends a calculated minimum and appends a calculated
|
* line charts. Prepends a calculated minimum and appends a calculated
|
||||||
@@ -241,26 +243,8 @@ export function getXIndex(data, xValue) {
|
|||||||
export function formatTime(seconds) {
|
export function formatTime(seconds) {
|
||||||
if (Number.isNaN(Number(seconds))) {
|
if (Number.isNaN(Number(seconds))) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
|
||||||
const remainingSeconds = seconds % 60;
|
|
||||||
const minutes = (Math.floor(seconds - remainingSeconds) / 60) % 60;
|
|
||||||
const hours = Math.floor(seconds / 3600) % 24;
|
|
||||||
const days = Math.floor(seconds / (3600 * 24));
|
|
||||||
|
|
||||||
let result = "";
|
|
||||||
if (days > 0) {
|
|
||||||
result += `${days}d`;
|
|
||||||
}
|
|
||||||
if (hours > 0) {
|
|
||||||
result += `${hours}h`;
|
|
||||||
}
|
|
||||||
if (minutes > 0) {
|
|
||||||
result += `${minutes}m`;
|
|
||||||
}
|
|
||||||
result += `${remainingSeconds}s`;
|
|
||||||
|
|
||||||
return result.trim(); // Remove trailing whitespace
|
|
||||||
}
|
}
|
||||||
|
return formatDuration(seconds, { separator: "", alwaysSeconds: true });
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Truncates each time string to show only the two largest time units.
|
* Truncates each time string to show only the two largest time units.
|
||||||
@@ -269,22 +253,23 @@ export function formatTime(seconds) {
|
|||||||
* @returns {Array<string>} Array of truncated strings (e.g. "2d 3h").
|
* @returns {Array<string>} Array of truncated strings (e.g. "2d 3h").
|
||||||
*/
|
*/
|
||||||
export function formatMaxTwo(times) {
|
export function formatMaxTwo(times) {
|
||||||
const formattedTimes = [];
|
const secondsPerUnit = Object.fromEntries(
|
||||||
for (let time of times) {
|
DURATION_UNITS.map((unit) => [unit.symbol, unit.seconds]),
|
||||||
// Match numbers and units (days, hours, minutes, seconds); assume numbers have at most 10 digits
|
);
|
||||||
let units = time.match(/\d{1,10}[dhms]/g) || [];
|
return times.map((time) => {
|
||||||
let formattedTime = "";
|
// Match numbers and units (days, hours, minutes, seconds); assume
|
||||||
let count = 0;
|
// numbers have at most 10 digits.
|
||||||
|
const units = time.match(/\d{1,10}[dhms]/g);
|
||||||
// Keep only the two largest units
|
if (!units) return "";
|
||||||
for (let unit of units) {
|
const totalSeconds = units.reduce(
|
||||||
if (count >= 2) {
|
(sum, unit) =>
|
||||||
break;
|
sum + Number.parseInt(unit) * secondsPerUnit[unit.slice(-1)],
|
||||||
}
|
0,
|
||||||
formattedTime += unit + " ";
|
);
|
||||||
count++;
|
return formatDuration(totalSeconds, {
|
||||||
}
|
separator: " ",
|
||||||
formattedTimes.push(formattedTime.trim()); // Remove trailing whitespace
|
maxUnits: 2,
|
||||||
}
|
alwaysSeconds: units.some((unit) => unit.endsWith("s")),
|
||||||
return formattedTimes;
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-111
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
|
||||||
|
import { pickDurationUnit } from "./formatDuration.js";
|
||||||
|
|
||||||
/** @constant {number} Number of decimal places for formatted time values. */
|
/** @constant {number} Number of decimal places for formatted time values. */
|
||||||
const TOFIXED_DECIMAL = 1;
|
const TOFIXED_DECIMAL = 1;
|
||||||
|
|
||||||
@@ -36,34 +38,11 @@ export const getStepSizeOfYTicks = (maxTimeInSecond, numOfParts) => {
|
|||||||
* character ("d", "h", "m", or "s") and the converted time value.
|
* character ("d", "h", "m", or "s") and the converted time value.
|
||||||
*/
|
*/
|
||||||
const getTimeUnitAndValueToUse = (secondToDecide) => {
|
const getTimeUnitAndValueToUse = (secondToDecide) => {
|
||||||
const day = 24 * 60 * 60;
|
const unit = pickDurationUnit(secondToDecide, true);
|
||||||
const hour = 60 * 60;
|
|
||||||
const minutes = 60;
|
|
||||||
const dd = secondToDecide / day;
|
|
||||||
const hh = secondToDecide / hour;
|
|
||||||
const mm = secondToDecide / minutes;
|
|
||||||
|
|
||||||
if (dd > 0 && dd > 1) {
|
|
||||||
return {
|
return {
|
||||||
unitToUse: "d",
|
unitToUse: unit.symbol,
|
||||||
timeValue: secondToDecide / day,
|
timeValue: secondToDecide / unit.seconds,
|
||||||
};
|
};
|
||||||
} else if (hh > 0 && hh > 1) {
|
|
||||||
return {
|
|
||||||
unitToUse: "h",
|
|
||||||
timeValue: secondToDecide / hour,
|
|
||||||
};
|
|
||||||
} else if (mm > 0 && mm > 1) {
|
|
||||||
return {
|
|
||||||
unitToUse: "m",
|
|
||||||
timeValue: secondToDecide / minutes,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
unitToUse: "s",
|
|
||||||
timeValue: secondToDecide,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,6 +59,27 @@ export function getYTicksByIndex(stepSize, index, unitToUse) {
|
|||||||
return `${shortenStepsizeMultIndex}${unitToUse}`;
|
return `${shortenStepsizeMultIndex}${unitToUse}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats seconds as a single-unit time label with the given suffixes.
|
||||||
|
*
|
||||||
|
* Chooses the largest unit whose size the value reaches. Values are
|
||||||
|
* rounded to fixedNumber decimal places, except seconds, which are
|
||||||
|
* concatenated as-is.
|
||||||
|
*
|
||||||
|
* @param {number} second - The total number of seconds.
|
||||||
|
* @param {number} fixedNumber - Number of decimal places.
|
||||||
|
* @param {{d: string, h: string, m: string, s: string}} suffixes - The
|
||||||
|
* unit suffixes to append.
|
||||||
|
* @returns {string} The formatted time label.
|
||||||
|
*/
|
||||||
|
function scaledTimeLabel(second, fixedNumber, suffixes) {
|
||||||
|
const unit = pickDurationUnit(second);
|
||||||
|
if (unit.symbol === "s") {
|
||||||
|
return second + suffixes.s;
|
||||||
|
}
|
||||||
|
return (second / unit.seconds).toFixed(fixedNumber) + suffixes[unit.symbol];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts seconds to a human-readable time string with full unit names.
|
* Converts seconds to a human-readable time string with full unit names.
|
||||||
*
|
*
|
||||||
@@ -89,27 +89,12 @@ export function getYTicksByIndex(stepSize, index, unitToUse) {
|
|||||||
* "30 mins", "5 sec").
|
* "30 mins", "5 sec").
|
||||||
*/
|
*/
|
||||||
export function getTimeLabel(second, fixedNumber = 0) {
|
export function getTimeLabel(second, fixedNumber = 0) {
|
||||||
const day = 24 * 60 * 60;
|
return scaledTimeLabel(second, fixedNumber, {
|
||||||
const hour = 60 * 60;
|
d: " days",
|
||||||
const minutes = 60;
|
h: " hrs",
|
||||||
|
m: " mins",
|
||||||
// The modulo operation limits the value to within one day (0 to 86399),
|
s: " sec",
|
||||||
// representing the remaining seconds after full days.
|
});
|
||||||
const dd = Math.floor(second / day);
|
|
||||||
const hh = Math.floor((second % day) / hour);
|
|
||||||
const mm = Math.floor((second % hour) / minutes);
|
|
||||||
|
|
||||||
if (dd > 0) {
|
|
||||||
return (second / day).toFixed(fixedNumber) + " days";
|
|
||||||
} else if (hh > 0) {
|
|
||||||
return ((second % day) / hour).toFixed(fixedNumber) + " hrs";
|
|
||||||
} else if (mm > 0) {
|
|
||||||
return ((second % hour) / minutes).toFixed(fixedNumber) + " mins";
|
|
||||||
}
|
|
||||||
if (second === 0) {
|
|
||||||
return second + " sec";
|
|
||||||
}
|
|
||||||
return second + " sec";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,24 +108,12 @@ export function getTimeLabel(second, fixedNumber = 0) {
|
|||||||
* @returns {string} The formatted string (e.g. "1d", "6.8h", "30m", "5s").
|
* @returns {string} The formatted string (e.g. "1d", "6.8h", "30m", "5s").
|
||||||
*/
|
*/
|
||||||
export function simpleTimeLabel(second, fixedNumber = 0) {
|
export function simpleTimeLabel(second, fixedNumber = 0) {
|
||||||
const day = 24 * 60 * 60;
|
return scaledTimeLabel(second, fixedNumber, {
|
||||||
const hour = 60 * 60;
|
d: "d",
|
||||||
const minutes = 60;
|
h: "h",
|
||||||
const dd = Math.floor(second / day);
|
m: "m",
|
||||||
const hh = Math.floor((second % day) / hour);
|
s: "s",
|
||||||
const mm = Math.floor((second % hour) / minutes);
|
});
|
||||||
|
|
||||||
if (dd > 0) {
|
|
||||||
return (second / day).toFixed(fixedNumber) + "d";
|
|
||||||
} else if (hh > 0) {
|
|
||||||
return ((second % day) / hour).toFixed(fixedNumber) + "h";
|
|
||||||
} else if (mm > 0) {
|
|
||||||
return ((second % hour) / minutes).toFixed(fixedNumber) + "m";
|
|
||||||
}
|
|
||||||
if (second === 0) {
|
|
||||||
return second + "s";
|
|
||||||
}
|
|
||||||
return second + "s";
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Converts seconds to a time string using the same unit as the maximum
|
* Converts seconds to a time string using the same unit as the maximum
|
||||||
@@ -155,52 +128,9 @@ export function simpleTimeLabel(second, fixedNumber = 0) {
|
|||||||
* @returns {string} The formatted string (e.g. "1.5d", "6.8h").
|
* @returns {string} The formatted string (e.g. "1.5d", "6.8h").
|
||||||
*/
|
*/
|
||||||
export function followTimeLabel(second, max, fixedNumber = 0) {
|
export function followTimeLabel(second, max, fixedNumber = 0) {
|
||||||
const day = 24 * 60 * 60;
|
const unit = pickDurationUnit(max, true);
|
||||||
const hour = 60 * 60;
|
const value = second / unit.seconds;
|
||||||
const minutes = 60;
|
return value.toFixed(value === 0 ? 0 : fixedNumber) + unit.symbol;
|
||||||
|
|
||||||
const dd = max / day;
|
|
||||||
const hh = max / hour;
|
|
||||||
const mm = max / minutes;
|
|
||||||
let maxUnit = "";
|
|
||||||
let result = "";
|
|
||||||
|
|
||||||
if (dd > 1) {
|
|
||||||
maxUnit = "d";
|
|
||||||
} else if (hh > 1) {
|
|
||||||
maxUnit = "h";
|
|
||||||
} else if (mm > 1) {
|
|
||||||
maxUnit = "m";
|
|
||||||
} else {
|
|
||||||
maxUnit = "s";
|
|
||||||
}
|
|
||||||
switch (maxUnit) {
|
|
||||||
case "d":
|
|
||||||
if (second / day === 0) {
|
|
||||||
fixedNumber = 0;
|
|
||||||
}
|
|
||||||
result = (second / day).toFixed(fixedNumber) + "d";
|
|
||||||
break;
|
|
||||||
case "h":
|
|
||||||
if (second / hour === 0) {
|
|
||||||
fixedNumber = 0;
|
|
||||||
}
|
|
||||||
result = (second / hour).toFixed(fixedNumber) + "h";
|
|
||||||
break;
|
|
||||||
case "m":
|
|
||||||
if (second / minutes === 0) {
|
|
||||||
fixedNumber = 0;
|
|
||||||
}
|
|
||||||
result = (second / minutes).toFixed(fixedNumber) + "m";
|
|
||||||
break;
|
|
||||||
case "s":
|
|
||||||
if (second === 0) {
|
|
||||||
fixedNumber = 0;
|
|
||||||
}
|
|
||||||
result = second.toFixed(fixedNumber) + "s";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// The Lucia project.
|
||||||
|
// Copyright 2026-2026 DSP, inc. All rights reserved.
|
||||||
|
// Authors:
|
||||||
|
// imacat.yang@dsp.im (imacat), 2026/7/5
|
||||||
|
// AI assistance: Claude Code (Anthropic)
|
||||||
|
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import formatDuration, { pickDurationUnit } from "@/module/formatDuration.js";
|
||||||
|
|
||||||
|
describe("formatDuration", () => {
|
||||||
|
it("formats a full cascade with the default separator", () => {
|
||||||
|
expect(formatDuration(93784)).toBe("1d 2h 3m 4s");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("omits zero-valued units", () => {
|
||||||
|
expect(formatDuration(86400)).toBe("1d");
|
||||||
|
expect(formatDuration(3661)).toBe("1h 1m 1s");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an empty string when no unit qualifies", () => {
|
||||||
|
expect(formatDuration(0)).toBe("");
|
||||||
|
expect(formatDuration(NaN)).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports a custom separator", () => {
|
||||||
|
expect(formatDuration(90061, { separator: "" })).toBe("1d1h1m1s");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("always appends seconds when alwaysSeconds is set", () => {
|
||||||
|
expect(formatDuration(0, { alwaysSeconds: true })).toBe("0s");
|
||||||
|
expect(formatDuration(60, { separator: "", alwaysSeconds: true })).toBe(
|
||||||
|
"1m0s",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("truncates to maxUnits largest units", () => {
|
||||||
|
expect(formatDuration(93784, { maxUnits: 2 })).toBe("1d 2h");
|
||||||
|
expect(formatDuration(45, { maxUnits: 2 })).toBe("45s");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("pickDurationUnit", () => {
|
||||||
|
it("picks the largest unit reached by the value", () => {
|
||||||
|
expect(pickDurationUnit(86400).symbol).toBe("d");
|
||||||
|
expect(pickDurationUnit(3600).symbol).toBe("h");
|
||||||
|
expect(pickDurationUnit(60).symbol).toBe("m");
|
||||||
|
expect(pickDurationUnit(59).symbol).toBe("s");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires strictly more than one unit when exclusive", () => {
|
||||||
|
expect(pickDurationUnit(86400, true).symbol).toBe("h");
|
||||||
|
expect(pickDurationUnit(86401, true).symbol).toBe("d");
|
||||||
|
expect(pickDurationUnit(60, true).symbol).toBe("s");
|
||||||
|
expect(pickDurationUnit(61, true).symbol).toBe("m");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to seconds for zero and negative values", () => {
|
||||||
|
expect(pickDurationUnit(0).symbol).toBe("s");
|
||||||
|
expect(pickDurationUnit(-100).symbol).toBe("s");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user