Skip to content

Commit

Permalink
feat(core): duration.toHumanString()
Browse files Browse the repository at this point in the history
Add a function for `Duration` to render itself to a human readable
string.

This can be used in dashboards or other situations where Durations need
to be represented.
  • Loading branch information
rix0rrr committed Mar 12, 2020
1 parent bed5357 commit 4a16d4c
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 15 deletions.
59 changes: 46 additions & 13 deletions packages/@aws-cdk/core/lib/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ export class Duration {
if (!days && !hours && !minutes && !seconds) {
throw new Error(`Not a valid ISO duration: ${duration}`);
}
return Duration.seconds(
_toInt(seconds)
+ (_toInt(minutes) * TimeUnit.Minutes.inSeconds)
+ (_toInt(hours) * TimeUnit.Hours.inSeconds)
+ (_toInt(days) * TimeUnit.Days.inSeconds)
return Duration.millis(
_toInt(seconds) * TimeUnit.Seconds.inMillis
+ (_toInt(minutes) * TimeUnit.Minutes.inMillis)
+ (_toInt(hours) * TimeUnit.Hours.inMillis)
+ (_toInt(days) * TimeUnit.Days.inMillis)
);

function _toInt(str: string): number {
Expand Down Expand Up @@ -143,6 +143,36 @@ export class Duration {
}
}

/**
* Turn this duration into a human-readable string
*/
public toHumanString(): string {
if (this.amount === 0) { return fmtUnit(0, this.unit); }
if (Token.isUnresolved(this.amount)) { return `<token> ${this.unit.label}`; }

let millis = convert(this.amount, this.unit, TimeUnit.Milliseconds, { integral: false });
const parts = new Array<string>();

for (const unit of [TimeUnit.Days, TimeUnit.Hours, TimeUnit.Hours, TimeUnit.Minutes, TimeUnit.Seconds, TimeUnit.Milliseconds]) {
const wholeCount = Math.floor(convert(millis, TimeUnit.Milliseconds, unit, { integral: false }));
if (wholeCount > 0) {
parts.push(fmtUnit(wholeCount, unit));
millis -= wholeCount * unit.inMillis;
}
}

// 2 significant parts, that's totally enough for humans
return parts.slice(0, 2).join(' ');

function fmtUnit(amount: number, unit: TimeUnit) {
if (amount === 1) {
// All of the labels end in 's'
return `${amount} ${unit.label.substring(0, unit.label.length - 1)}`;
}
return `${amount} ${unit.label}`;
}
}

/**
* Returns a string representation of this `Duration` that is also a Token that cannot be successfully resolved. This
* protects users against inadvertently stringifying a `Duration` object, when they should have called one of the
Expand Down Expand Up @@ -183,13 +213,16 @@ export interface TimeConversionOptions {
}

class TimeUnit {
public static readonly Milliseconds = new TimeUnit('millis', 0.001);
public static readonly Seconds = new TimeUnit('seconds', 1);
public static readonly Minutes = new TimeUnit('minutes', 60);
public static readonly Hours = new TimeUnit('hours', 3_600);
public static readonly Days = new TimeUnit('days', 86_400);
public static readonly Milliseconds = new TimeUnit('millis', 1);
public static readonly Seconds = new TimeUnit('seconds', 1_000);
public static readonly Minutes = new TimeUnit('minutes', 60_000);
public static readonly Hours = new TimeUnit('hours', 3_600_000);
public static readonly Days = new TimeUnit('days', 86_400_000);

private constructor(public readonly label: string, public readonly inSeconds: number) {
private constructor(public readonly label: string, public readonly inMillis: number) {
// MAX_SAFE_INTEGER is 2^53, so by representing our duration in millis (the lowest
// common unit) the highest duration we can represent is
// 2^53 / 86*10^6 ~= 104 * 10^6 days (about 100 million days).
}

public toString() {
Expand All @@ -198,8 +231,8 @@ class TimeUnit {
}

function convert(amount: number, fromUnit: TimeUnit, toUnit: TimeUnit, { integral = true }: TimeConversionOptions) {
if (fromUnit.inSeconds === toUnit.inSeconds) { return amount; }
const multiplier = fromUnit.inSeconds / toUnit.inSeconds;
if (fromUnit.inMillis === toUnit.inMillis) { return amount; }
const multiplier = fromUnit.inMillis / toUnit.inMillis;

if (Token.isUnresolved(amount)) {
throw new Error(`Unable to perform time unit conversion on un-resolved token ${amount}.`);
Expand Down
21 changes: 19 additions & 2 deletions packages/@aws-cdk/core/test/test.duration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as nodeunit from 'nodeunit';
import { Duration, Stack, Token } from '../lib';
import { Duration, Lazy, Stack, Token } from '../lib';

export = nodeunit.testCase({
'negative amount'(test: nodeunit.Test) {
Expand Down Expand Up @@ -107,7 +107,24 @@ export = nodeunit.testCase({
test.equal(Duration.parse('PT1D1H1M1S').toSeconds(), 1 + 60 * (1 + 60 * (1 + 24)));

test.done();
}
},

'to human string'(test: nodeunit.Test) {
test.equal(Duration.minutes(0).toHumanString(), '0 minutes');
test.equal(Duration.minutes(Lazy.numberValue({ produce: () => 5 })).toHumanString(), '<token> minutes');

test.equal(Duration.minutes(10).toHumanString(), '10 minutes');
test.equal(Duration.minutes(1).toHumanString(), '1 minute');

test.equal(Duration.minutes(62).toHumanString(), '1 hour 2 minutes');

test.equal(Duration.seconds(3666).toHumanString(), '1 hour 1 minute');

test.equal(Duration.millis(3000).toHumanString(), '3 seconds');
test.equal(Duration.millis(3666).toHumanString(), '3 seconds 666 millis');

test.done();
},
});

function floatEqual(test: nodeunit.Test, actual: number, expected: number) {
Expand Down

0 comments on commit 4a16d4c

Please sign in to comment.