Skip to content

Commit eac39af

Browse files
authoredMar 15, 2023
feat(duration): add units on Duration (#561)
1 parent ddebb70 commit eac39af

File tree

4 files changed

+221
-85
lines changed

4 files changed

+221
-85
lines changed
 

‎packages/duration/src/lib/Duration.ts

+139-74
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,72 @@
1+
import { Time } from './constants';
2+
13
const tokens = new Map([
2-
['nanosecond', 1 / 1e6],
3-
['nanoseconds', 1 / 1e6],
4-
['ns', 1 / 1e6],
5-
6-
['millisecond', 1],
7-
['milliseconds', 1],
8-
['ms', 1],
9-
10-
['second', 1000],
11-
['seconds', 1000],
12-
['sec', 1000],
13-
['secs', 1000],
14-
['s', 1000],
15-
16-
['minute', 1000 * 60],
17-
['minutes', 1000 * 60],
18-
['min', 1000 * 60],
19-
['mins', 1000 * 60],
20-
['m', 1000 * 60],
21-
22-
['hour', 1000 * 60 * 60],
23-
['hours', 1000 * 60 * 60],
24-
['hr', 1000 * 60 * 60],
25-
['hrs', 1000 * 60 * 60],
26-
['h', 1000 * 60 * 60],
27-
28-
['day', 1000 * 60 * 60 * 24],
29-
['days', 1000 * 60 * 60 * 24],
30-
['d', 1000 * 60 * 60 * 24],
31-
32-
['week', 1000 * 60 * 60 * 24 * 7],
33-
['weeks', 1000 * 60 * 60 * 24 * 7],
34-
['wk', 1000 * 60 * 60 * 24 * 7],
35-
['wks', 1000 * 60 * 60 * 24 * 7],
36-
['w', 1000 * 60 * 60 * 24 * 7],
37-
38-
['month', 1000 * 60 * 60 * 24 * (365.25 / 12)],
39-
['months', 1000 * 60 * 60 * 24 * (365.25 / 12)],
40-
['b', 1000 * 60 * 60 * 24 * (365.25 / 12)],
41-
['mo', 1000 * 60 * 60 * 24 * (365.25 / 12)],
42-
43-
['year', 1000 * 60 * 60 * 24 * 365.25],
44-
['years', 1000 * 60 * 60 * 24 * 365.25],
45-
['yr', 1000 * 60 * 60 * 24 * 365.25],
46-
['yrs', 1000 * 60 * 60 * 24 * 365.25],
47-
['y', 1000 * 60 * 60 * 24 * 365.25]
4+
['nanosecond', Time.Nanosecond],
5+
['nanoseconds', Time.Nanosecond],
6+
['ns', Time.Nanosecond],
7+
8+
['microsecond', Time.Microsecond],
9+
['microseconds', Time.Microsecond],
10+
['μs', Time.Microsecond],
11+
['us', Time.Microsecond],
12+
13+
['millisecond', Time.Millisecond],
14+
['milliseconds', Time.Millisecond],
15+
['ms', Time.Millisecond],
16+
17+
['second', Time.Second],
18+
['seconds', Time.Second],
19+
['sec', Time.Second],
20+
['secs', Time.Second],
21+
['s', Time.Second],
22+
23+
['minute', Time.Minute],
24+
['minutes', Time.Minute],
25+
['min', Time.Minute],
26+
['mins', Time.Minute],
27+
['m', Time.Minute],
28+
29+
['hour', Time.Hour],
30+
['hours', Time.Hour],
31+
['hr', Time.Hour],
32+
['hrs', Time.Hour],
33+
['h', Time.Hour],
34+
35+
['day', Time.Day],
36+
['days', Time.Day],
37+
['d', Time.Day],
38+
39+
['week', Time.Week],
40+
['weeks', Time.Week],
41+
['wk', Time.Week],
42+
['wks', Time.Week],
43+
['w', Time.Week],
44+
45+
['month', Time.Month],
46+
['months', Time.Month],
47+
['b', Time.Month],
48+
['mo', Time.Month],
49+
50+
['year', Time.Year],
51+
['years', Time.Year],
52+
['yr', Time.Year],
53+
['yrs', Time.Year],
54+
['y', Time.Year]
4855
]);
4956

57+
const mappings = new Map([
58+
[Time.Nanosecond, 'nanoseconds'],
59+
[Time.Microsecond, 'microseconds'],
60+
[Time.Millisecond, 'milliseconds'],
61+
[Time.Second, 'seconds'],
62+
[Time.Minute, 'minutes'],
63+
[Time.Hour, 'hours'],
64+
[Time.Day, 'days'],
65+
[Time.Week, 'weeks'],
66+
[Time.Month, 'months'],
67+
[Time.Year, 'years']
68+
] as const);
69+
5070
/**
5171
* Converts duration strings into ms and future dates
5272
*/
@@ -57,66 +77,111 @@ export class Duration {
5777
public offset: number;
5878

5979
/**
60-
* Create a new Duration instance
61-
* @param pattern The string to parse
80+
* The amount of nanoseconds extracted from the text.
6281
*/
63-
public constructor(pattern: string) {
64-
this.offset = Duration.parse(pattern.toLowerCase());
65-
}
82+
public nanoseconds = 0;
6683

6784
/**
68-
* Get the date from now
85+
* The amount of microseconds extracted from the text.
6986
*/
70-
public get fromNow(): Date {
71-
return this.dateFrom(new Date());
72-
}
87+
public microseconds = 0;
7388

7489
/**
75-
* Get the date from
76-
* @param date The Date instance to get the date from
90+
* The amount of milliseconds extracted from the text.
7791
*/
78-
public dateFrom(date: Date): Date {
79-
return new Date(date.getTime() + this.offset);
80-
}
92+
public milliseconds = 0;
8193

8294
/**
83-
* The RegExp used for the pattern parsing
95+
* The amount of seconds extracted from the text.
8496
*/
85-
private static readonly kPatternRegex = /(-?\d*\.?\d+(?:e[-+]?\d+)?)\s*([a-zμ]*)/gi;
97+
public seconds = 0;
8698

8799
/**
88-
* The RegExp used for removing commas
100+
* The amount of minutes extracted from the text.
89101
*/
90-
private static readonly kCommaRegex = /,/g;
102+
public minutes = 0;
91103

92104
/**
93-
* The RegExp used for replacing a/an with 1
105+
* The amount of hours extracted from the text.
106+
*/
107+
public hours = 0;
108+
109+
/**
110+
* The amount of days extracted from the text.
111+
*/
112+
public days = 0;
113+
114+
/**
115+
* The amount of weeks extracted from the text.
116+
*/
117+
public weeks = 0;
118+
119+
/**
120+
* The amount of months extracted from the text.
121+
*/
122+
public months = 0;
123+
124+
/**
125+
* The amount of years extracted from the text.
94126
*/
95-
private static readonly kAanRegex = /\ban?\b/gi;
127+
public years = 0;
96128

97129
/**
98-
* Parse the pattern
99-
* @param pattern The pattern to parse
130+
* Create a new Duration instance
131+
* @param pattern The string to parse
100132
*/
101-
private static parse(pattern: string): number {
133+
public constructor(pattern: string) {
102134
let result = 0;
103135
let valid = false;
104136

105137
pattern
138+
.toLowerCase()
106139
// ignore commas
107-
.replace(Duration.kCommaRegex, '')
140+
.replace(Duration.commaRegex, '')
108141
// a / an = 1
109-
.replace(Duration.kAanRegex, '1')
142+
.replace(Duration.aAndAnRegex, '1')
110143
// do math
111-
.replace(Duration.kPatternRegex, (_, i, units) => {
144+
.replace(Duration.patternRegex, (_, i, units) => {
112145
const token = tokens.get(units);
113146
if (token !== undefined) {
114-
result += Number(i) * token;
147+
const n = Number(i);
148+
result += n * token;
149+
this[mappings.get(token)!] += n;
115150
valid = true;
116151
}
117152
return '';
118153
});
119154

120-
return valid ? result : NaN;
155+
this.offset = valid ? result : NaN;
156+
}
157+
158+
/**
159+
* Get the date from now
160+
*/
161+
public get fromNow(): Date {
162+
return this.dateFrom(new Date());
163+
}
164+
165+
/**
166+
* Get the date from
167+
* @param date The Date instance to get the date from
168+
*/
169+
public dateFrom(date: Date): Date {
170+
return new Date(date.getTime() + this.offset);
121171
}
172+
173+
/**
174+
* The RegExp used for the pattern parsing
175+
*/
176+
private static readonly patternRegex = /(-?\d*\.?\d+(?:e[-+]?\d+)?)\s*([a-zμ]*)/gi;
177+
178+
/**
179+
* The RegExp used for removing commas
180+
*/
181+
private static readonly commaRegex = /,/g;
182+
183+
/**
184+
* The RegExp used for replacing a/an with 1
185+
*/
186+
private static readonly aAndAnRegex = /\ban?\b/gi;
122187
}
+80-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,85 @@
1-
import { Duration } from '../../src';
1+
import { Duration, Time } from '../../src';
22

33
describe('Duration', () => {
4-
test('GIVEN duration with an offset of 0s, THEN shows 0ms', () => {
5-
const duration = new Duration('0s');
6-
expect(duration.offset).toEqual(0);
7-
});
4+
describe('units', () => {
5+
function expectDurationUnits(duration: Duration, units: Partial<Record<keyof Duration, number>> = {}) {
6+
expect(duration.nanoseconds).toEqual(units.nanoseconds ?? 0);
7+
expect(duration.microseconds).toEqual(units.microseconds ?? 0);
8+
expect(duration.milliseconds).toEqual(units.milliseconds ?? 0);
9+
expect(duration.seconds).toEqual(units.seconds ?? 0);
10+
expect(duration.minutes).toEqual(units.minutes ?? 0);
11+
expect(duration.hours).toEqual(units.hours ?? 0);
12+
expect(duration.days).toEqual(units.days ?? 0);
13+
expect(duration.weeks).toEqual(units.weeks ?? 0);
14+
expect(duration.months).toEqual(units.months ?? 0);
15+
expect(duration.years).toEqual(units.years ?? 0);
16+
}
817

9-
test('GIVEN duration with an offset of 1s, THEN shows 1000ms', () => {
10-
const duration = new Duration('a second');
11-
expect(duration.offset).toEqual(1000);
18+
test.each(['0ns', '0us', '0μs', '0ms', '0s', '0m', '0h', '0d', '0w', '0mo', '0yr'])('GIVEN %s THEN shows 0ms', (pattern) => {
19+
const duration = new Duration(pattern);
20+
expect(duration.offset).toEqual(0);
21+
expectDurationUnits(duration, {});
22+
});
23+
24+
test.each(['a nanosecond', '1ns'])('GIVEN %s THEN shows 1ns', (pattern) => {
25+
const duration = new Duration(pattern);
26+
expect(duration.offset).toEqual(Time.Nanosecond);
27+
expectDurationUnits(duration, { nanoseconds: 1 });
28+
});
29+
30+
test.each(['a microsecond', '1us', '1μs'])('GIVEN %s THEN shows 1μs', (pattern) => {
31+
const duration = new Duration(pattern);
32+
expect(duration.offset).toEqual(Time.Microsecond);
33+
expectDurationUnits(duration, { microseconds: 1 });
34+
});
35+
36+
test.each(['a millisecond', '1ms'])('GIVEN %s THEN shows 1ms', (pattern) => {
37+
const duration = new Duration(pattern);
38+
expect(duration.offset).toEqual(Time.Millisecond);
39+
expectDurationUnits(duration, { milliseconds: 1 });
40+
});
41+
42+
test.each(['a second', '1s'])('GIVEN %s THEN shows 1s', (pattern) => {
43+
const duration = new Duration(pattern);
44+
expect(duration.offset).toEqual(Time.Second);
45+
expectDurationUnits(duration, { seconds: 1 });
46+
});
47+
48+
test.each(['a minute', '1m'])('GIVEN %s THEN shows 1m', (pattern) => {
49+
const duration = new Duration(pattern);
50+
expect(duration.offset).toEqual(Time.Minute);
51+
expectDurationUnits(duration, { minutes: 1 });
52+
});
53+
54+
test.each(['a hour', '1h'])('GIVEN %s THEN shows 1h', (pattern) => {
55+
const duration = new Duration(pattern);
56+
expect(duration.offset).toEqual(Time.Hour);
57+
expectDurationUnits(duration, { hours: 1 });
58+
});
59+
60+
test.each(['a day', '1d'])('GIVEN %s THEN shows 1d', (pattern) => {
61+
const duration = new Duration(pattern);
62+
expect(duration.offset).toEqual(Time.Day);
63+
expectDurationUnits(duration, { days: 1 });
64+
});
65+
66+
test.each(['a week', '1w'])('GIVEN %s THEN shows 1w', (pattern) => {
67+
const duration = new Duration(pattern);
68+
expect(duration.offset).toEqual(Time.Week);
69+
expectDurationUnits(duration, { weeks: 1 });
70+
});
71+
72+
test.each(['a month', '1mo'])('GIVEN %s THEN shows 1mo', (pattern) => {
73+
const duration = new Duration(pattern);
74+
expect(duration.offset).toEqual(Time.Month);
75+
expectDurationUnits(duration, { months: 1 });
76+
});
77+
78+
test.each(['a year', '1yr'])('GIVEN %s THEN shows 1yr', (pattern) => {
79+
const duration = new Duration(pattern);
80+
expect(duration.offset).toEqual(Time.Year);
81+
expectDurationUnits(duration, { years: 1 });
82+
});
1283
});
1384

1485
test('GIVEN invalid duration THEN show NaN', () => {
@@ -19,6 +90,6 @@ describe('Duration', () => {
1990
test('GIVEN duration with offset, THEN dateFrom is valid', () => {
2091
const duration = new Duration('a second');
2192
const date = new Date();
22-
expect(duration.dateFrom(date)).toEqual(new Date(date.getTime() + 1000));
93+
expect(duration.dateFrom(date)).toEqual(new Date(date.getTime() + Time.Second));
2394
});
2495
});

‎packages/eslint-config/tests/__snapshots__/eslint.test.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
1+
// Vitest Snapshot v1
22

33
exports[`ESLint Config > should export rules 1`] = `
44
{

‎packages/prettier-config/tests/__snapshots__/prettier.test.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
1+
// Vitest Snapshot v1
22

33
exports[`Prettier Config > should export rules 1`] = `
44
{

0 commit comments

Comments
 (0)
Please sign in to comment.