126 lines
3.6 KiB
JavaScript
126 lines
3.6 KiB
JavaScript
|
'use strict';
|
||
|
const parseMilliseconds = require('parse-ms');
|
||
|
|
||
|
const pluralize = (word, count) => count === 1 ? word : `${word}s`;
|
||
|
|
||
|
const SECOND_ROUNDING_EPSILON = 0.0000001;
|
||
|
|
||
|
module.exports = (milliseconds, options = {}) => {
|
||
|
if (!Number.isFinite(milliseconds)) {
|
||
|
throw new TypeError('Expected a finite number');
|
||
|
}
|
||
|
|
||
|
if (options.colonNotation) {
|
||
|
options.compact = false;
|
||
|
options.formatSubMilliseconds = false;
|
||
|
options.separateMilliseconds = false;
|
||
|
options.verbose = false;
|
||
|
}
|
||
|
|
||
|
if (options.compact) {
|
||
|
options.secondsDecimalDigits = 0;
|
||
|
options.millisecondsDecimalDigits = 0;
|
||
|
}
|
||
|
|
||
|
const result = [];
|
||
|
|
||
|
const floorDecimals = (value, decimalDigits) => {
|
||
|
const flooredInterimValue = Math.floor((value * (10 ** decimalDigits)) + SECOND_ROUNDING_EPSILON);
|
||
|
const flooredValue = Math.round(flooredInterimValue) / (10 ** decimalDigits);
|
||
|
return flooredValue.toFixed(decimalDigits);
|
||
|
};
|
||
|
|
||
|
const add = (value, long, short, valueString) => {
|
||
|
if ((result.length === 0 || !options.colonNotation) && value === 0 && !(options.colonNotation && short === 'm')) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
valueString = (valueString || value || '0').toString();
|
||
|
let prefix;
|
||
|
let suffix;
|
||
|
if (options.colonNotation) {
|
||
|
prefix = result.length > 0 ? ':' : '';
|
||
|
suffix = '';
|
||
|
const wholeDigits = valueString.includes('.') ? valueString.split('.')[0].length : valueString.length;
|
||
|
const minLength = result.length > 0 ? 2 : 1;
|
||
|
valueString = '0'.repeat(Math.max(0, minLength - wholeDigits)) + valueString;
|
||
|
} else {
|
||
|
prefix = '';
|
||
|
suffix = options.verbose ? ' ' + pluralize(long, value) : short;
|
||
|
}
|
||
|
|
||
|
result.push(prefix + valueString + suffix);
|
||
|
};
|
||
|
|
||
|
const parsed = parseMilliseconds(milliseconds);
|
||
|
|
||
|
add(Math.trunc(parsed.days / 365), 'year', 'y');
|
||
|
add(parsed.days % 365, 'day', 'd');
|
||
|
add(parsed.hours, 'hour', 'h');
|
||
|
add(parsed.minutes, 'minute', 'm');
|
||
|
|
||
|
if (
|
||
|
options.separateMilliseconds ||
|
||
|
options.formatSubMilliseconds ||
|
||
|
(!options.colonNotation && milliseconds < 1000)
|
||
|
) {
|
||
|
add(parsed.seconds, 'second', 's');
|
||
|
if (options.formatSubMilliseconds) {
|
||
|
add(parsed.milliseconds, 'millisecond', 'ms');
|
||
|
add(parsed.microseconds, 'microsecond', 'µs');
|
||
|
add(parsed.nanoseconds, 'nanosecond', 'ns');
|
||
|
} else {
|
||
|
const millisecondsAndBelow =
|
||
|
parsed.milliseconds +
|
||
|
(parsed.microseconds / 1000) +
|
||
|
(parsed.nanoseconds / 1e6);
|
||
|
|
||
|
const millisecondsDecimalDigits =
|
||
|
typeof options.millisecondsDecimalDigits === 'number' ?
|
||
|
options.millisecondsDecimalDigits :
|
||
|
0;
|
||
|
|
||
|
const roundedMiliseconds = millisecondsAndBelow >= 1 ?
|
||
|
Math.round(millisecondsAndBelow) :
|
||
|
Math.ceil(millisecondsAndBelow);
|
||
|
|
||
|
const millisecondsString = millisecondsDecimalDigits ?
|
||
|
millisecondsAndBelow.toFixed(millisecondsDecimalDigits) :
|
||
|
roundedMiliseconds;
|
||
|
|
||
|
add(
|
||
|
Number.parseFloat(millisecondsString, 10),
|
||
|
'millisecond',
|
||
|
'ms',
|
||
|
millisecondsString
|
||
|
);
|
||
|
}
|
||
|
} else {
|
||
|
const seconds = (milliseconds / 1000) % 60;
|
||
|
const secondsDecimalDigits =
|
||
|
typeof options.secondsDecimalDigits === 'number' ?
|
||
|
options.secondsDecimalDigits :
|
||
|
1;
|
||
|
const secondsFixed = floorDecimals(seconds, secondsDecimalDigits);
|
||
|
const secondsString = options.keepDecimalsOnWholeSeconds ?
|
||
|
secondsFixed :
|
||
|
secondsFixed.replace(/\.0+$/, '');
|
||
|
add(Number.parseFloat(secondsString, 10), 'second', 's', secondsString);
|
||
|
}
|
||
|
|
||
|
if (result.length === 0) {
|
||
|
return '0' + (options.verbose ? ' milliseconds' : 'ms');
|
||
|
}
|
||
|
|
||
|
if (options.compact) {
|
||
|
return result[0];
|
||
|
}
|
||
|
|
||
|
if (typeof options.unitCount === 'number') {
|
||
|
const separator = options.colonNotation ? '' : ' ';
|
||
|
return result.slice(0, Math.max(options.unitCount, 1)).join(separator);
|
||
|
}
|
||
|
|
||
|
return options.colonNotation ? result.join('') : result.join(' ');
|
||
|
};
|