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
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ declare namespace prettyMilliseconds {
@default false
*/
readonly formatSubMilliseconds?: boolean;

/**
Display time using colon notation: `5h 1m 45s`→ `5:01:45`.

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

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})
);
19 changes: 18 additions & 1 deletion 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 @@ -90,6 +94,8 @@ Only show the first unit: `1h 10m` → `~1h`.

Also ensures that `millisecondsDecimalDigits` and `secondsDecimalDigits` are both set to `0`.

Setting `colonNotation` to `true` overrides this option.

##### unitCount

Type: `number`\
Expand All @@ -104,19 +110,30 @@ Default: `false`

Use full-length units: `5h 1m 45s` → `5 hours 1 minute 45 seconds`

Setting `colonNotation` to `true` overrides this option.
Copy link
Owner

Choose a reason for hiding this comment

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


##### separateMilliseconds

Type: `boolean`\
Default: `false`

Show milliseconds separately. This means they won't be included in the decimal part of the seconds.

Setting `colonNotation` to `true` overrides this option.
Copy link
Owner

Choose a reason for hiding this comment

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

Instead of writing this for each option, I think it would be better to just document in the colonNotation docs what options it overrides.


##### formatSubMilliseconds

Type: `boolean`\
Default: `false`

Show microseconds and nanoseconds.
Show microseconds and nanoseconds. Setting `colonNotation` to `true` overrides this option.

##### colonNotation

Type: `boolean`<br>
Default: `false`

Use colon notation: `5h 1m 45s` → `5:01:45`
Copy link
Owner

Choose a reason for hiding this comment

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

The description should be the same as in index.d.ts



## Related
Expand Down
37 changes: 37 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,40 @@ 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('work with colon output 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');
// 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');
// 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');
});