Skip to content

Commit 4ed65a7

Browse files
committed
breaking: Replace getDuration() / humanizeDuration() utils with Duration class (with .format() method)
1 parent 4405120 commit 4ed65a7

7 files changed

Lines changed: 395 additions & 145 deletions

File tree

.changeset/quick-ducks-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@layerstack/utils': major
3+
---
4+
5+
breaking: Replace `getDuration()` / `humanizeDuration()` utils with `Duration` class (with `.format()` method)
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
import { Duration, DurationUnits } from './duration.js';
4+
import { PeriodType } from './date_types.js';
5+
import { testDate } from './date.test.js';
6+
import { subDays } from 'date-fns';
7+
8+
describe('Duration', () => {
9+
it('default', () => {
10+
const actual = new Duration();
11+
expect(actual.years).equal(0);
12+
expect(actual.days).equal(0);
13+
expect(actual.hours).equal(0);
14+
expect(actual.minutes).equal(0);
15+
expect(actual.seconds).equal(0);
16+
expect(actual.milliseconds).equal(0);
17+
});
18+
19+
it('start/end range with strings', () => {
20+
const actual = new Duration({ start: '2025-05-19', end: '2025-05-20' });
21+
expect(actual.years).equal(0);
22+
expect(actual.days).equal(1);
23+
expect(actual.hours).equal(0);
24+
expect(actual.minutes).equal(0);
25+
expect(actual.seconds).equal(0);
26+
expect(actual.milliseconds).equal(0);
27+
});
28+
29+
it('start/end range with Date objects', () => {
30+
const actual = new Duration({ start: new Date('2025-05-19'), end: new Date('2025-05-20') });
31+
expect(actual.years).equal(0);
32+
expect(actual.days).equal(1);
33+
expect(actual.hours).equal(0);
34+
expect(actual.minutes).equal(0);
35+
expect(actual.seconds).equal(0);
36+
expect(actual.milliseconds).equal(0);
37+
});
38+
39+
it('start-only should use `now` for end', () => {
40+
const start = subDays(new Date(), 10);
41+
const actual = new Duration({ start });
42+
expect(actual.years).equal(0);
43+
expect(actual.days).equal(10);
44+
expect(actual.hours).equal(0);
45+
expect(actual.minutes).equal(0);
46+
expect(actual.seconds).equal(0);
47+
// expect(actual.milliseconds).equal(0); // Ignoring just in case test timing is off
48+
});
49+
50+
it('duration option', () => {
51+
const actual = new Duration({ duration: { seconds: 10 } });
52+
expect(actual.years).equal(0);
53+
expect(actual.days).equal(0);
54+
expect(actual.hours).equal(0);
55+
expect(actual.minutes).equal(0);
56+
expect(actual.seconds).equal(10);
57+
expect(actual.milliseconds).equal(0);
58+
});
59+
60+
it('duration option with carryover seconds', () => {
61+
const actual = new Duration({ duration: { seconds: 90 } });
62+
expect(actual.years).equal(0);
63+
expect(actual.days).equal(0);
64+
expect(actual.hours).equal(0);
65+
expect(actual.minutes).equal(1);
66+
expect(actual.seconds).equal(30);
67+
expect(actual.milliseconds).equal(0);
68+
});
69+
70+
it('duration option with carryover minutes', () => {
71+
const actual = new Duration({ duration: { minutes: 90 } });
72+
expect(actual.years).equal(0);
73+
expect(actual.days).equal(0);
74+
expect(actual.hours).equal(1);
75+
expect(actual.minutes).equal(30);
76+
expect(actual.seconds).equal(0);
77+
expect(actual.milliseconds).equal(0);
78+
});
79+
80+
it('duration option with carryover hours', () => {
81+
const actual = new Duration({ duration: { hours: 30 } });
82+
expect(actual.years).equal(0);
83+
expect(actual.days).equal(1);
84+
expect(actual.hours).equal(6);
85+
expect(actual.minutes).equal(0);
86+
expect(actual.seconds).equal(0);
87+
expect(actual.milliseconds).equal(0);
88+
});
89+
90+
it('duration comparison with explicit duration', () => {
91+
const duration1 = new Duration({ duration: { seconds: 10 } });
92+
const duration2 = new Duration({ duration: { seconds: 11 } });
93+
94+
expect(duration1 < duration2).equal(true);
95+
expect(duration2 > duration1).equal(true);
96+
expect(duration1 != duration2).equal(true);
97+
});
98+
99+
it('duration comparison with dates', () => {
100+
const duration1 = new Duration({ start: '2025-05-19', end: '2025-05-20' });
101+
const duration2 = new Duration({ start: '2025-05-19', end: '2025-05-21' });
102+
103+
expect(duration1 < duration2).equal(true);
104+
expect(duration2 > duration1).equal(true);
105+
});
106+
107+
it('duration comparison with dates and duration', () => {
108+
const duration = new Duration({ start: '2025-05-19', end: '2025-05-20' });
109+
110+
const durationSame = new Duration({ duration: { days: 1 } });
111+
// TODO: Why is `valueOf()` not called implicitly?
112+
// expect(duration == durationSame).equal(true);
113+
expect(duration.valueOf() == durationSame.valueOf()).equal(true);
114+
115+
const durationMore = new Duration({ duration: { days: 2 } });
116+
expect(duration < durationMore).equal(true);
117+
118+
const durationLess = new Duration({ duration: { hours: 3 } });
119+
expect(duration > durationLess).equal(true);
120+
});
121+
122+
it('toJSON', () => {
123+
const duration = new Duration({
124+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
125+
});
126+
const json = duration.toJSON();
127+
expect(json).eql({
128+
years: 0,
129+
days: 1,
130+
hours: 2,
131+
minutes: 3,
132+
seconds: 4,
133+
milliseconds: 5,
134+
});
135+
});
136+
137+
it('toJSON', () => {
138+
const duration = new Duration({
139+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
140+
});
141+
const string = JSON.stringify(duration);
142+
expect(string).eql('{"years":0,"days":1,"hours":2,"minutes":3,"seconds":4,"milliseconds":5}');
143+
});
144+
145+
it('reconstruct from JSON', () => {
146+
const duration = new Duration({
147+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
148+
});
149+
const json = JSON.parse(JSON.stringify(duration));
150+
const duration2 = new Duration({ duration: json });
151+
152+
expect(duration2.years).equal(0);
153+
expect(duration2.days).equal(1);
154+
expect(duration2.hours).equal(2);
155+
expect(duration2.minutes).equal(3);
156+
expect(duration2.seconds).equal(4);
157+
expect(duration2.milliseconds).equal(5);
158+
});
159+
160+
describe('format', () => {
161+
it('default', () => {
162+
const duration = new Duration({
163+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
164+
});
165+
const actual = duration.format();
166+
expect(actual).equal('1d 2h 3m 4s 5ms');
167+
});
168+
169+
it('long variant', () => {
170+
const duration = new Duration({
171+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
172+
});
173+
const actual = duration.format({ variant: 'long' });
174+
expect(actual).equal('1 day and 2 hours and 3 minutes and 4 seconds and 5 milliseconds');
175+
});
176+
177+
it('minUnits', () => {
178+
const duration = new Duration({
179+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
180+
});
181+
const actual = duration.format({ minUnits: DurationUnits.Hour });
182+
expect(actual).equal('1d 2h');
183+
});
184+
185+
it('totalUnits', () => {
186+
const duration = new Duration({
187+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
188+
});
189+
const actual = duration.format({ totalUnits: 3 });
190+
expect(actual).equal('1d 2h 3m');
191+
});
192+
});
193+
194+
it('toString', () => {
195+
const duration = new Duration({
196+
duration: { days: 1, hours: 2, minutes: 3, seconds: 4, milliseconds: 5 },
197+
});
198+
const actual = duration.toString();
199+
expect(actual).equal('1d 2h 3m 4s 5ms');
200+
});
201+
});

0 commit comments

Comments
 (0)