Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add colonNotation option #37

Merged
merged 12 commits into from
Nov 22, 2019
19 changes: 19 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ declare namespace prettyMilliseconds {
@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`

sindresorhus marked this conversation as resolved.
Show resolved Hide resolved
@default false
*/
readonly colonNotation?: boolean;
}
}

Expand Down Expand Up @@ -90,6 +105,10 @@ prettyMilliseconds(1337, {compact: true});
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'
Expand Down
27 changes: 23 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ module.exports = (milliseconds, options = {}) => {
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;
Expand All @@ -16,13 +23,25 @@ module.exports = (milliseconds, options = {}) => {
const result = [];

const add = (value, long, short, valueString) => {
if (value === 0) {
if ((result.length === 0 || !options.colonNotation) && value === 0 && !(options.colonNotation && short === 'm')) {
return;
}

const postfix = options.verbose ? ' ' + pluralize(long, value) : short;
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((valueString || value) + postfix);
result.push(prefix + valueString + suffix);
};

const secondsDecimalDigits =
Expand Down Expand Up @@ -101,5 +120,5 @@ module.exports = (milliseconds, options = {}) => {
return '~' + result.slice(0, Math.max(options.unitCount, 1)).join(' ');
}

return result.join(' ');
return options.colonNotation ? result.join('') : result.join(' ');
};
3 changes: 3 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ expectType<string>(
expectType<string>(
prettyMilliseconds(1335669000, {formatSubMilliseconds: true})
);
expectType<string>(
prettyMilliseconds(1335669000, {colonNotation: true})
);
18 changes: 18 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ prettyMilliseconds(1337, {compact: true});
prettyMilliseconds(1335669000, {verbose: true});
//=> '15 days 11 hours 1 minute 9 seconds'

// `colonNotation` option
prettyMilliseconds(95500, {colonNotation: true});
//=> '1:35.5'
sindresorhus marked this conversation as resolved.
Show resolved Hide resolved

// `formatSubMilliseconds` option
prettyMilliseconds(100.400080, {formatSubMilliseconds: true})
//=> '100ms 400µs 80ns'
Expand Down Expand Up @@ -118,6 +122,20 @@ Default: `false`

Show microseconds and nanoseconds.

##### colonNotation

Type: `boolean`<br>
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`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to keep index.d.ts in sync.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed some of the others were slightly out of sync (more comprehensive in readme), ok if I sync those at the same time?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


## Related

Expand Down
39 changes: 39 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,42 @@ test('properly rounds milliseconds with secondsDecimalDigits', t => {
t.is(fn((3600 * 1e3) - 1), '1 hour');
t.is(fn((2 * 3600 * 1e3) - 1), '2 hours');
});

test('`colonNotation` option', t => {
// Default formats
t.is(prettyMilliseconds(1000, {colonNotation: true}), '0:01');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is minute zero prefixed, but hour is not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried googling for an official standard outlining how this format should behave, but was unfortunately unable to find one. However, the attached screenshot from the Kona Ironman World Championships this year shows what the commonly accepted convention is:
image
Here we can see, that Jan Frodeno's total time 2h 24m 45s was 1s behind whoever was leading at that time, which in this notation would be expressed as 2:24:45 and 0:01 respectively. This example doesn't show fractional seconds, but for seconds regular decimal formatting applies.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your assessment.

t.is(prettyMilliseconds(1543, {colonNotation: true}), '0:01.5');
t.is(prettyMilliseconds(1000 * 60, {colonNotation: true}), '1:00');
t.is(prettyMilliseconds(1000 * 90, {colonNotation: true}), '1:30');
t.is(prettyMilliseconds(95543, {colonNotation: true}), '1:35.5');
t.is(prettyMilliseconds((1000 * 60 * 10) + 543, {colonNotation: true}), '10:00.5');
t.is(prettyMilliseconds((1000 * 60 * 59) + (1000 * 59) + 543, {colonNotation: true}), '59:59.5');
t.is(prettyMilliseconds((1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543, {colonNotation: true}), '15:59:59.5');

// Together with `secondsDecimalDigits`
t.is(prettyMilliseconds(1543, {colonNotation: true, secondsDecimalDigits: 0}), '0:02');
t.is(prettyMilliseconds(1543, {colonNotation: true, secondsDecimalDigits: 1}), '0:01.5');
t.is(prettyMilliseconds(1543, {colonNotation: true, secondsDecimalDigits: 2}), '0:01.54');
t.is(prettyMilliseconds(1543, {colonNotation: true, secondsDecimalDigits: 3}), '0:01.543');
t.is(prettyMilliseconds(95543, {colonNotation: true, secondsDecimalDigits: 0}), '1:36');
t.is(prettyMilliseconds(95543, {colonNotation: true, secondsDecimalDigits: 1}), '1:35.5');
t.is(prettyMilliseconds(95543, {colonNotation: true, secondsDecimalDigits: 2}), '1:35.54');
t.is(prettyMilliseconds(95543, {colonNotation: true, secondsDecimalDigits: 3}), '1:35.543');
t.is(prettyMilliseconds((1000 * 60 * 10) + 543, {colonNotation: true, secondsDecimalDigits: 3}), '10:00.543');
t.is(prettyMilliseconds((1000 * 60 * 60 * 15) + (1000 * 60 * 59) + (1000 * 59) + 543, {colonNotation: true, secondsDecimalDigits: 3}), '15:59:59.543');

// Together with `keepDecimalsOnWholeSeconds`
t.is(prettyMilliseconds(1000, {colonNotation: true, keepDecimalsOnWholeSeconds: true}), '0:01.0');
t.is(prettyMilliseconds(1000, {colonNotation: true, secondsDecimalDigits: 0, keepDecimalsOnWholeSeconds: true}), '0:01');
t.is(prettyMilliseconds(1000, {colonNotation: true, secondsDecimalDigits: 1, keepDecimalsOnWholeSeconds: true}), '0:01.0');
t.is(prettyMilliseconds(1000, {colonNotation: true, secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}), '0:01.000');
t.is(prettyMilliseconds(1000 * 90, {colonNotation: true, keepDecimalsOnWholeSeconds: true}), '1:30.0');
t.is(prettyMilliseconds(1000 * 90, {colonNotation: true, secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}), '1:30.000');
t.is(prettyMilliseconds(1000 * 60 * 10, {colonNotation: true, secondsDecimalDigits: 3, keepDecimalsOnWholeSeconds: true}), '10:00.000');

// Make sure incompatible options fall back to `colonNotation`
t.is(prettyMilliseconds((1000 * 60 * 59) + (1000 * 59) + 543, {colonNotation: true, formatSubMilliseconds: true}), '59:59.5');
t.is(prettyMilliseconds((1000 * 60 * 59) + (1000 * 59) + 543, {colonNotation: true, separateMilliseconds: true}), '59:59.5');
t.is(prettyMilliseconds((1000 * 60 * 59) + (1000 * 59) + 543, {colonNotation: true, verbose: true}), '59:59.5');
t.is(prettyMilliseconds((1000 * 60 * 59) + (1000 * 59) + 543, {colonNotation: true, compact: true}), '59:59.5');
});