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

feat: add parse() and format() exports #213

Merged
merged 3 commits into from Mar 19, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
174 changes: 174 additions & 0 deletions src/format.test.ts
@@ -0,0 +1,174 @@
import { format } from './index';

// numbers

describe('format(number, { long: true })', () => {
it('should not throw an error', () => {
expect(() => {
format(500, { long: true });
}).not.toThrowError();
});

it('should support milliseconds', () => {
expect(format(500, { long: true })).toBe('500 ms');

expect(format(-500, { long: true })).toBe('-500 ms');
});

it('should support seconds', () => {
expect(format(1000, { long: true })).toBe('1 second');
expect(format(1200, { long: true })).toBe('1 second');
expect(format(10000, { long: true })).toBe('10 seconds');

expect(format(-1000, { long: true })).toBe('-1 second');
expect(format(-1200, { long: true })).toBe('-1 second');
expect(format(-10000, { long: true })).toBe('-10 seconds');
});

it('should support minutes', () => {
expect(format(60 * 1000, { long: true })).toBe('1 minute');
expect(format(60 * 1200, { long: true })).toBe('1 minute');
expect(format(60 * 10000, { long: true })).toBe('10 minutes');

expect(format(-1 * 60 * 1000, { long: true })).toBe('-1 minute');
expect(format(-1 * 60 * 1200, { long: true })).toBe('-1 minute');
expect(format(-1 * 60 * 10000, { long: true })).toBe('-10 minutes');
});

it('should support hours', () => {
expect(format(60 * 60 * 1000, { long: true })).toBe('1 hour');
expect(format(60 * 60 * 1200, { long: true })).toBe('1 hour');
expect(format(60 * 60 * 10000, { long: true })).toBe('10 hours');

expect(format(-1 * 60 * 60 * 1000, { long: true })).toBe('-1 hour');
expect(format(-1 * 60 * 60 * 1200, { long: true })).toBe('-1 hour');
expect(format(-1 * 60 * 60 * 10000, { long: true })).toBe('-10 hours');
});

it('should support days', () => {
expect(format(24 * 60 * 60 * 1000, { long: true })).toBe('1 day');
expect(format(24 * 60 * 60 * 1200, { long: true })).toBe('1 day');
expect(format(24 * 60 * 60 * 10000, { long: true })).toBe('10 days');

expect(format(-1 * 24 * 60 * 60 * 1000, { long: true })).toBe('-1 day');
expect(format(-1 * 24 * 60 * 60 * 1200, { long: true })).toBe('-1 day');
expect(format(-1 * 24 * 60 * 60 * 10000, { long: true })).toBe('-10 days');
});

it('should round', () => {
expect(format(234234234, { long: true })).toBe('3 days');

expect(format(-234234234, { long: true })).toBe('-3 days');
});
});

// numbers

describe('format(number)', () => {
it('should not throw an error', () => {
expect(() => {
format(500);
}).not.toThrowError();
});

it('should support milliseconds', () => {
expect(format(500)).toBe('500ms');

expect(format(-500)).toBe('-500ms');
});

it('should support seconds', () => {
expect(format(1000)).toBe('1s');
expect(format(10000)).toBe('10s');

expect(format(-1000)).toBe('-1s');
expect(format(-10000)).toBe('-10s');
});

it('should support minutes', () => {
expect(format(60 * 1000)).toBe('1m');
expect(format(60 * 10000)).toBe('10m');

expect(format(-1 * 60 * 1000)).toBe('-1m');
expect(format(-1 * 60 * 10000)).toBe('-10m');
});

it('should support hours', () => {
expect(format(60 * 60 * 1000)).toBe('1h');
expect(format(60 * 60 * 10000)).toBe('10h');

expect(format(-1 * 60 * 60 * 1000)).toBe('-1h');
expect(format(-1 * 60 * 60 * 10000)).toBe('-10h');
});

it('should support days', () => {
expect(format(24 * 60 * 60 * 1000)).toBe('1d');
expect(format(24 * 60 * 60 * 10000)).toBe('10d');

expect(format(-1 * 24 * 60 * 60 * 1000)).toBe('-1d');
expect(format(-1 * 24 * 60 * 60 * 10000)).toBe('-10d');
});

it('should round', () => {
expect(format(234234234)).toBe('3d');

expect(format(-234234234)).toBe('-3d');
});
});

// invalid inputs

describe('format(invalid inputs)', () => {
it('should throw an error, when format("")', () => {
expect(() => {
// @ts-expect-error - We expect this to throw.
format('');
}).toThrowError();
});

it('should throw an error, when format(undefined)', () => {
expect(() => {
// @ts-expect-error - We expect this to throw.
format(undefined);
}).toThrowError();
});

it('should throw an error, when format(null)', () => {
expect(() => {
// @ts-expect-error - We expect this to throw.
format(null);
}).toThrowError();
});

it('should throw an error, when format([])', () => {
expect(() => {
// @ts-expect-error - We expect this to throw.
format([]);
}).toThrowError();
});

it('should throw an error, when format({})', () => {
expect(() => {
// @ts-expect-error - We expect this to throw.
format({});
}).toThrowError();
});

it('should throw an error, when format(NaN)', () => {
expect(() => {
format(NaN);
}).toThrowError();
});

it('should throw an error, when format(Infinity)', () => {
expect(() => {
format(Infinity);
}).toThrowError();
});

it('should throw an error, when format(-Infinity)', () => {
expect(() => {
format(-Infinity);
}).toThrowError();
});
});
41 changes: 34 additions & 7 deletions src/index.ts
Expand Up @@ -64,12 +64,12 @@ function msFn(value: StringValue, options?: Options): number;
function msFn(value: number, options?: Options): string;
function msFn(value: StringValue | number, options?: Options): number | string {
try {
if (typeof value === 'string' && value.length > 0) {
if (typeof value === 'string') {
return parse(value);
} else if (typeof value === 'number' && isFinite(value)) {
return options?.long ? fmtLong(value) : fmtShort(value);
} else if (typeof value === 'number') {
return format(value, options);
}
throw new Error('Value is not a string or number.');
throw new Error('Value provided to ms() must be a string or number.');
} catch (error) {
const message = isError(error)
? `${error.message}. value=${JSON.stringify(value)}`
Expand All @@ -85,9 +85,11 @@ function msFn(value: StringValue | number, options?: Options): number | string {
* @returns The parsed value in milliseconds, or `NaN` if the string can't be
* parsed
*/
function parse(str: string): number {
if (str.length > 100) {
throw new Error('Value exceeds the maximum length of 100 characters.');
export function parse(str: string): number {
styfle marked this conversation as resolved.
Show resolved Hide resolved
if (typeof str !== 'string' || str.length === 0 || str.length > 100) {
throw new Error(
'Value provided to ms.parse() must be a string with length between 1 and 99.',
);
}
const match =
/^(?<value>-?(?:\d+)?\.?\d+) *(?<type>milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
Expand Down Expand Up @@ -148,6 +150,17 @@ function parse(str: string): number {
}
}

/**
* Parse the given StringValue and return milliseconds.
*
* @param value - A typesafe StringValue to parse to milliseconds
* @returns The parsed value in milliseconds, or `NaN` if the string can't be
* parsed
*/
export function parseStrict(value: StringValue): number {
return parse(value);
}

// eslint-disable-next-line import/no-default-export
export default msFn;

Expand Down Expand Up @@ -191,6 +204,20 @@ function fmtLong(ms: number): StringValue {
return `${ms} ms`;
}

/**
* Format the given integer as a string.
*
* @param ms - milliseconds
* @param options - Options for the conversion
* @returns The formatted string
*/
export function format(ms: number, options?: Options): string {
if (typeof ms !== 'number' || !isFinite(ms)) {
throw new Error('Value provided to ms.format() must be of type number.');
}
return options?.long ? fmtLong(ms) : fmtShort(ms);
}

/**
* Pluralization helper.
*/
Expand Down