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
Binary file removed .DS_Store
Binary file not shown.
6 changes: 6 additions & 0 deletions .changeset/fresh-maps-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"std": minor
---

feat(ts/time): Use Branded types for format duration and units.

6 changes: 6 additions & 0 deletions .changeset/ninety-queens-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"std": minor
---

feat(ts/types): Add `Brand` utility type

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules
coverage
coverage

.DS_Store
28 changes: 18 additions & 10 deletions src/ts/time.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { describe, expect, it } from 'vitest';
import { DAY, HOUR, MINUTE, SECOND, formatDuration } from './time';
import { type Milliseconds, formatDuration, milliseconds } from './time';

describe('formatDuration', () => {
it('correctly formats durations', () => {
expect(formatDuration(500)).toBe('500ms');
expect(formatDuration(SECOND)).toBe('1s');
expect(formatDuration(MINUTE)).toBe('1min');
expect(formatDuration(HOUR)).toBe('1h');
expect(formatDuration(DAY)).toBe('24h');
expect(formatDuration(500 as Milliseconds)).toBe('500ms');
expect(formatDuration(milliseconds.SECOND)).toBe('1s');
expect(formatDuration(milliseconds.MINUTE)).toBe('1min');
expect(formatDuration(milliseconds.HOUR)).toBe('1h');
expect(formatDuration(milliseconds.DAY)).toBe('24h');
});

it('correctly handles transform function', () => {
expect(formatDuration(SECOND / 55, (num) => num.toFixed(2))).toBe('18.18ms');
expect(formatDuration(MINUTE / 55, (num) => num.toFixed(2))).toBe('1.09s');
expect(formatDuration(HOUR / 55, (num) => num.toFixed(2))).toBe('1.09min');
expect(formatDuration(DAY / 55, (num) => num.toFixed(2))).toBe('26.18min');
expect(
formatDuration((milliseconds.SECOND / 55) as Milliseconds, (num) => num.toFixed(2))
).toBe('18.18ms');
expect(
formatDuration((milliseconds.MINUTE / 55) as Milliseconds, (num) => num.toFixed(2))
).toBe('1.09s');
expect(
formatDuration((milliseconds.HOUR / 55) as Milliseconds, (num) => num.toFixed(2))
).toBe('1.09min');
expect(
formatDuration((milliseconds.DAY / 55) as Milliseconds, (num) => num.toFixed(2))
).toBe('26.18min');
});
});
49 changes: 35 additions & 14 deletions src/ts/time.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
/** Milliseconds in a second */
export const SECOND = 1000;
/** Milliseconds in a minute */
export const MINUTE = SECOND * 60;
/** Milliseconds in an hour */
export const HOUR = MINUTE * 60;
/** Milliseconds in a day */
export const DAY = HOUR * 24;
import type { Brand } from './types';

export type Milliseconds = Brand<number, 'milliseconds'>;

export const milliseconds = {
/** Milliseconds in a second */
SECOND: 1000 as Milliseconds,
/** Milliseconds in a minute */
MINUTE: (1000 * 60) as Milliseconds,
/** Milliseconds in a hour */
HOUR: (1000 * 60 * 60) as Milliseconds,
/** Milliseconds in a day */
DAY: (1000 * 60 * 60 * 24) as Milliseconds,
/** Milliseconds in a year */
YEAR: (1000 * 60 * 60 * 24 * 365) as Milliseconds,
} as const;

export type Seconds = Brand<number, 'seconds'>;

export const seconds = {
/** Seconds in a minute */
MINUTE: 60 as Seconds,
/** Seconds in a hour */
HOUR: (60 * 60) as Seconds,
/** Seconds in a day */
DAY: (60 * 60 * 24) as Seconds,
/** Seconds in a year */
YEAR: (60 * 60 * 24 * 365) as Seconds,
} as const;

/** Formats a time given in milliseconds with units.
*
* @param durationMs Time to be formatted in milliseconds
* @param duration Time to be formatted in milliseconds
* @param transform Runs before the num is formatted perfect place to put a `.toFixed()`
* @returns
*
Expand All @@ -22,14 +43,14 @@ export const DAY = HOUR * 24;
* ```
*/
export function formatDuration(
durationMs: number,
duration: Milliseconds,
transform: (num: number) => string = (num) => num.toString()
): string {
if (durationMs < SECOND) return `${transform(durationMs)}ms`;
if (duration < milliseconds.SECOND) return `${transform(duration)}ms`;

if (durationMs < MINUTE) return `${transform(durationMs / SECOND)}s`;
if (duration < milliseconds.MINUTE) return `${transform(duration / milliseconds.SECOND)}s`;

if (durationMs < HOUR) return `${transform(durationMs / MINUTE)}min`;
if (duration < milliseconds.HOUR) return `${transform(duration / milliseconds.MINUTE)}min`;

return `${durationMs / HOUR}h`;
return `${duration / milliseconds.HOUR}h`;
}
11 changes: 11 additions & 0 deletions src/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ export type LooseAutocomplete<T> = T | (string & {});
export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};

declare const brand: unique symbol;

/** Allows you to create a branded type.
*
* ## Usage
* ```ts
* type Milliseconds = Brand<number, 'milliseconds'>;
* ```
*/
export type Brand<T, Brand extends string> = T & { [brand]: Brand };
Loading