@samual/duration
Version:
Normalize and format durations of time.
88 lines (87 loc) • 3.86 kB
JavaScript
import { DurationError } from "./DurationError.js"
class FormatDurationError extends DurationError {}
Object.defineProperty(FormatDurationError.prototype, "name", { value: "FormatDurationError" })
class FormatEmptyDurationError extends FormatDurationError {}
Object.defineProperty(FormatEmptyDurationError.prototype, "name", { value: "FormatEmptyDurationError" })
class FormatNonIntegerDurationError extends FormatDurationError {}
Object.defineProperty(FormatEmptyDurationError.prototype, "name", { value: "FormatEmptyDurationError" })
class FormatDurationOptionError extends FormatDurationError {}
Object.defineProperty(FormatDurationOptionError.prototype, "name", { value: "FormatDurationOptionError" })
const formatDuration = (duration, options = {}) => {
if (null != options.maxEntries) {
if (options.maxEntries < 1) throw new FormatDurationOptionError("maxEntries must be at least 1")
if (!Number.isInteger(options.maxEntries)) throw new FormatDurationOptionError("maxEntries must be an integer")
}
const nonIntegerEntry = Object.entries(duration).find(([, value]) => value && !Number.isInteger(value))
if (nonIntegerEntry)
throw new FormatNonIntegerDurationError(
`Given number must be an integer, got ${nonIntegerEntry[0]}: ${nonIntegerEntry[1]}`
)
const yearUnitNameSingular = options.yearUnitNameSingular ?? "year",
yearUnitNamePlural = options.yearUnitNamePlural ?? "years",
dayUnitNameSingular = options.dayUnitNameSingular ?? "day",
dayUnitNamePlural = options.dayUnitNamePlural ?? "days",
hourUnitNameSingular = options.hourUnitNameSingular ?? "hour",
hourUnitNamePlural = options.hourUnitNamePlural ?? "hours",
minuteUnitNameSingular = options.minuteUnitNameSingular ?? "minute",
minuteUnitNamePlural = options.minuteUnitNamePlural ?? "minutes",
secondUnitNameSingular = options.secondUnitNameSingular ?? "second",
secondUnitNamePlural = options.secondUnitNamePlural ?? "seconds",
millisecondUnitNameSingular = options.millisecondUnitNameSingular ?? "millisecond",
millisecondUnitNamePlural = options.millisecondUnitNamePlural ?? "milliseconds",
entries = [
null != duration.years && {
value: duration.years,
unit: 1 == duration.years ? yearUnitNameSingular : yearUnitNamePlural
},
null != duration.days && {
value: duration.days,
unit: 1 == duration.days ? dayUnitNameSingular : dayUnitNamePlural
},
null != duration.hours && {
value: duration.hours,
unit: 1 == duration.hours ? hourUnitNameSingular : hourUnitNamePlural
},
null != duration.minutes && {
value: duration.minutes,
unit: 1 == duration.minutes ? minuteUnitNameSingular : minuteUnitNamePlural
},
null != duration.seconds && {
value: duration.seconds,
unit: 1 == duration.seconds ? secondUnitNameSingular : secondUnitNamePlural
},
null != duration.milliseconds && {
value: duration.milliseconds,
unit: 1 == duration.milliseconds ? millisecondUnitNameSingular : millisecondUnitNamePlural
}
].filter(Boolean)
if (!entries.length) throw new FormatEmptyDurationError("Cannot format empty duration")
if (options.hideZero) {
const nonZeros =
"leading" == options.hideZero
? entries.slice(
Math.max(
0,
entries.findIndex(item => item.value)
)
)
: entries.filter(item => item.value)
return nonZeros.length
? nonZeros
.slice(0, options.maxEntries)
.map(item => `${item.value}${options.noSpaceBeforeUnit ? "" : " "}${item.unit}`)
.join(options.separator ?? ", ")
: "0 " + entries.at(-1).unit
}
return entries
.slice(0, options.maxEntries)
.map(item => `${item.value}${options.noSpaceBeforeUnit ? "" : " "}${item.unit}`)
.join(options.separator ?? ", ")
}
export {
FormatDurationError,
FormatDurationOptionError,
FormatEmptyDurationError,
FormatNonIntegerDurationError,
formatDuration
}