diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index efe15e4..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.changeset/fresh-maps-rush.md b/.changeset/fresh-maps-rush.md new file mode 100644 index 0000000..7eda966 --- /dev/null +++ b/.changeset/fresh-maps-rush.md @@ -0,0 +1,6 @@ +--- +"std": minor +--- + +feat(ts/time): Use Branded types for format duration and units. + \ No newline at end of file diff --git a/.changeset/ninety-queens-hide.md b/.changeset/ninety-queens-hide.md new file mode 100644 index 0000000..f255a81 --- /dev/null +++ b/.changeset/ninety-queens-hide.md @@ -0,0 +1,6 @@ +--- +"std": minor +--- + +feat(ts/types): Add `Brand` utility type + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3091757..b1fa891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -coverage \ No newline at end of file +coverage + +.DS_Store diff --git a/src/ts/time.test.ts b/src/ts/time.test.ts index 9250e93..bd378dd 100644 --- a/src/ts/time.test.ts +++ b/src/ts/time.test.ts @@ -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'); }); }); diff --git a/src/ts/time.ts b/src/ts/time.ts index 6eef28c..aaaf2be 100644 --- a/src/ts/time.ts +++ b/src/ts/time.ts @@ -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; + +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; + +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 * @@ -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`; } diff --git a/src/ts/types.ts b/src/ts/types.ts index bfbf908..06fcc84 100644 --- a/src/ts/types.ts +++ b/src/ts/types.ts @@ -17,3 +17,14 @@ export type LooseAutocomplete = T | (string & {}); export type Prettify = { [K in keyof T]: T[K]; } & {}; + +declare const brand: unique symbol; + +/** Allows you to create a branded type. + * + * ## Usage + * ```ts + * type Milliseconds = Brand; + * ``` + */ +export type Brand = T & { [brand]: Brand };