aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver/frontend/html/util/friendly.js
blob: f400bbce0767830a3fda5c5e5577240f1885c42a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Copyright Epic Games, Inc. All Rights Reserved.

"use strict";

////////////////////////////////////////////////////////////////////////////////
export class Friendly
{
	static sep(value, prec=0)
	{
		return (+Number(value)).toLocaleString("en", {
			style: "decimal",
			minimumFractionDigits : prec,
			maximumFractionDigits : prec,
		});
	}

	static k(x, p=0)	{ return Friendly.sep((BigInt(x) + 999n)  / BigInt(Math.pow(10, 3))|0n, p) + "K"; }
	static m(x, p=1)	{ return Friendly.sep( BigInt(x)         / BigInt(Math.pow(10, 6)),   p) + "M"; }
	static g(x, p=2)	{ return Friendly.sep( BigInt(x)         / BigInt(Math.pow(10, 9)),   p) + "G"; }
	static kib(x, p=0)	{ return Friendly.sep((BigInt(x) + 1023n) / (1n << 10n)|0n, p) + " KiB"; }
	static mib(x, p=1)	{ return Friendly.sep( BigInt(x)         / (1n << 20n),   p) + " MiB"; }
	static gib(x, p=2)	{ return Friendly.sep( BigInt(x)         / (1n << 30n),   p) + " GiB"; }

	static duration(s)
	{
		const v = Number(s);
		if (v >= 1)		return Friendly.sep(v, 2) + " s";
		if (v >= 0.001)	return Friendly.sep(v * 1000, 2) + " ms";
		if (v >= 0.000001)	return Friendly.sep(v * 1000000, 1) + " µs";
		return Friendly.sep(v * 1000000000, 0) + " ns";
	}

	/** Format a .NET-style TimeSpan string (e.g. "0:05:23.4560000") as a human-readable duration. */
	static timespan(value)
	{
		if (typeof value === "number")
		{
			return Friendly._formatDurationMs(value);
		}

		const str = String(value);
		const match = str.match(/^[+-]?(?:(\d+)\.)?(\d+):(\d+):(\d+)(?:\.(\d+))?$/);
		if (!match)
		{
			return str;
		}

		const days = parseInt(match[1] || "0", 10);
		const hours = parseInt(match[2], 10);
		const minutes = parseInt(match[3], 10);
		const seconds = parseInt(match[4], 10);
		const frac = match[5] ? parseInt(match[5].substring(0, 3).padEnd(3, "0"), 10) : 0;
		const totalMs = ((days * 86400 + hours * 3600 + minutes * 60 + seconds) * 1000) + frac;

		return Friendly._formatDurationMs(totalMs);
	}

	static _formatDurationMs(ms)
	{
		const seconds = Math.floor(ms / 1000);
		const minutes = Math.floor(seconds / 60);
		const hours = Math.floor(minutes / 60);
		const days = Math.floor(hours / 24);

		if (days > 0)	return `${days}d ${hours % 24}h ${minutes % 60}m`;
		if (hours > 0)	return `${hours}h ${minutes % 60}m`;
		if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
		if (seconds > 0) return `${seconds}.${String(ms % 1000).padStart(3, "0")}s`;
		return `${ms}ms`;
	}

	/** Format an ISO / .NET datetime string as a friendly local date+time. */
	static datetime(value)
	{
		const d = new Date(value);
		if (isNaN(d.getTime()))
		{
			return String(value);
		}
		return d.toLocaleString(undefined, {
			year: "numeric", month: "short", day: "numeric",
			hour: "2-digit", minute: "2-digit", second: "2-digit",
		});
	}

	static bytes(x)
	{
		const v = BigInt(Math.trunc(Number(x)));
		if (v >= (1n << 60n))	return Friendly.sep(Number(v) / Number(1n << 60n), 2) + " EiB";
		if (v >= (1n << 50n))	return Friendly.sep(Number(v) / Number(1n << 50n), 2) + " PiB";
		if (v >= (1n << 40n))	return Friendly.sep(Number(v) / Number(1n << 40n), 2) + " TiB";
		if (v >= (1n << 30n))	return Friendly.sep(Number(v) / Number(1n << 30n), 2) + " GiB";
		if (v >= (1n << 20n))	return Friendly.sep(Number(v) / Number(1n << 20n), 1) + " MiB";
		if (v >= (1n << 10n))	return Friendly.sep(Number(v) / Number(1n << 10n), 0) + " KiB";
		return Friendly.sep(Number(v), 0) + " B";
	}
}