mrkeyoor.com_
Thu 06 Aug 02:44 UTC
npmUtilsupdated 06 Aug 2026

pretty-ms

One function that turns a millisecond count into a string a person can read: 1337000000 becomes '15d 11h 23m 20s', 1337 becomes '1.3s', 133 becomes '133ms'. It takes a number or a bigint and a small options object that controls how many units to show, whether to spell them out ('15 days 11 hours'), whether to use colon notation like a stopwatch ('1:35.5'), and how many decimals to keep. It is the thing you reach for when a CLI needs to print how long a build took, or a dashboard needs to show job runtime, and you do not want to hand-roll the divide-and-pad arithmetic again. The whole package is about 1 KB gzipped with one dependency, parse-ms, from the same author.

Verdict

The correct answer for English duration strings in a Node CLI or build tool: tiny, well-specified, and it has thought about the edge cases you have not. Skip it if you need translated output or still ship CommonJS.

API stability5/5One default export whose signature has not changed in years; v8 broke only by going ESM only, and later releases have added options like subSecondsAsDecimals without touching existing behavior
Docs4/5The README documents every option with its default and a sample output, plus an FAQ explaining why months and years are excluded and why rounded differences do not line up; there is no separate docs site, but the surface is one function
Maintenance5/5Zero open issues and zero open PRs at the time of writing, pushed July 2026, 9.3.0 released September 2025; Sindre Sorhus has kept it and its parse-ms dependency current for a decade
Ecosystem4/536.6M weekly downloads, mostly as a transitive dependency of build tools and CLIs, with a companion CLI (pretty-ms-cli) and inverse parser (parse-duration-ms); only 1.2k stars because nobody stars a formatter

Use it if

  • You print elapsed times in a CLI or log line and want consistent output like '2m 30s' without writing modulo arithmetic in every project
  • You need a stopwatch or media-player style display: colonNotation gives you '1:35.5' with correct zero padding
  • You measure with process.hrtime.bigint() and want sub-millisecond output: it accepts bigint input and can format microseconds and nanoseconds
  • You want a progress indicator whose width does not jitter: keepDecimalsOnWholeSeconds and subSecondsAsDecimals exist specifically for that
Skip it if

Setup reality

npm install pretty-ms, import the default export, call it. The only real friction is module format and Node version: package.json declares type module with an exports map and engines node >=18, so CommonJS consumers get ERR_REQUIRE_ESM and have to await import('pretty-ms') or transpile. Jest setups without ESM support choke on it for the same reason. Types are bundled, there are no peer dependencies and no native builds, and the one runtime dependency is parse-ms. Passing NaN or Infinity throws a TypeError rather than returning a placeholder, so validate before formatting values that come from timers that never fired.

Patterns

Format a duration in millisecondsbasic-format

import prettyMilliseconds from 'pretty-ms';

prettyMilliseconds(1337000000); //=> '15d 11h 23m 20s'
prettyMilliseconds(1337);       //=> '1.3s'
prettyMilliseconds(133);        //=> '133ms'
prettyMilliseconds(0);          //=> '0ms'

Sub-second values print as milliseconds by default; seconds carry one decimal place unless you change secondsDecimalDigits.

Print how long an operation tooktime-an-operation

const start = Date.now();
await buildProject();
console.log(`Built in ${prettyMilliseconds(Date.now() - start)}`);

// or between two dates
prettyMilliseconds(new Date(2014, 0, 1, 10, 40) - new Date(2014, 0, 1, 10, 5)); //=> '35m'

Date subtraction yields a plain number of milliseconds, which is exactly the input shape this expects.

Show only the largest unitcompact-single-unit

prettyMilliseconds(1337, {compact: true});       //=> '1s'
prettyMilliseconds(5400000, {compact: true});    //=> '1h'

// two units instead of one:
prettyMilliseconds(5400000, {unitCount: 2});     //=> '1h 30m'

compact is unitCount 1 plus zeroed decimals, and it overrides unitCount if you pass both.

Spell out the unit namesverbose-units

prettyMilliseconds(1335669000, {verbose: true});
//=> '15 days 11 hours 1 minute 9 seconds'

Pluralization is handled per unit, but the words are English only; there is no locale option.

Render stopwatch style with colonscolon-notation

prettyMilliseconds(95500, {colonNotation: true});   //=> '1:35.5'
prettyMilliseconds(1000, {colonNotation: true});    //=> '0:01'
prettyMilliseconds(18300000, {colonNotation: true}); //=> '5:05:00'

colonNotation forces compact, verbose, separateMilliseconds, and formatSubMilliseconds off, and always shows at least minutes.

Control decimal places on secondsdecimal-control

prettyMilliseconds(1337, {secondsDecimalDigits: 0}); //=> '1s'
prettyMilliseconds(13000, {keepDecimalsOnWholeSeconds: true}); //=> '13.0s'
prettyMilliseconds(900, {subSecondsAsDecimals: true}); //=> '0.9s'

keepDecimalsOnWholeSeconds and subSecondsAsDecimals both exist to stop the string width from jumping in a live-updating progress line.

Format nanosecond timings from hrtimesub-millisecond-precision

const start = process.hrtime.bigint();
doWork();
const ns = process.hrtime.bigint() - start;

prettyMilliseconds(Number(ns) / 1e6, {formatSubMilliseconds: true});
//=> e.g. '100ms 400\u00b5s 80ns'

formatSubMilliseconds splits microseconds and nanoseconds into their own units instead of folding them into a decimal.

Pass a bigint for very large or exact valuesbigint-input

prettyMilliseconds(1337000000n); //=> '15d 11h 23m 20s'

bigint input avoids float rounding on huge spans; mixing bigint and number arithmetic before the call is what usually breaks, not the call itself.

Suppress years, days, or secondshide-units

const ms = 31_795_305_000; // about 1y 3d 5h 1m 45s

prettyMilliseconds(ms, {hideYear: true});        //=> '368d 5h 1m 45s'
prettyMilliseconds(ms, {hideYearAndDays: true}); //=> '8837h 1m 45s'
prettyMilliseconds(ms, {hideSeconds: true});     //=> '1y 3d 5h 1m'

The year unit is computed as days divided by 365, so it is a rough bucket, not a calendar year. Use hideYear when that approximation would mislead.

Handle negative and non-finite inputnegative-and-invalid

prettyMilliseconds(-1500); //=> '-1.5s'

try {
  prettyMilliseconds(Number.NaN);
} catch (error) {
  // TypeError: Expected a finite number or bigint
}

Negative durations get a leading minus sign. NaN and Infinity throw, so guard timers that may never have started.

Use it from CommonJScommonjs-import

// index.cjs
async function main() {
  const {default: prettyMilliseconds} = await import('pretty-ms');
  console.log(prettyMilliseconds(4242));
}

main();

The package is ESM only from v8 onward, so require() throws ERR_REQUIRE_ESM. Dynamic import works from CJS on Node 18+; older code can pin pretty-ms@7.

Format a column of durations consistentlytable-of-durations

const jobs = [{name: 'lint', ms: 812}, {name: 'test', ms: 94_300}, {name: 'build', ms: 3_600_000}];

for (const job of jobs) {
  const time = prettyMilliseconds(job.ms, {unitCount: 2, secondsDecimalDigits: 0});
  console.log(job.name.padEnd(8), time.padStart(9));
}

Fixing unitCount and dropping decimals keeps column widths stable; the library does not pad output for you.

Alternatives

PackageRegistryPick it when
humanize-durationnpmYou need the same output translated into other languages, with configurable unit lists
msnpmYou want the tiny two-way helper that both parses '2 days' and prints short forms, at the cost of only one unit at a time
date-fnsnpmYou already use date-fns and want formatDuration plus locale support from the same package
luxonnpmYour durations come from real calendar math and you want Duration objects rather than a formatting function