1 | export class Timer {
|
2 | private readonly start: Date;
|
3 | private readonly markers: Record<string, Date | undefined> = Object.create(null);
|
4 |
|
5 | constructor(private readonly now = () => new Date()) {
|
6 | this.start = this.now();
|
7 | }
|
8 | public humanReadableElapsed(sinceMarker?: string): string {
|
9 | const elapsedSeconds = this.elapsedSeconds(sinceMarker);
|
10 | return Timer.humanReadableElapsedMinutes(elapsedSeconds) + Timer.humanReadableElapsedSeconds(elapsedSeconds);
|
11 | }
|
12 |
|
13 | public elapsedSeconds(sinceMarker?: string): number {
|
14 | const elapsedMs = this.elapsedMs(sinceMarker);
|
15 | return Math.floor(elapsedMs / 1000);
|
16 | }
|
17 |
|
18 | public elapsedMs(sinceMarker?: string): number {
|
19 | const marker = sinceMarker && this.markers[sinceMarker];
|
20 | if (marker) {
|
21 | return this.now().getTime() - marker.getTime();
|
22 | } else {
|
23 | return this.now().getTime() - this.start.getTime();
|
24 | }
|
25 | }
|
26 |
|
27 | public mark(name: string): void {
|
28 | this.markers[name] = this.now();
|
29 | }
|
30 |
|
31 | private static humanReadableElapsedSeconds(elapsedSeconds: number) {
|
32 | const restSeconds = elapsedSeconds % 60;
|
33 | return this.formatTime('second', restSeconds);
|
34 | }
|
35 |
|
36 | private static humanReadableElapsedMinutes(elapsedSeconds: number) {
|
37 | const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
38 | if (elapsedMinutes === 0) {
|
39 | return '';
|
40 | } else {
|
41 | return this.formatTime('minute', elapsedMinutes);
|
42 | }
|
43 | }
|
44 |
|
45 | private static formatTime(word: 'minute' | 'second', elapsed: number) {
|
46 | const s = elapsed === 1 ? '' : 's';
|
47 | const blank = word === 'minute' ? ' ' : '';
|
48 | return `${elapsed} ${word}${s}${blank}`;
|
49 | }
|
50 | }
|