Skip to content

Commit

Permalink
feat(js-core): add Stopwatch
Browse files Browse the repository at this point in the history
  • Loading branch information
ersimont committed Dec 8, 2021
1 parent d3b78e9 commit 2704c53
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 2 deletions.
5 changes: 5 additions & 0 deletions projects/integration/src/app/api-tests/js-core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
setIntersection,
setUnion,
sleep,
Stopwatch,
symmetricSetDifference,
TimeUnit,
toCsv,
Expand Down Expand Up @@ -56,6 +57,10 @@ describe('js-core', () => {
expect(Persistence).toBeDefined();
});

it('has Stopwatch', () => {
expect(Stopwatch).toBeDefined();
});

it('has assert', () => {
expect(assert).toBeDefined();
});
Expand Down
1 change: 1 addition & 0 deletions projects/js-core/src/lib/time/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { Debouncer } from './debouncer';
export { Deferred } from './deferred';
export { sleep } from './sleep';
export { Stopwatch } from './stopwatch';
export { TimeUnit, convertTime, elapsedToString } from './time-utils';
109 changes: 109 additions & 0 deletions projects/js-core/src/lib/time/stopwatch.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { fakeAsync, tick } from '@angular/core/testing';
import { convertTime } from '@s-libs/js-core';
import { Stopwatch } from './stopwatch';

describe('Stopwatch', () => {
beforeEach(() => {
spyOn(performance, 'now').and.callFake(() => Date.now());
});

describe('constructor', () => {
it('accepts a running flag', fakeAsync(() => {
const started = new Stopwatch({ running: true });
const stopped = new Stopwatch({ running: false });

tick(1);

expect(started.getElapsed()).toBe(1);
expect(stopped.getElapsed()).toBe(0);
}));

it('accepts previouslyElapsed', fakeAsync(() => {
expect(new Stopwatch({ previouslyElapsed: 23 }).getElapsed()).toBe(23);
}));

it('defaults to running w/ no previous time', fakeAsync(() => {
const timer = new Stopwatch();
tick(1);
expect(timer.getElapsed()).toBe(1);
}));
});

describe('.start()', () => {
it('starts the timer', fakeAsync(() => {
const timer = new Stopwatch({ running: false });

timer.start();

tick(1);
expect(timer.getElapsed()).toBe(1);
}));

it('does nothing if already running', fakeAsync(() => {
const timer = new Stopwatch();
tick(1);

timer.start();

tick(1);
expect(timer.getElapsed()).toBe(2);
}));

it('resumes the timer', fakeAsync(() => {
const timer = new Stopwatch({ running: false, previouslyElapsed: 1 });

timer.start();

tick(1);
expect(timer.getElapsed()).toBe(2);
}));
});

describe('.stop()', () => {
it('stops the timer', fakeAsync(() => {
const timer = new Stopwatch();

timer.stop();

tick(1);
expect(timer.getElapsed()).toBe(0);
}));

it('preserves already elapsed time', fakeAsync(() => {
const timer = new Stopwatch({ previouslyElapsed: 1 });
timer.stop();
expect(timer.getElapsed()).toBe(1);
}));

it('preserves currently elapsed time', fakeAsync(() => {
const timer = new Stopwatch();
tick(1);

timer.stop();

expect(timer.getElapsed()).toBe(1);
}));
});

describe('.getElapsed()', () => {
it('includes previously and currently elapsed time', fakeAsync(() => {
const timer = new Stopwatch({ previouslyElapsed: 1 });
tick(1);
expect(timer.getElapsed()).toBe(2);
}));
});

describe('.toString()', () => {
it('goes up to days', fakeAsync(() => {
const timer = new Stopwatch();
tick(convertTime(365, 'days', 'ms'));
expect(timer.toString()).toBe('365 d 0 h 0 m 0 s 0 ms');
}));

it('does not show leading zeros', fakeAsync(() => {
const timer = new Stopwatch();
tick(1);
expect(timer.toString()).toBe('1 ms');
}));
});
});
65 changes: 65 additions & 0 deletions projects/js-core/src/lib/time/stopwatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { elapsedToString, isDefined } from '@s-libs/js-core';

/**
* Keeps track of elapsed time.
*
* ```ts
* const stopwatch = new Stopwatch();
* // do some stuff that should be included in time
* stopwatch.stop();
* // do some more stuff that should not
* stopwatch.start();
* // do more stuff that should be timed
* console.log(`important stuff took ${stopwatch}`);
* ```
*/
export class Stopwatch {
#start?: number;
#previouslyElapsed: number;

/**
* @param running whether the stopwatch is started immediately when constructed
* @param previouslyElapsed milliseconds to be added to the total
*/
constructor({ running = true, previouslyElapsed = 0 } = {}) {
this.#previouslyElapsed = previouslyElapsed;
if (running) {
this.start();
}
}

/**
* Starts adding elapsed time to the total.
*/
start(): void {
this.#start ??= performance.now();
}

/**
* Stops adding elapsed time to the total.
*/
stop(): void {
this.#previouslyElapsed += this.#getCurrentlyElapsed();
this.#start = undefined;
}

/**
* @returns the total number of milliseconds added to the total so far.
*/
getElapsed(): number {
return this.#previouslyElapsed + this.#getCurrentlyElapsed();
}

/**
* @param units see {@link elapsedToString}
*/
toString(units = ['d', 'h', 'm', 's', 'ms']): string {
return elapsedToString(this.getElapsed(), units, {
showLeadingZeros: false,
});
}

#getCurrentlyElapsed(): number {
return isDefined(this.#start) ? performance.now() - this.#start : 0;
}
}
4 changes: 2 additions & 2 deletions projects/js-core/src/lib/time/time-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('time-utils', () => {
elapsedToString(convertTime(1, 'w', 'ns') - 1, ['w', 'ns'], {
elapsedUnit: TimeUnit.Nanoseconds,
}),
).toBe('0 w 604_799_999_999_999 ns');
).toBe('0 w 604799999999999 ns');
expect(
elapsedToString(convertTime(1, 'w', 'ns'), ['w', 'ns'], {
elapsedUnit: TimeUnit.Nanoseconds,
Expand All @@ -99,7 +99,7 @@ describe('time-utils', () => {
elapsedToString(convertTime(1, 'mil', 'ms') - 1, ['mil', 'ms'], {
elapsedUnit: TimeUnit.Milliseconds,
}),
).toBe('0 mil 31_535_999_999_999 ms');
).toBe('0 mil 31535999999999 ms');
expect(
elapsedToString(convertTime(1, 'mil', 'ms'), ['mil', 'ms'], {
elapsedUnit: TimeUnit.Milliseconds,
Expand Down

0 comments on commit 2704c53

Please sign in to comment.