pax_global_header00006660000000000000000000000064147156130350014517gustar00rootroot0000000000000052 comment=3e851280acce1393bad97136f9fdf3c31a0d077d pretty-ms-9.2.0/000077500000000000000000000000001471561303500134735ustar00rootroot00000000000000pretty-ms-9.2.0/.editorconfig000066400000000000000000000002571471561303500161540ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 pretty-ms-9.2.0/.gitattributes000066400000000000000000000000231471561303500163610ustar00rootroot00000000000000* text=auto eol=lf pretty-ms-9.2.0/.github/000077500000000000000000000000001471561303500150335ustar00rootroot00000000000000pretty-ms-9.2.0/.github/security.md000066400000000000000000000002631471561303500172250ustar00rootroot00000000000000# Security Policy To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. pretty-ms-9.2.0/.github/workflows/000077500000000000000000000000001471561303500170705ustar00rootroot00000000000000pretty-ms-9.2.0/.github/workflows/main.yml000066400000000000000000000006451471561303500205440ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: test: name: Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: - 22 - 18 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm test pretty-ms-9.2.0/.gitignore000066400000000000000000000000271471561303500154620ustar00rootroot00000000000000node_modules yarn.lock pretty-ms-9.2.0/.npmrc000066400000000000000000000000231471561303500146060ustar00rootroot00000000000000package-lock=false pretty-ms-9.2.0/index.d.ts000066400000000000000000000063311471561303500153770ustar00rootroot00000000000000export type Options = { /** Number of digits to appear after the seconds decimal point. @default 1 */ readonly secondsDecimalDigits?: number; /** Number of digits to appear after the milliseconds decimal point. Useful in combination with [`process.hrtime()`](https://nodejs.org/api/process.html#process_process_hrtime). @default 0 */ readonly millisecondsDecimalDigits?: number; /** Keep milliseconds on whole seconds: `13s` → `13.0s`. Useful when you are showing a number of seconds spent on an operation and don't want the width of the output to change when hitting a whole number. @default false */ readonly keepDecimalsOnWholeSeconds?: boolean; /** Only show the first unit: `1h 10m` → `1h`. Also ensures that `millisecondsDecimalDigits` and `secondsDecimalDigits` are both set to `0`. @default false */ readonly compact?: boolean; /** Number of units to show. Setting `compact` to `true` overrides this option. @default Infinity */ readonly unitCount?: number; /** Use full-length units: `5h 1m 45s` → `5 hours 1 minute 45 seconds`. @default false */ readonly verbose?: boolean; /** Show milliseconds separately. This means they won't be included in the decimal part of the seconds. @default false */ readonly separateMilliseconds?: boolean; /** Show microseconds and nanoseconds. @default false */ readonly formatSubMilliseconds?: boolean; /** Display time using colon notation: `5h 1m 45s` → `5:01:45`. Always shows time in at least minutes: `1s` → `0:01` Useful when you want to display time without the time units, similar to a digital watch. Setting `colonNotation` to `true` overrides the following options to `false`: - `compact` - `formatSubMilliseconds` - `separateMilliseconds` - `verbose` @default false */ readonly colonNotation?: boolean; /** Hides the year and shows the hidden year additionally as days (365 per year): `1y 3d 5h 1m 45s` → `368d 5h 1m 45s`. @default false */ readonly hideYear?: boolean; /** Hides the year and days and shows the hidden values additionally as hours: `1y 3d 5h 1m 45s` → `8837h 1m 45s`. @default false */ readonly hideYearAndDays?: boolean; /** Hides the seconds: `1y 3d 5h 1m 45s` → `1y 3d 5h 1m`. @default false */ readonly hideSeconds?: boolean; }; /** Convert milliseconds to a human readable string: `1337000000` → `15d 11h 23m 20s`. @param milliseconds - Milliseconds to humanize. @example ``` import prettyMilliseconds from 'pretty-ms'; prettyMilliseconds(1337000000); //=> '15d 11h 23m 20s' prettyMilliseconds(1337); //=> '1.3s' prettyMilliseconds(133); //=> '133ms' // `compact` option prettyMilliseconds(1337, {compact: true}); //=> '1s' // `verbose` option prettyMilliseconds(1335669000, {verbose: true}); //=> '15 days 11 hours 1 minute 9 seconds' // `colonNotation` option prettyMilliseconds(95500, {colonNotation: true}); //=> '1:35.5' // `formatSubMilliseconds` option prettyMilliseconds(100.400080, {formatSubMilliseconds: true}) //=> '100ms 400µs 80ns' // Can be useful for time durations prettyMilliseconds(new Date(2014, 0, 1, 10, 40) - new Date(2014, 0, 1, 10, 5)) //=> '35m' ``` */ export default function prettyMilliseconds( milliseconds: number | bigint, options?: Options ): string; pretty-ms-9.2.0/index.js000066400000000000000000000105261471561303500151440ustar00rootroot00000000000000import 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); } pretty-ms-9.2.0/index.test-d.ts000066400000000000000000000016341471561303500163550ustar00rootroot00000000000000import {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}), ); expectType( prettyMilliseconds(1_335_669_000, {keepDecimalsOnWholeSeconds: true}), ); expectType(prettyMilliseconds(1337, {compact: true})); expectType(prettyMilliseconds(1_335_669_000, {unitCount: 2})); expectType(prettyMilliseconds(1_335_669_000, {verbose: true})); expectType( prettyMilliseconds(1_335_669_000, {separateMilliseconds: true}), ); expectType( prettyMilliseconds(1_335_669_000, {formatSubMilliseconds: true}), ); expectType( prettyMilliseconds(1_335_669_000, {colonNotation: true}), ); pretty-ms-9.2.0/license000066400000000000000000000021351471561303500150410ustar00rootroot00000000000000MIT License Copyright (c) Sindre Sorhus (https://sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pretty-ms-9.2.0/package.json000066400000000000000000000017101471561303500157600ustar00rootroot00000000000000{ "name": "pretty-ms", "version": "9.2.0", "description": "Convert milliseconds to a human readable string: `1337000000` → `15d 11h 23m 20s`", "license": "MIT", "repository": "sindresorhus/pretty-ms", "funding": "https://github.com/sponsors/sindresorhus", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, "type": "module", "exports": { "types": "./index.d.ts", "default": "./index.js" }, "sideEffects": false, "engines": { "node": ">=18" }, "scripts": { "test": "xo && ava && tsd" }, "files": [ "index.js", "index.d.ts" ], "keywords": [ "pretty", "prettify", "human", "humanize", "humanized", "readable", "time", "ms", "milliseconds", "duration", "period", "range", "text", "string", "number", "hrtime" ], "dependencies": { "parse-ms": "^4.0.0" }, "devDependencies": { "ava": "^6.2.0", "tsd": "^0.31.2", "xo": "^0.59.3" } } pretty-ms-9.2.0/readme.md000066400000000000000000000072071471561303500152600ustar00rootroot00000000000000# pretty-ms > Convert milliseconds to a human readable string: `1337000000` → `15d 11h 23m 20s` ## Install ```sh npm install pretty-ms ``` ## Usage ```js import prettyMilliseconds from 'pretty-ms'; prettyMilliseconds(1337000000); //=> '15d 11h 23m 20s' prettyMilliseconds(1337000000n); //=> '15d 11h 23m 20s' prettyMilliseconds(1337); //=> '1.3s' prettyMilliseconds(133); //=> '133ms' // `compact` option prettyMilliseconds(1337, {compact: true}); //=> '1s' // `verbose` option prettyMilliseconds(1335669000, {verbose: true}); //=> '15 days 11 hours 1 minute 9 seconds' // `colonNotation` option prettyMilliseconds(95500, {colonNotation: true}); //=> '1:35.5' // `formatSubMilliseconds` option prettyMilliseconds(100.400080, {formatSubMilliseconds: true}) //=> '100ms 400µs 80ns' // Can be useful for time durations prettyMilliseconds(new Date(2014, 0, 1, 10, 40) - new Date(2014, 0, 1, 10, 5)) //=> '35m' ``` ## API ### prettyMilliseconds(milliseconds, options?) #### milliseconds Type: `number | bigint` Milliseconds to humanize. #### options Type: `object` ##### secondsDecimalDigits Type: `number`\ Default: `1` Number of digits to appear after the seconds decimal point. ##### millisecondsDecimalDigits Type: `number`\ Default: `0` Number of digits to appear after the milliseconds decimal point. Useful in combination with [`process.hrtime()`](https://nodejs.org/api/process.html#process_process_hrtime_time). ##### keepDecimalsOnWholeSeconds Type: `boolean`\ Default: `false` Keep milliseconds on whole seconds: `13s` → `13.0s`. Useful when you are showing a number of seconds spent on an operation and don't want the width of the output to change when hitting a whole number. ##### compact Type: `boolean`\ Default: `false` Only show the first unit: `1h 10m` → `1h`. Also ensures that `millisecondsDecimalDigits` and `secondsDecimalDigits` are both set to `0`. ##### unitCount Type: `number`\ Default: `Infinity` Number of units to show. Setting `compact` to `true` overrides this option. ##### verbose Type: `boolean`\ Default: `false` Use full-length units: `5h 1m 45s` → `5 hours 1 minute 45 seconds` ##### separateMilliseconds Type: `boolean`\ Default: `false` Show milliseconds separately. This means they won't be included in the decimal part of the seconds. ##### formatSubMilliseconds Type: `boolean`\ Default: `false` Show microseconds and nanoseconds. ##### colonNotation Type: `boolean`\ Default: `false` Display time using colon notation: `5h 1m 45s` → `5:01:45`. Always shows time in at least minutes: `1s` → `0:01` Useful when you want to display time without the time units, similar to a digital watch. Setting `colonNotation` to `true` overrides the following options to `false`: - `compact` - `formatSubMilliseconds` - `separateMilliseconds` - `verbose` ##### hideYear Type: `boolean`\ Default: `false` Hides the year and shows the hidden year additionally as days (365 per year): `1y 3d 5h 1m 45s` → `368d 5h 1m 45s`. ##### hideYearAndDays Type: `boolean`\ Default: `false` Hides the year and days and shows the hidden values additionally as hours: `1y 3d 5h 1m 45s` → `8837h 1m 45s`. ##### hideSeconds Type: `boolean`\ Default: `false` Hides the seconds: `1y 3d 5h 1m 45s` → `1y 3d 5h 1m`. ## Related - [pretty-ms-cli](https://github.com/sindresorhus/pretty-ms-cli) - CLI for this module - [parse-ms](https://github.com/sindresorhus/parse-ms) - Parse milliseconds into an object - [to-milliseconds](https://github.com/sindresorhus/to-milliseconds) - Convert an object of time properties to milliseconds - [pretty-bytes](https://github.com/sindresorhus/pretty-bytes) - Convert bytes to a human readable string pretty-ms-9.2.0/test.js000066400000000000000000000427111471561303500150150ustar00rootroot00000000000000import test from 'ava'; import prettyMilliseconds from './index.js'; 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'], ], }); 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'], ], }); 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'], ], }); 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'], ], }); runTests({ title: 'have a millisecondsDecimalDigits option', cases: [ [33.333, '33ms'], [33.333, {millisecondsDecimalDigits: 0}, '33ms'], [33.333, {millisecondsDecimalDigits: 4}, '33.3330ms'], ], }); runTests({ title: 'have a keepDecimalsOnWholeSeconds option', defaultOptions: {keepDecimalsOnWholeSeconds: true}, cases: [ [1000 * 33, {secondsDecimalDigits: 2}, '33.00s'], [1000 * 33.000_04, {secondsDecimalDigits: 2}, '33.00s'], ], }); 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'], ], }); runTests({ title: 'have a separateMilliseconds option', cases: [ [1100, {separateMilliseconds: false}, '1.1s'], [1100, {separateMilliseconds: true}, '1s 100ms'], ], }); 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'], ], }); 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'], ], }); 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'], ], }); 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'], ], }); 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'], ], }); 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'], ], }); 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'], ], }); 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 => { t.throws(() => { prettyMilliseconds('foo'); }); t.throws(() => { prettyMilliseconds(Number.NaN); }); t.throws(() => { prettyMilliseconds(Number.POSITIVE_INFINITY); }); }); 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: 'negative milliseconds with options', cases: [ [-0, '0ms'], [-0.1, '-1ms'], [-1, '-1ms'], [-999, '-999ms'], [-1000, '-1s'], [-1000 + 400, '-600ms'], [(-1000 * 2) + 400, '-1.6s'], [-13_370, '-13.3s'], [Number.MIN_SAFE_INTEGER, '-285616y 151d 8h 59m 0.9s'], // With compact option [-1000 * 60 * 60 * 999, {compact: true}, '-41d'], [-1000 * 60 * 60 * 24 * 465, {compact: true}, '-1y'], // With unit-count [-1000 * 60 * 67, {unitCount: 2}, '-1h 7m'], [-1000 * 60 * 67 * 24 * 465, {unitCount: 1}, '-1y'], [-1000 * 60 * 67 * 24 * 465, {unitCount: 2}, '-1y 154d'], // With verbose and secondsDecimalDigits [(-1000 * 5) - 254, {verbose: true, secondsDecimalDigits: 4}, '-5.2540 seconds'], [-33_333, {verbose: true, secondsDecimalDigits: 4}, '-33.3330 seconds'], // With verbose and compact [-1000 * 60 * 5, {verbose: true, compact: true}, '-5 minutes'], [-1000 * 60 * 67, {verbose: true, compact: true}, '-1 hour'], [-1000 * 60 * 60 * 12, {verbose: true, compact: true}, '-12 hours'], // With separateMilliseconds option [-1001, {separateMilliseconds: true}, '-1s 1ms'], [-1234, {separateMilliseconds: true}, '-1s 234ms'], // With formatSubMilliseconds option [-1.234_567, {formatSubMilliseconds: true}, '-1ms 234µs 567ns'], [-1234.567, {formatSubMilliseconds: true}, '-1s 234ms 567µs'], ], }); 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'], ], }); runTests({ title: 'have a hideYear option', cases: [ [1000 * 60, {hideYear: true}, '1m'], [1000 * 60, {hideYear: false}, '1m'], [1000 * 60 * 67, {hideYear: true}, '1h 7m'], [1000 * 60 * 67, {hideYear: false}, '1h 7m'], [1000 * 60 * 67 * 24 * 465, {hideYear: false}, '1y 154d 6h'], [1000 * 60 * 67 * 24 * 465, {hideYear: true}, '519d 6h'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYear: false}, '1y 154d 6h 1m 6.5s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYear: true}, '519d 6h 1m 6.5s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYear: true, secondsDecimalDigits: 0}, '519d 6h 1m 6s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6000, {hideYear: true, keepDecimalsOnWholeSeconds: true}, '519d 6h 1m 6.0s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYear: true, separateMilliseconds: true}, '519d 6h 1m 6s 500ms'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYear: true, verbose: true}, '519 days 6 hours 1 minute 6.5 seconds'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYear: true, compact: true}, '519d'], ], }); runTests({ title: 'have a hideYearAndDays option', cases: [ [1000 * 60, {hideYearAndDays: true}, '1m'], [1000 * 60, {hideYearAndDays: false}, '1m'], [1000 * 60 * 67, {hideYearAndDays: false}, '1h 7m'], [1000 * 60 * 67, {hideYearAndDays: true}, '1h 7m'], [1000 * 60 * 67 * 24 * 465, {hideYearAndDays: false}, '1y 154d 6h'], [1000 * 60 * 67 * 24 * 465, {hideYearAndDays: true}, '12462h'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYearAndDays: false}, '1y 154d 6h 1m 6.5s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYearAndDays: true}, '12462h 1m 6.5s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYearAndDays: true, secondsDecimalDigits: 0}, '12462h 1m 6s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6000, {hideYearAndDays: true, keepDecimalsOnWholeSeconds: true}, '12462h 1m 6.0s'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYearAndDays: true, separateMilliseconds: true}, '12462h 1m 6s 500ms'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYearAndDays: true, verbose: true}, '12462 hours 1 minute 6.5 seconds'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60) + 6500, {hideYearAndDays: true, compact: true}, '12462h'], ], }); runTests({ title: 'have a hideSeconds option', cases: [ [(1000 * 60) + 6500, {hideSeconds: false}, '1m 6.5s'], [(1000 * 60) + 6500, {hideSeconds: true}, '1m'], [(1000 * 60) + 6500, {hideSeconds: true, secondsDecimalDigits: 3}, '1m'], [(1000 * 60) + 6500, {hideSeconds: true, keepDecimalsOnWholeSeconds: true}, '1m'], [(1000 * 60) + 6500, {hideSeconds: true, formatSubMilliseconds: true}, '1m'], [(1000 * 60) + 6500, {hideSeconds: true, separateMilliseconds: true}, '1m'], [(1000 * 60) + 6500, {hideSeconds: true, verbose: true}, '1 minute'], [(1000 * 60) + 6500, {hideSeconds: true, compact: true}, '1m'], ], }); runTests({ title: 'have hideYearAndDays,hideSeconds and colonNotation options', cases: [ [(1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543, {hideSeconds: true, hideYearAndDays: true, colonNotation: true}, '15:59'], [(1000 * 60 * 67 * 24 * 465) + (1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543, {hideSeconds: true, hideYearAndDays: true, colonNotation: true}, '12477:59'], [BigInt(Number.MAX_VALUE), {hideSeconds: true, hideYearAndDays: true, colonNotation: true}, '49935920412842103004035395481028987999464046534956943499699299111988127994452371877941544064657466158761238598198439573398422590802628939657907651862093754718347197382375356132290413913997035817798852363459759428417939788028673041157169044258923152298554951723373534213538382550255361078125112229495590:14'], ], }); 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 => { const runTest = options => { const copy = {...options}; prettyMilliseconds(1, options); t.deepEqual(options, copy); }; runTest({colonNotation: true}); runTest({compact: true}); });