123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- import parseMilliseconds from 'parse-ms';
- const isZero = value => value === 0 || value === 0n;
- const pluralize = (word, count) => (count === 1 || count === 1n) ? word : `${word}s`;
- const SECOND_ROUNDING_EPSILON = 0.000_000_1;
- const ONE_DAY_IN_MILLISECONDS = 24n * 60n * 60n * 1000n;
- export default function prettyMilliseconds(milliseconds, options) {
- const isBigInt = typeof milliseconds === 'bigint';
- if (!isBigInt && !Number.isFinite(milliseconds)) {
- throw new TypeError('Expected a finite number or bigint');
- }
- options = {...options};
- const sign = milliseconds < 0 ? '-' : '';
- milliseconds = milliseconds < 0 ? -milliseconds : milliseconds; // Cannot use `Math.abs()` because of BigInt support.
- if (options.colonNotation) {
- options.compact = false;
- options.formatSubMilliseconds = false;
- options.separateMilliseconds = false;
- options.verbose = false;
- }
- if (options.compact) {
- options.unitCount = 1;
- options.secondsDecimalDigits = 0;
- options.millisecondsDecimalDigits = 0;
- }
- let 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)
- && isZero(value)
- && !(options.colonNotation && short === 'm')) {
- return;
- }
- valueString ??= String(value);
- if (options.colonNotation) {
- 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 {
- valueString += options.verbose ? ' ' + pluralize(long, value) : short;
- }
- result.push(valueString);
- };
- const parsed = parseMilliseconds(milliseconds);
- const days = BigInt(parsed.days);
- if (options.hideYearAndDays) {
- add((BigInt(days) * 24n) + BigInt(parsed.hours), 'hour', 'h');
- } else {
- if (options.hideYear) {
- add(days, 'day', 'd');
- } else {
- add(days / 365n, 'year', 'y');
- add(days % 365n, 'day', 'd');
- }
- add(Number(parsed.hours), 'hour', 'h');
- }
- add(Number(parsed.minutes), 'minute', 'm');
- if (!options.hideSeconds) {
- if (
- options.separateMilliseconds
- || options.formatSubMilliseconds
- || (!options.colonNotation && milliseconds < 1000)
- ) {
- const seconds = Number(parsed.seconds);
- const milliseconds = Number(parsed.milliseconds);
- const microseconds = Number(parsed.microseconds);
- const nanoseconds = Number(parsed.nanoseconds);
- add(seconds, 'second', 's');
- if (options.formatSubMilliseconds) {
- add(milliseconds, 'millisecond', 'ms');
- add(microseconds, 'microsecond', 'µs');
- add(nanoseconds, 'nanosecond', 'ns');
- } else {
- const millisecondsAndBelow
- = milliseconds
- + (microseconds / 1000)
- + (nanoseconds / 1e6);
- const millisecondsDecimalDigits
- = typeof options.millisecondsDecimalDigits === 'number'
- ? options.millisecondsDecimalDigits
- : 0;
- const roundedMilliseconds = millisecondsAndBelow >= 1
- ? Math.round(millisecondsAndBelow)
- : Math.ceil(millisecondsAndBelow);
- const millisecondsString = millisecondsDecimalDigits
- ? millisecondsAndBelow.toFixed(millisecondsDecimalDigits)
- : roundedMilliseconds;
- add(
- Number.parseFloat(millisecondsString),
- 'millisecond',
- 'ms',
- millisecondsString,
- );
- }
- } else {
- const seconds = (
- (isBigInt ? Number(milliseconds % ONE_DAY_IN_MILLISECONDS) : 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), 'second', 's', secondsString);
- }
- }
- if (result.length === 0) {
- return sign + '0' + (options.verbose ? ' milliseconds' : 'ms');
- }
- const separator = options.colonNotation ? ':' : ' ';
- if (typeof options.unitCount === 'number') {
- result = result.slice(0, Math.max(options.unitCount, 1));
- }
- return sign + result.join(separator);
- }
|