Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blue-snails-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bengsfort/stdlib': minor
---

Add new formatting modules including formatDuration() and restrictDecimals() functions.
40 changes: 40 additions & 0 deletions lib/formatting/__tests__/numbers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, it, expect } from 'vitest';

import { restrictDecimals, formatDuration } from '../numbers.js';

describe('formatting/numbers', () => {
describe('restrictDecimals()', () => {
it('should limit large floats to a specified number of places', () => {
// We are intentionally trying to test that it restricts the float properly,
// therefore the float warning rule is ignorable.

const randomFloat = 1.5233219472835921359;
expect(restrictDecimals(randomFloat, 2)).toEqual(1.52);
expect(restrictDecimals(randomFloat, 1)).toEqual(1.5);
expect(restrictDecimals(randomFloat, 0)).toEqual(1);
});

it('should remove trailing zeroes', () => {
const randomFloat = 3.040203;
expect(restrictDecimals(randomFloat, 4)).toEqual(3.0402);
expect(restrictDecimals(randomFloat, 3)).toEqual(3.04);
expect(restrictDecimals(randomFloat, 1)).toEqual(3);
});
});

describe('formatDuration()', () => {
it('should display the value in the most appropriate timescale', () => {
const microSecs = 0.095;
expect(formatDuration(microSecs)).toEqual('95μs');

const millisecs = 50;
expect(formatDuration(millisecs)).toEqual('50ms');

const seconds = 3000;
expect(formatDuration(seconds)).toEqual('3s');

const minutes = 65000;
expect(formatDuration(minutes)).toEqual('1m 5s');
});
});
});
58 changes: 58 additions & 0 deletions lib/formatting/numbers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Restricts the given value to a certain number of decimal places, but does NOT
* force that number of places like `.toFixed()` does. Trailing 0's will be removed.
*/
export function restrictDecimals(value: number, maxDecimalPlaces: number): number {
if (maxDecimalPlaces < 0) return value;
// When 0, dont use .toFixed since it rounds
if (maxDecimalPlaces === 0) {
return Math.floor(value);
}

// Force to fixed -> Convert back to number to remove trailing 0's.
return Number(value.toFixed(maxDecimalPlaces));
}

/**
* Takes a given duration in milliseconds and formats it into a more readable
* string value suitable for logging or display.
*
* Determines what timescale to use in an opinionated fashion:
*
* - Below 0.1ms switches to microseconds for more readability.
* - Between 0.1ms - 999ms displays milliseconds.
* - Between 1s - 59s displays seconds.
* - Over 1m will display Xminutes Yseconds.
*
* The timescale choices try to enforce readability patterns that make it easier
* to visually understand how much time something takes. Large values that could
* be dislayed as a smaller value at a larger timescale are preferred, as they
* require less mental math to understand.
*/
export function formatDuration(durationMs: number): string {
// Show microseconds when below 0.1ms
if (0.1 > durationMs) {
return `${restrictDecimals(durationMs * 1000, 0).toString(10)}μs`;
}

// Show milliseconds when below 1s
if (1000 > durationMs) {
return `${restrictDecimals(durationMs, 2).toString(10)}ms`;
}

const totalSeconds = restrictDecimals(durationMs / 1000, 2);
// Handle just seconds
if (60 > totalSeconds) {
return `${totalSeconds.toString(10)}s`;
}

// Handle seconds/minutes handling
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;

const formatted: string[] = [];
if (1 >= minutes) formatted.push(`${minutes.toString(10)}m`);
formatted.push(`${seconds.toString(10)}s`);

return formatted.join(' ');
}
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
"./logging/node/*": {
"default": "./dist/logging/node/*.js",
"types": "./dist/logging/node/*.d.ts"
},
"./formatting/*": {
"default": "./dist/formatting/*.js",
"types": "./dist/formatting/*.d.ts"
}
},
"files": [
Expand Down Expand Up @@ -61,4 +65,4 @@
"typescript": "^5.7.3",
"vitest": "^3.0.6"
}
}
}