Skip to content

Commit f150c1c

Browse files
authored
feat: add parse() and format() exports (#213)
This PR exports `parse()` and `format()` functions which makes it much easier to work with arbitrary input from users. For example, `onChange(e => ms.parse(e.target.value))` no longer needs to type cast since `parse()` accepts a string. I also added `parseStrict()` for the case when you want the type safety and you're using a literal.
1 parent dfd1803 commit f150c1c

File tree

4 files changed

+598
-7
lines changed

4 files changed

+598
-7
lines changed

src/format.test.ts

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { format } from './index';
2+
3+
// numbers
4+
5+
describe('format(number, { long: true })', () => {
6+
it('should not throw an error', () => {
7+
expect(() => {
8+
format(500, { long: true });
9+
}).not.toThrowError();
10+
});
11+
12+
it('should support milliseconds', () => {
13+
expect(format(500, { long: true })).toBe('500 ms');
14+
15+
expect(format(-500, { long: true })).toBe('-500 ms');
16+
});
17+
18+
it('should support seconds', () => {
19+
expect(format(1000, { long: true })).toBe('1 second');
20+
expect(format(1200, { long: true })).toBe('1 second');
21+
expect(format(10000, { long: true })).toBe('10 seconds');
22+
23+
expect(format(-1000, { long: true })).toBe('-1 second');
24+
expect(format(-1200, { long: true })).toBe('-1 second');
25+
expect(format(-10000, { long: true })).toBe('-10 seconds');
26+
});
27+
28+
it('should support minutes', () => {
29+
expect(format(60 * 1000, { long: true })).toBe('1 minute');
30+
expect(format(60 * 1200, { long: true })).toBe('1 minute');
31+
expect(format(60 * 10000, { long: true })).toBe('10 minutes');
32+
33+
expect(format(-1 * 60 * 1000, { long: true })).toBe('-1 minute');
34+
expect(format(-1 * 60 * 1200, { long: true })).toBe('-1 minute');
35+
expect(format(-1 * 60 * 10000, { long: true })).toBe('-10 minutes');
36+
});
37+
38+
it('should support hours', () => {
39+
expect(format(60 * 60 * 1000, { long: true })).toBe('1 hour');
40+
expect(format(60 * 60 * 1200, { long: true })).toBe('1 hour');
41+
expect(format(60 * 60 * 10000, { long: true })).toBe('10 hours');
42+
43+
expect(format(-1 * 60 * 60 * 1000, { long: true })).toBe('-1 hour');
44+
expect(format(-1 * 60 * 60 * 1200, { long: true })).toBe('-1 hour');
45+
expect(format(-1 * 60 * 60 * 10000, { long: true })).toBe('-10 hours');
46+
});
47+
48+
it('should support days', () => {
49+
expect(format(24 * 60 * 60 * 1000, { long: true })).toBe('1 day');
50+
expect(format(24 * 60 * 60 * 1200, { long: true })).toBe('1 day');
51+
expect(format(24 * 60 * 60 * 10000, { long: true })).toBe('10 days');
52+
53+
expect(format(-1 * 24 * 60 * 60 * 1000, { long: true })).toBe('-1 day');
54+
expect(format(-1 * 24 * 60 * 60 * 1200, { long: true })).toBe('-1 day');
55+
expect(format(-1 * 24 * 60 * 60 * 10000, { long: true })).toBe('-10 days');
56+
});
57+
58+
it('should round', () => {
59+
expect(format(234234234, { long: true })).toBe('3 days');
60+
61+
expect(format(-234234234, { long: true })).toBe('-3 days');
62+
});
63+
});
64+
65+
// numbers
66+
67+
describe('format(number)', () => {
68+
it('should not throw an error', () => {
69+
expect(() => {
70+
format(500);
71+
}).not.toThrowError();
72+
});
73+
74+
it('should support milliseconds', () => {
75+
expect(format(500)).toBe('500ms');
76+
77+
expect(format(-500)).toBe('-500ms');
78+
});
79+
80+
it('should support seconds', () => {
81+
expect(format(1000)).toBe('1s');
82+
expect(format(10000)).toBe('10s');
83+
84+
expect(format(-1000)).toBe('-1s');
85+
expect(format(-10000)).toBe('-10s');
86+
});
87+
88+
it('should support minutes', () => {
89+
expect(format(60 * 1000)).toBe('1m');
90+
expect(format(60 * 10000)).toBe('10m');
91+
92+
expect(format(-1 * 60 * 1000)).toBe('-1m');
93+
expect(format(-1 * 60 * 10000)).toBe('-10m');
94+
});
95+
96+
it('should support hours', () => {
97+
expect(format(60 * 60 * 1000)).toBe('1h');
98+
expect(format(60 * 60 * 10000)).toBe('10h');
99+
100+
expect(format(-1 * 60 * 60 * 1000)).toBe('-1h');
101+
expect(format(-1 * 60 * 60 * 10000)).toBe('-10h');
102+
});
103+
104+
it('should support days', () => {
105+
expect(format(24 * 60 * 60 * 1000)).toBe('1d');
106+
expect(format(24 * 60 * 60 * 10000)).toBe('10d');
107+
108+
expect(format(-1 * 24 * 60 * 60 * 1000)).toBe('-1d');
109+
expect(format(-1 * 24 * 60 * 60 * 10000)).toBe('-10d');
110+
});
111+
112+
it('should round', () => {
113+
expect(format(234234234)).toBe('3d');
114+
115+
expect(format(-234234234)).toBe('-3d');
116+
});
117+
});
118+
119+
// invalid inputs
120+
121+
describe('format(invalid inputs)', () => {
122+
it('should throw an error, when format("")', () => {
123+
expect(() => {
124+
// @ts-expect-error - We expect this to throw.
125+
format('');
126+
}).toThrowError();
127+
});
128+
129+
it('should throw an error, when format(undefined)', () => {
130+
expect(() => {
131+
// @ts-expect-error - We expect this to throw.
132+
format(undefined);
133+
}).toThrowError();
134+
});
135+
136+
it('should throw an error, when format(null)', () => {
137+
expect(() => {
138+
// @ts-expect-error - We expect this to throw.
139+
format(null);
140+
}).toThrowError();
141+
});
142+
143+
it('should throw an error, when format([])', () => {
144+
expect(() => {
145+
// @ts-expect-error - We expect this to throw.
146+
format([]);
147+
}).toThrowError();
148+
});
149+
150+
it('should throw an error, when format({})', () => {
151+
expect(() => {
152+
// @ts-expect-error - We expect this to throw.
153+
format({});
154+
}).toThrowError();
155+
});
156+
157+
it('should throw an error, when format(NaN)', () => {
158+
expect(() => {
159+
format(NaN);
160+
}).toThrowError();
161+
});
162+
163+
it('should throw an error, when format(Infinity)', () => {
164+
expect(() => {
165+
format(Infinity);
166+
}).toThrowError();
167+
});
168+
169+
it('should throw an error, when format(-Infinity)', () => {
170+
expect(() => {
171+
format(-Infinity);
172+
}).toThrowError();
173+
});
174+
});

src/index.ts

+34-7
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ function msFn(value: StringValue, options?: Options): number;
6464
function msFn(value: number, options?: Options): string;
6565
function msFn(value: StringValue | number, options?: Options): number | string {
6666
try {
67-
if (typeof value === 'string' && value.length > 0) {
67+
if (typeof value === 'string') {
6868
return parse(value);
69-
} else if (typeof value === 'number' && isFinite(value)) {
70-
return options?.long ? fmtLong(value) : fmtShort(value);
69+
} else if (typeof value === 'number') {
70+
return format(value, options);
7171
}
72-
throw new Error('Value is not a string or number.');
72+
throw new Error('Value provided to ms() must be a string or number.');
7373
} catch (error) {
7474
const message = isError(error)
7575
? `${error.message}. value=${JSON.stringify(value)}`
@@ -85,9 +85,11 @@ function msFn(value: StringValue | number, options?: Options): number | string {
8585
* @returns The parsed value in milliseconds, or `NaN` if the string can't be
8686
* parsed
8787
*/
88-
function parse(str: string): number {
89-
if (str.length > 100) {
90-
throw new Error('Value exceeds the maximum length of 100 characters.');
88+
export function parse(str: string): number {
89+
if (typeof str !== 'string' || str.length === 0 || str.length > 100) {
90+
throw new Error(
91+
'Value provided to ms.parse() must be a string with length between 1 and 99.',
92+
);
9193
}
9294
const match =
9395
/^(?<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(
@@ -148,6 +150,17 @@ function parse(str: string): number {
148150
}
149151
}
150152

153+
/**
154+
* Parse the given StringValue and return milliseconds.
155+
*
156+
* @param value - A typesafe StringValue to parse to milliseconds
157+
* @returns The parsed value in milliseconds, or `NaN` if the string can't be
158+
* parsed
159+
*/
160+
export function parseStrict(value: StringValue): number {
161+
return parse(value);
162+
}
163+
151164
// eslint-disable-next-line import/no-default-export
152165
export default msFn;
153166

@@ -191,6 +204,20 @@ function fmtLong(ms: number): StringValue {
191204
return `${ms} ms`;
192205
}
193206

207+
/**
208+
* Format the given integer as a string.
209+
*
210+
* @param ms - milliseconds
211+
* @param options - Options for the conversion
212+
* @returns The formatted string
213+
*/
214+
export function format(ms: number, options?: Options): string {
215+
if (typeof ms !== 'number' || !isFinite(ms)) {
216+
throw new Error('Value provided to ms.format() must be of type number.');
217+
}
218+
return options?.long ? fmtLong(ms) : fmtShort(ms);
219+
}
220+
194221
/**
195222
* Pluralization helper.
196223
*/

0 commit comments

Comments
 (0)