diff --git a/index.d.ts b/index.d.ts index ef284f5..01fd9ba 100644 --- a/index.d.ts +++ b/index.d.ts @@ -117,7 +117,7 @@ prettyMilliseconds(new Date(2014, 0, 1, 10, 40) - new Date(2014, 0, 1, 10, 5)) ``` */ export default function prettyMilliseconds( - milliseconds: number, + milliseconds: number | bigint, options?: Options ): string; diff --git a/index.js b/index.js index 81fbec0..4048925 100644 --- a/index.js +++ b/index.js @@ -4,10 +4,12 @@ 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) { - if (!Number.isFinite(milliseconds)) { - throw new TypeError('Expected a finite number'); + const isBigInt = typeof milliseconds === 'bigint'; + if (!isBigInt && !Number.isFinite(milliseconds)) { + throw new TypeError('Expected a finite number or bigint'); } options = {...options}; @@ -58,27 +60,34 @@ export default function prettyMilliseconds(milliseconds, options) { }; const parsed = parseMilliseconds(milliseconds); + const days = BigInt(parsed.days); - add(BigInt(parsed.days) / 365n, 'year', 'y'); - add(parsed.days % 365, 'day', 'd'); - add(parsed.hours, 'hour', 'h'); - add(parsed.minutes, 'minute', 'm'); + add(days / 365n, 'year', 'y'); + add(days % 365n, 'day', 'd'); + add(Number(parsed.hours), 'hour', 'h'); + add(Number(parsed.minutes), 'minute', 'm'); if ( options.separateMilliseconds || options.formatSubMilliseconds || (!options.colonNotation && milliseconds < 1000) ) { - add(parsed.seconds, 'second', 's'); + 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(parsed.milliseconds, 'millisecond', 'ms'); - add(parsed.microseconds, 'microsecond', 'µs'); - add(parsed.nanoseconds, 'nanosecond', 'ns'); + add(milliseconds, 'millisecond', 'ms'); + add(microseconds, 'microsecond', 'µs'); + add(nanoseconds, 'nanosecond', 'ns'); } else { const millisecondsAndBelow - = parsed.milliseconds - + (parsed.microseconds / 1000) - + (parsed.nanoseconds / 1e6); + = milliseconds + + (microseconds / 1000) + + (nanoseconds / 1e6); const millisecondsDecimalDigits = typeof options.millisecondsDecimalDigits === 'number' @@ -101,7 +110,10 @@ export default function prettyMilliseconds(milliseconds, options) { ); } } else { - const seconds = (milliseconds / 1000) % 60; + const seconds = ( + (isBigInt ? Number(milliseconds % ONE_DAY_IN_MILLISECONDS) : milliseconds) + / 1000 + ) % 60; const secondsDecimalDigits = typeof options.secondsDecimalDigits === 'number' ? options.secondsDecimalDigits diff --git a/index.test-d.ts b/index.test-d.ts index 7dd03d7..b8ca75a 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -2,6 +2,7 @@ import {expectType} from 'tsd'; import prettyMilliseconds from './index.js'; expectType(prettyMilliseconds(1_335_669_000)); +expectType(prettyMilliseconds(1_335_669_000n)); expectType(prettyMilliseconds(1_335_669_000, {secondsDecimalDigits: 1})); expectType( prettyMilliseconds(1_335_669_000, {millisecondsDecimalDigits: 2}), diff --git a/readme.md b/readme.md index b1e823f..389c515 100644 --- a/readme.md +++ b/readme.md @@ -16,6 +16,9 @@ import prettyMilliseconds from 'pretty-ms'; prettyMilliseconds(1337000000); //=> '15d 11h 23m 20s' +prettyMilliseconds(1337000000n); +//=> '15d 11h 23m 20s' + prettyMilliseconds(1337); //=> '1.3s' @@ -49,7 +52,7 @@ prettyMilliseconds(new Date(2014, 0, 1, 10, 40) - new Date(2014, 0, 1, 10, 5)) #### milliseconds -Type: `number` +Type: `number | bigint` Milliseconds to humanize. diff --git a/test.js b/test.js index 5e79e1c..ccbfb0e 100644 --- a/test.js +++ b/test.js @@ -1,208 +1,246 @@ import test from 'ava'; import prettyMilliseconds from './index.js'; -test('prettify milliseconds', t => { - t.is(prettyMilliseconds(0), '0ms'); - t.is(prettyMilliseconds(0.1), '1ms'); - t.is(prettyMilliseconds(1), '1ms'); - t.is(prettyMilliseconds(999), '999ms'); - t.is(prettyMilliseconds(1000), '1s'); - t.is(prettyMilliseconds(1000 + 400), '1.4s'); - t.is(prettyMilliseconds((1000 * 2) + 400), '2.4s'); - t.is(prettyMilliseconds(1000 * 55), '55s'); - t.is(prettyMilliseconds(1000 * 67), '1m 7s'); - t.is(prettyMilliseconds(1000 * 60 * 5), '5m'); - t.is(prettyMilliseconds(1000 * 60 * 67), '1h 7m'); - t.is(prettyMilliseconds(1000 * 60 * 60 * 12), '12h'); - t.is(prettyMilliseconds(1000 * 60 * 60 * 40), '1d 16h'); - t.is(prettyMilliseconds(1000 * 60 * 60 * 999), '41d 15h'); - t.is(prettyMilliseconds(1000 * 60 * 60 * 24 * 465), '1y 100d'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465), '1y 154d 6h'); - t.is(prettyMilliseconds(119_999), '1m 59.9s'); - t.is(prettyMilliseconds(120_000), '2m'); - t.is(prettyMilliseconds(Number.MAX_SAFE_INTEGER), '285616y 151d 8h 59m 0.9s'); - t.is(prettyMilliseconds(Number.MAX_VALUE), '5700447535712568547083700427941645003808085225292279557374304680873482979681895890593452082909683139015032646149857723394516742095667500822861020052921074432454921864096959420926519725467567456931340929884912090099277441972878147362726992943838905852030073647982034630974035871792165820638724934142y 218d 8h 8m 48s'); +const toBigInt = milliseconds => { + if (typeof milliseconds !== 'number') { + return; + } + + try { + return BigInt(milliseconds); + } catch {} +}; + +function runTests({title, defaultOptions, cases}) { + test(title, t => { + const format = (milliseconds, options) => + prettyMilliseconds(milliseconds, {...defaultOptions, ...options}); + + for (const testCase of cases) { + const [ + milliseconds, + options, + expected, + ] = testCase.length === 3 ? testCase : [testCase[0], undefined, testCase[1]]; + + t.is(format(milliseconds, options), expected, `Number(${milliseconds})`); + + const bigint = toBigInt(milliseconds); + if (typeof bigint === 'bigint') { + t.is(format(bigint, options), expected, `BigInt(${bigint}n)`); + } + } + }); +} + +runTests({ + title: 'prettify milliseconds', + cases: [ + [0, '0ms'], + [0.1, '1ms'], + [1, '1ms'], + [999, '999ms'], + [1000, '1s'], + [1000 + 400, '1.4s'], + [(1000 * 2) + 400, '2.4s'], + [1000 * 55, '55s'], + [1000 * 67, '1m 7s'], + [1000 * 60 * 5, '5m'], + [1000 * 60 * 67, '1h 7m'], + [1000 * 60 * 60 * 12, '12h'], + [1000 * 60 * 60 * 40, '1d 16h'], + [1000 * 60 * 60 * 999, '41d 15h'], + [1000 * 60 * 60 * 24 * 465, '1y 100d'], + [1000 * 60 * 67 * 24 * 465, '1y 154d 6h'], + [119_999, '1m 59.9s'], + [120_000, '2m'], + [Number.MAX_SAFE_INTEGER, '285616y 151d 8h 59m 0.9s'], + ], }); -test('have a compact option', t => { - t.is(prettyMilliseconds(1000 + 4, {compact: true}), '1s'); - t.is(prettyMilliseconds(1000 * 60 * 60 * 999, {compact: true}), '41d'); - t.is(prettyMilliseconds(1000 * 60 * 60 * 24 * 465, {compact: true}), '1y'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {compact: true}), '1y'); +runTests({ + title: 'have a compact option', + defaultOptions: {compact: true}, + cases: [ + [1000 + 4, '1s'], + [1000 * 60 * 60 * 999, '41d'], + [1000 * 60 * 60 * 24 * 465, '1y'], + [1000 * 60 * 67 * 24 * 465, '1y'], + ], }); -test('have a unitCount option', t => { - t.is(prettyMilliseconds(1000 * 60, {unitCount: 0}), '1m'); - t.is(prettyMilliseconds(1000 * 60, {unitCount: 1}), '1m'); - t.is(prettyMilliseconds(1000 * 60 * 67, {unitCount: 1}), '1h'); - t.is(prettyMilliseconds(1000 * 60 * 67, {unitCount: 2}), '1h 7m'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {unitCount: 1}), '1y'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {unitCount: 2}), '1y 154d'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {unitCount: 3}), '1y 154d 6h'); +runTests({ + title: 'have a unitCount option', + cases: [ + [1000 * 60, {unitCount: 0}, '1m'], + [1000 * 60, {unitCount: 1}, '1m'], + [1000 * 60 * 67, {unitCount: 1}, '1h'], + [1000 * 60 * 67, {unitCount: 2}, '1h 7m'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 1}, '1y'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 2}, '1y 154d'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 3}, '1y 154d 6h'], + ], }); -test('have a secondsDecimalDigits option', t => { - t.is(prettyMilliseconds(10_000), '10s'); - t.is(prettyMilliseconds(33_333), '33.3s'); - t.is(prettyMilliseconds(999, {secondsDecimalDigits: 0}), '999ms'); - t.is(prettyMilliseconds(1000, {secondsDecimalDigits: 0}), '1s'); - t.is(prettyMilliseconds(1999, {secondsDecimalDigits: 0}), '1s'); - t.is(prettyMilliseconds(2000, {secondsDecimalDigits: 0}), '2s'); - t.is(prettyMilliseconds(33_333, {secondsDecimalDigits: 0}), '33s'); - t.is(prettyMilliseconds(33_333, {secondsDecimalDigits: 4}), '33.3330s'); +runTests({ + title: 'have a secondsDecimalDigits option', + cases: [ + [10_000, '10s'], + [33_333, '33.3s'], + [999, {secondsDecimalDigits: 0}, '999ms'], + [1000, {secondsDecimalDigits: 0}, '1s'], + [1999, {secondsDecimalDigits: 0}, '1s'], + [2000, {secondsDecimalDigits: 0}, '2s'], + [33_333, {secondsDecimalDigits: 0}, '33s'], + [33_333, {secondsDecimalDigits: 4}, '33.3330s'], + ], }); -test('have a millisecondsDecimalDigits option', t => { - t.is(prettyMilliseconds(33.333), '33ms'); - t.is(prettyMilliseconds(33.333, {millisecondsDecimalDigits: 0}), '33ms'); - t.is(prettyMilliseconds(33.333, {millisecondsDecimalDigits: 4}), '33.3330ms'); +runTests({ + title: 'have a millisecondsDecimalDigits option', + cases: [ + [33.333, '33ms'], + [33.333, {millisecondsDecimalDigits: 0}, '33ms'], + [33.333, {millisecondsDecimalDigits: 4}, '33.3330ms'], + ], }); -test('have a keepDecimalsOnWholeSeconds option', t => { - t.is(prettyMilliseconds(1000 * 33, {secondsDecimalDigits: 2, keepDecimalsOnWholeSeconds: true}), '33.00s'); - t.is(prettyMilliseconds(1000 * 33.000_04, {secondsDecimalDigits: 2, keepDecimalsOnWholeSeconds: true}), '33.00s'); +runTests({ + title: 'have a keepDecimalsOnWholeSeconds option', + defaultOptions: {keepDecimalsOnWholeSeconds: true}, + cases: [ + [1000 * 33, {secondsDecimalDigits: 2}, '33.00s'], + [1000 * 33.000_04, {secondsDecimalDigits: 2}, '33.00s'], + ], }); -test('have a verbose option', t => { - const format = milliseconds => prettyMilliseconds(milliseconds, {verbose: true}); - - t.is(format(0), '0 milliseconds'); - t.is(format(0.1), '1 millisecond'); - t.is(format(1), '1 millisecond'); - t.is(format(1000), '1 second'); - t.is(format(1000 + 400), '1.4 seconds'); - t.is(format((1000 * 2) + 400), '2.4 seconds'); - t.is(format(1000 * 5), '5 seconds'); - t.is(format(1000 * 55), '55 seconds'); - t.is(format(1000 * 67), '1 minute 7 seconds'); - t.is(format(1000 * 60 * 5), '5 minutes'); - t.is(format(1000 * 60 * 67), '1 hour 7 minutes'); - t.is(format(1000 * 60 * 60 * 12), '12 hours'); - t.is(format(1000 * 60 * 60 * 40), '1 day 16 hours'); - t.is(format(1000 * 60 * 60 * 999), '41 days 15 hours'); - t.is(format(1000 * 60 * 60 * 24 * 465), '1 year 100 days'); - t.is(format(1000 * 60 * 67 * 24 * 465), '1 year 154 days 6 hours'); +runTests({ + title: 'have a verbose option', + defaultOptions: {verbose: true}, + cases: [ + [0, '0 milliseconds'], + [0.1, '1 millisecond'], + [1, '1 millisecond'], + [1000, '1 second'], + [1000 + 400, '1.4 seconds'], + [(1000 * 2) + 400, '2.4 seconds'], + [1000 * 5, '5 seconds'], + [1000 * 55, '55 seconds'], + [1000 * 67, '1 minute 7 seconds'], + [1000 * 60 * 5, '5 minutes'], + [1000 * 60 * 67, '1 hour 7 minutes'], + [1000 * 60 * 60 * 12, '12 hours'], + [1000 * 60 * 60 * 40, '1 day 16 hours'], + [1000 * 60 * 60 * 999, '41 days 15 hours'], + [1000 * 60 * 60 * 24 * 465, '1 year 100 days'], + [1000 * 60 * 67 * 24 * 465, '1 year 154 days 6 hours'], + ], }); -test('have a separateMilliseconds option', t => { - t.is(prettyMilliseconds(1100, {separateMilliseconds: false}), '1.1s'); - t.is(prettyMilliseconds(1100, {separateMilliseconds: true}), '1s 100ms'); +runTests({ + title: 'have a separateMilliseconds option', + cases: [ + [1100, {separateMilliseconds: false}, '1.1s'], + [1100, {separateMilliseconds: true}, '1s 100ms'], + ], }); -test('have a formatSubMilliseconds option', t => { - t.is(prettyMilliseconds(0.4, {formatSubMilliseconds: true}), '400µs'); - t.is(prettyMilliseconds(0.123_571, {formatSubMilliseconds: true}), '123µs 571ns'); - t.is(prettyMilliseconds(0.123_456_789, {formatSubMilliseconds: true}), '123µs 456ns'); - t.is( - prettyMilliseconds((60 * 60 * 1000) + (23 * 1000) + 433 + 0.123_456, { - formatSubMilliseconds: true, - }), - '1h 23s 433ms 123µs 456ns', - ); +runTests({ + title: 'have a formatSubMilliseconds option', + defaultOptions: {formatSubMilliseconds: true}, + cases: [ + [0.4, '400µs'], + [0.123_571, '123µs 571ns'], + [0.123_456_789, '123µs 456ns'], + [(60 * 60 * 1000) + (23 * 1000) + 433 + 0.123_456, '1h 23s 433ms 123µs 456ns'], + ], }); -test('work with verbose and compact options', t => { - const format = milliseconds => prettyMilliseconds(milliseconds, { - verbose: true, - compact: true, - }); - - t.is(format(1000), '1 second'); - t.is(format(1000 + 400), '1 second'); - t.is(format((1000 * 2) + 400), '2 seconds'); - t.is(format(1000 * 5), '5 seconds'); - t.is(format(1000 * 55), '55 seconds'); - t.is(format(1000 * 67), '1 minute'); - t.is(format(1000 * 60 * 5), '5 minutes'); - t.is(format(1000 * 60 * 67), '1 hour'); - t.is(format(1000 * 60 * 60 * 12), '12 hours'); - t.is(format(1000 * 60 * 60 * 40), '1 day'); - t.is(format(1000 * 60 * 60 * 999), '41 days'); - t.is(format(1000 * 60 * 60 * 24 * 465), '1 year'); - t.is(format(1000 * 60 * 67 * 24 * 750), '2 years'); +runTests({ + title: 'work with verbose and compact options', + defaultOptions: {verbose: true, compact: true}, + cases: [ + [1000, '1 second'], + [1000 + 400, '1 second'], + [(1000 * 2) + 400, '2 seconds'], + [1000 * 5, '5 seconds'], + [1000 * 55, '55 seconds'], + [1000 * 67, '1 minute'], + [1000 * 60 * 5, '5 minutes'], + [1000 * 60 * 67, '1 hour'], + [1000 * 60 * 60 * 12, '12 hours'], + [1000 * 60 * 60 * 40, '1 day'], + [1000 * 60 * 60 * 999, '41 days'], + [1000 * 60 * 60 * 24 * 465, '1 year'], + [1000 * 60 * 67 * 24 * 750, '2 years'], + ], }); -test('work with verbose and unitCount options', t => { - t.is(prettyMilliseconds(1000 * 60, {verbose: true, unitCount: 1}), '1 minute'); - t.is(prettyMilliseconds(1000 * 60 * 67, {verbose: true, unitCount: 1}), '1 hour'); - t.is(prettyMilliseconds(1000 * 60 * 67, {verbose: true, unitCount: 2}), '1 hour 7 minutes'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {verbose: true, unitCount: 1}), '1 year'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {verbose: true, unitCount: 2}), '1 year 154 days'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {verbose: true, unitCount: 3}), '1 year 154 days 6 hours'); +runTests({ + title: 'work with verbose and unitCount options', + defaultOptions: {verbose: true}, + cases: [ + [1000 * 60, {unitCount: 1}, '1 minute'], + [1000 * 60 * 67, {unitCount: 1}, '1 hour'], + [1000 * 60 * 67, {unitCount: 2}, '1 hour 7 minutes'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 1}, '1 year'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 2}, '1 year 154 days'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 3}, '1 year 154 days 6 hours'], + ], }); -test('work with verbose and secondsDecimalDigits options', t => { - const format = milliseconds => prettyMilliseconds(milliseconds, { - verbose: true, - secondsDecimalDigits: 4, - }); - - t.is(format(1000), '1 second'); - t.is(format(1000 + 400), '1.4000 seconds'); - t.is(format((1000 * 2) + 400), '2.4000 seconds'); - t.is(format((1000 * 5) + 254), '5.2540 seconds'); - t.is(format(33_333), '33.3330 seconds'); +runTests({ + title: 'work with verbose and secondsDecimalDigits options', + defaultOptions: {verbose: true, secondsDecimalDigits: 4}, + cases: [ + [1000, '1 second'], + [1000 + 400, '1.4000 seconds'], + [(1000 * 2) + 400, '2.4000 seconds'], + [(1000 * 5) + 254, '5.2540 seconds'], + [33_333, '33.3330 seconds'], + ], }); -test('work with verbose and millisecondsDecimalDigits options', t => { - const format = milliseconds => prettyMilliseconds(milliseconds, { - verbose: true, - millisecondsDecimalDigits: 4, - }); - - t.is(format(1), '1.0000 millisecond'); - t.is(format(1 + 0.4), '1.4000 milliseconds'); - t.is(format((1 * 2) + 0.4), '2.4000 milliseconds'); - t.is(format((1 * 5) + 0.254), '5.2540 milliseconds'); - t.is(format(33.333), '33.3330 milliseconds'); +runTests({ + title: 'work with verbose and millisecondsDecimalDigits options', + defaultOptions: {verbose: true, millisecondsDecimalDigits: 4}, + cases: [ + [1, '1.0000 millisecond'], + [1 + 0.4, '1.4000 milliseconds'], + [(1 * 2) + 0.4, '2.4000 milliseconds'], + [(1 * 5) + 0.254, '5.2540 milliseconds'], + [33.333, '33.3330 milliseconds'], + ], }); -test('work with verbose and formatSubMilliseconds options', t => { - t.is( - prettyMilliseconds(0.4, {formatSubMilliseconds: true, verbose: true}), - '400 microseconds', - ); - t.is( - prettyMilliseconds(0.123_571, { - formatSubMilliseconds: true, - verbose: true, - }), - '123 microseconds 571 nanoseconds', - ); - t.is( - prettyMilliseconds(0.123_456_789, { - formatSubMilliseconds: true, - verbose: true, - }), - '123 microseconds 456 nanoseconds', - ); - t.is( - prettyMilliseconds(0.001, {formatSubMilliseconds: true, verbose: true}), - '1 microsecond', - ); +runTests({ + title: 'work with verbose and formatSubMilliseconds options', + defaultOptions: {formatSubMilliseconds: true, verbose: true}, + cases: [ + [0.4, '400 microseconds'], + [0.123_571, '123 microseconds 571 nanoseconds'], + [0.123_456_789, '123 microseconds 456 nanoseconds'], + [0.001, '1 microsecond'], + ], }); -test('compact option overrides unitCount option', t => { - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {verbose: true, compact: true, unitCount: 1}), '1 year'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {verbose: true, compact: true, unitCount: 2}), '1 year'); - t.is(prettyMilliseconds(1000 * 60 * 67 * 24 * 465, {verbose: true, compact: true, unitCount: 3}), '1 year'); +runTests({ + title: 'compact option overrides unitCount option', + defaultOptions: {verbose: true, compact: true}, + cases: [ + [1000 * 60 * 67 * 24 * 465, {unitCount: 1}, '1 year'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 2}, '1 year'], + [1000 * 60 * 67 * 24 * 465, {unitCount: 3}, '1 year'], + ], }); -test('work with separateMilliseconds and formatSubMilliseconds options', t => { - t.is( - prettyMilliseconds(1010.340_067, { - separateMilliseconds: true, - formatSubMilliseconds: true, - }), - '1s 10ms 340µs 67ns', - ); - t.is( - prettyMilliseconds((60 * 1000) + 34 + 0.000_005, { - separateMilliseconds: true, - formatSubMilliseconds: true, - }), - '1m 34ms 5ns', - ); +runTests({ + title: 'work with separateMilliseconds and formatSubMilliseconds options', + defaultOptions: {separateMilliseconds: true, formatSubMilliseconds: true}, + cases: [ + [1010.340_067, '1s 10ms 340µs 67ns'], + [(60 * 1000) + 34 + 0.000_005, '1m 34ms 5ns'], + ], }); test('throw on invalid', t => { @@ -219,94 +257,122 @@ test('throw on invalid', t => { }); }); -test('properly rounds milliseconds with secondsDecimalDigits', t => { - const format = milliseconds => - prettyMilliseconds(milliseconds, { - verbose: true, - secondsDecimalDigits: 0, - }); - t.is(format(3 * 60 * 1000), '3 minutes'); - t.is(format((3 * 60 * 1000) - 1), '2 minutes 59 seconds'); - t.is(format(365 * 24 * 3600 * 1e3), '1 year'); - t.is(format((365 * 24 * 3600 * 1e3) - 1), '364 days 23 hours 59 minutes 59 seconds'); - t.is(format(24 * 3600 * 1e3), '1 day'); - t.is(format((24 * 3600 * 1e3) - 1), '23 hours 59 minutes 59 seconds'); - t.is(format(3600 * 1e3), '1 hour'); - t.is(format((3600 * 1e3) - 1), '59 minutes 59 seconds'); - t.is(format(2 * 3600 * 1e3), '2 hours'); - t.is(format((2 * 3600 * 1e3) - 1), '1 hour 59 minutes 59 seconds'); +runTests({ + title: 'properly rounds milliseconds with secondsDecimalDigits', + defaultOptions: {verbose: true, secondsDecimalDigits: 0}, + cases: [ + [3 * 60 * 1000, '3 minutes'], + [(3 * 60 * 1000) - 1, '2 minutes 59 seconds'], + [365 * 24 * 3600 * 1e3, '1 year'], + [(365 * 24 * 3600 * 1e3) - 1, '364 days 23 hours 59 minutes 59 seconds'], + [24 * 3600 * 1e3, '1 day'], + [(24 * 3600 * 1e3) - 1, '23 hours 59 minutes 59 seconds'], + [3600 * 1e3, '1 hour'], + [(3600 * 1e3) - 1, '59 minutes 59 seconds'], + [2 * 3600 * 1e3, '2 hours'], + [(2 * 3600 * 1e3) - 1, '1 hour 59 minutes 59 seconds'], + ], +}); + +runTests({ + title: '`colonNotation` option', + defaultOptions: {colonNotation: true}, + cases: [ + // Default formats + [1000, '0:01'], + [1543, '0:01.5'], + [1000 * 60, '1:00'], + [1000 * 90, '1:30'], + [95_543, '1:35.5'], + [(1000 * 60 * 10) + 543, '10:00.5'], + [(1000 * 60 * 59) + (1000 * 59) + 543, '59:59.5'], + [(1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543, '15:59:59.5'], + + // Together with `secondsDecimalDigits` + [999, {secondsDecimalDigits: 0}, '0:00'], + [999, {secondsDecimalDigits: 1}, '0:00.9'], + [999, {secondsDecimalDigits: 2}, '0:00.99'], + [999, {secondsDecimalDigits: 3}, '0:00.999'], + [1000, {secondsDecimalDigits: 0}, '0:01'], + [1000, {secondsDecimalDigits: 1}, '0:01'], + [1000, {secondsDecimalDigits: 2}, '0:01'], + [1000, {secondsDecimalDigits: 3}, '0:01'], + [1001, {secondsDecimalDigits: 0}, '0:01'], + [1001, {secondsDecimalDigits: 1}, '0:01'], + [1001, {secondsDecimalDigits: 2}, '0:01'], + [1001, {secondsDecimalDigits: 3}, '0:01.001'], + [1543, {secondsDecimalDigits: 0}, '0:01'], + [1543, {secondsDecimalDigits: 1}, '0:01.5'], + [1543, {secondsDecimalDigits: 2}, '0:01.54'], + [1543, {secondsDecimalDigits: 3}, '0:01.543'], + [95_543, {secondsDecimalDigits: 0}, '1:35'], + [95_543, {secondsDecimalDigits: 1}, '1:35.5'], + [95_543, {secondsDecimalDigits: 2}, '1:35.54'], + [95_543, {secondsDecimalDigits: 3}, '1:35.543'], + [(1000 * 60 * 10) + 543, {secondsDecimalDigits: 3}, '10:00.543'], + [(1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543, {secondsDecimalDigits: 3}, '15:59:59.543'], + + // Together with `keepDecimalsOnWholeSeconds` + [999, {secondsDecimalDigits: 0, keepDecimalsOnWholeSeconds: true}, '0:00'], + [999, {secondsDecimalDigits: 1, keepDecimalsOnWholeSeconds: true}, '0:00.9'], + [999, {secondsDecimalDigits: 2, keepDecimalsOnWholeSeconds: true}, '0:00.99'], + [999, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}, '0:00.999'], + [1000, {keepDecimalsOnWholeSeconds: true}, '0:01.0'], + [1000, {secondsDecimalDigits: 0, keepDecimalsOnWholeSeconds: true}, '0:01'], + [1000, {secondsDecimalDigits: 1, keepDecimalsOnWholeSeconds: true}, '0:01.0'], + [1000, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}, '0:01.000'], + [1000 * 90, {keepDecimalsOnWholeSeconds: true}, '1:30.0'], + [1000 * 90, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}, '1:30.000'], + [1000 * 60 * 10, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}, '10:00.000'], + + // Together with `unitCount` + [1000 * 90, {secondsDecimalDigits: 0, unitCount: 1}, '1'], + [1000 * 90, {secondsDecimalDigits: 0, unitCount: 2}, '1:30'], + [1000 * 60 * 90, {secondsDecimalDigits: 0, unitCount: 3}, '1:30:00'], + [95_543, {secondsDecimalDigits: 1, unitCount: 1}, '1'], + [95_543, {secondsDecimalDigits: 1, unitCount: 2}, '1:35.5'], + [95_543 + (1000 * 60 * 60), {secondsDecimalDigits: 1, unitCount: 3}, '1:01:35.5'], + + // Make sure incompatible options fall back to `colonNotation` + [(1000 * 60 * 59) + (1000 * 59) + 543, {formatSubMilliseconds: true}, '59:59.5'], + [(1000 * 60 * 59) + (1000 * 59) + 543, {separateMilliseconds: true}, '59:59.5'], + [(1000 * 60 * 59) + (1000 * 59) + 543, {verbose: true}, '59:59.5'], + [(1000 * 60 * 59) + (1000 * 59) + 543, {compact: true}, '59:59.5'], + + // Big numbers + [Number.MAX_SAFE_INTEGER, '285616:151:08:59:00.9'], + ], }); -test('`colonNotation` option', t => { - const format = (milliseconds, options) => - prettyMilliseconds(milliseconds, { - colonNotation: true, - ...options, - }); - // Default formats - t.is(format(1000), '0:01'); - t.is(format(1543), '0:01.5'); - t.is(format(1000 * 60), '1:00'); - t.is(format(1000 * 90), '1:30'); - t.is(format(95_543), '1:35.5'); - t.is(format((1000 * 60 * 10) + 543), '10:00.5'); - t.is(format((1000 * 60 * 59) + (1000 * 59) + 543), '59:59.5'); - t.is(format((1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543), '15:59:59.5'); - - // Together with `secondsDecimalDigits` - t.is(format(999, {secondsDecimalDigits: 0}), '0:00'); - t.is(format(999, {secondsDecimalDigits: 1}), '0:00.9'); - t.is(format(999, {secondsDecimalDigits: 2}), '0:00.99'); - t.is(format(999, {secondsDecimalDigits: 3}), '0:00.999'); - t.is(format(1000, {secondsDecimalDigits: 0}), '0:01'); - t.is(format(1000, {secondsDecimalDigits: 1}), '0:01'); - t.is(format(1000, {secondsDecimalDigits: 2}), '0:01'); - t.is(format(1000, {secondsDecimalDigits: 3}), '0:01'); - t.is(format(1001, {secondsDecimalDigits: 0}), '0:01'); - t.is(format(1001, {secondsDecimalDigits: 1}), '0:01'); - t.is(format(1001, {secondsDecimalDigits: 2}), '0:01'); - t.is(format(1001, {secondsDecimalDigits: 3}), '0:01.001'); - t.is(format(1543, {secondsDecimalDigits: 0}), '0:01'); - t.is(format(1543, {secondsDecimalDigits: 1}), '0:01.5'); - t.is(format(1543, {secondsDecimalDigits: 2}), '0:01.54'); - t.is(format(1543, {secondsDecimalDigits: 3}), '0:01.543'); - t.is(format(95_543, {secondsDecimalDigits: 0}), '1:35'); - t.is(format(95_543, {secondsDecimalDigits: 1}), '1:35.5'); - t.is(format(95_543, {secondsDecimalDigits: 2}), '1:35.54'); - t.is(format(95_543, {secondsDecimalDigits: 3}), '1:35.543'); - t.is(format((1000 * 60 * 10) + 543, {secondsDecimalDigits: 3}), '10:00.543'); - t.is(format((1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543, {secondsDecimalDigits: 3}), '15:59:59.543'); - - // Together with `keepDecimalsOnWholeSeconds` - t.is(format(999, {secondsDecimalDigits: 0, keepDecimalsOnWholeSeconds: true}), '0:00'); - t.is(format(999, {secondsDecimalDigits: 1, keepDecimalsOnWholeSeconds: true}), '0:00.9'); - t.is(format(999, {secondsDecimalDigits: 2, keepDecimalsOnWholeSeconds: true}), '0:00.99'); - t.is(format(999, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}), '0:00.999'); - t.is(format(1000, {keepDecimalsOnWholeSeconds: true}), '0:01.0'); - t.is(format(1000, {secondsDecimalDigits: 0, keepDecimalsOnWholeSeconds: true}), '0:01'); - t.is(format(1000, {secondsDecimalDigits: 1, keepDecimalsOnWholeSeconds: true}), '0:01.0'); - t.is(format(1000, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}), '0:01.000'); - t.is(format(1000 * 90, {keepDecimalsOnWholeSeconds: true}), '1:30.0'); - t.is(format(1000 * 90, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}), '1:30.000'); - t.is(format(1000 * 60 * 10, {secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}), '10:00.000'); - - // Together with `unitCount` - t.is(format(1000 * 90, {secondsDecimalDigits: 0, unitCount: 1}), '1'); - t.is(format(1000 * 90, {secondsDecimalDigits: 0, unitCount: 2}), '1:30'); - t.is(format(1000 * 60 * 90, {secondsDecimalDigits: 0, unitCount: 3}), '1:30:00'); - t.is(format(95_543, {secondsDecimalDigits: 1, unitCount: 1}), '1'); - t.is(format(95_543, {secondsDecimalDigits: 1, unitCount: 2}), '1:35.5'); - t.is(format(95_543 + (1000 * 60 * 60), {secondsDecimalDigits: 1, unitCount: 3}), '1:01:35.5'); - - // Make sure incompatible options fall back to `colonNotation` - t.is(format((1000 * 60 * 59) + (1000 * 59) + 543, {formatSubMilliseconds: true}), '59:59.5'); - t.is(format((1000 * 60 * 59) + (1000 * 59) + 543, {separateMilliseconds: true}), '59:59.5'); - t.is(format((1000 * 60 * 59) + (1000 * 59) + 543, {verbose: true}), '59:59.5'); - t.is(format((1000 * 60 * 59) + (1000 * 59) + 543, {compact: true}), '59:59.5'); - - // Big numbers - t.is(format(Number.MAX_SAFE_INTEGER), '285616:151:08:59:00.9'); - t.is(format(Number.MAX_VALUE), '5700447535712568547083700427941645003808085225292279557374304680873482979681895890593452082909683139015032646149857723394516742095667500822861020052921074432454921864096959420926519725467567456931340929884912090099277441972878147362726992943838905852030073647982034630974035871792165820638724934142:218:08:08:48'); +test('Big numbers', t => { + t.is( + prettyMilliseconds(Number.MAX_VALUE), + '5700447535712568547083700427941645003808085225292279557374304680873482979681895890593452082909683139015032646149857723394516742095667500822861020052921074432454921864096959420926519725467567456931340929884912090099277441972878147362726992943838905852030073647982034630974035871792165820638724934142y 218d 8h 8m 48s', + ); + t.is( + prettyMilliseconds(BigInt(Number.MAX_VALUE)), + '5700447535712568836077099940756733789893155997141203595856084373514626483384973958669126034778249561502424497511237394223564222694364034207523704550467323597984839883832803211448677387442583997465622415920063861691545637902816557209722493636863373550063350653353143175061459195234630260059944318435y 207d 22h 14m 18.3s', + ); + + const duration = 0n + // 1ms + + 1n + // 2s + + (2n * 1000n) + // 3m + + (3n * 1000n * 60n) + // 4h + + (4n * 1000n * 60n * 60n) + // Days + + (BigInt(Number.MAX_VALUE) * 1000n * 60n * 60n * 24n); + t.is( + prettyMilliseconds(duration), + '492518667085565947437061434881381799446768678152999990681965689871663728164461750029012489404840762113809476584970910860915948840793052555530048073160376758865890165963154197469165726275039257381029776735493517650149543114803350542920023450224995474725473496449711570325310074468272054469179189112833218790y 18d 4h 3m 2s', + ); + t.is( + prettyMilliseconds(duration, {colonNotation: true}), + '492518667085565947437061434881381799446768678152999990681965689871663728164461750029012489404840762113809476584970910860915948840793052555530048073160376758865890165963154197469165726275039257381029776735493517650149543114803350542920023450224995474725473496449711570325310074468272054469179189112833218790:18:04:03:02', + ); }); test('pure', t => {