import { Duration as DurationPB } from "@bufbuild/protobuf"; /** * A time value with nanosecond precision, generated by `now()`. * * The first element is the number of seconds since some epoch. * The second element is the number of nanoseconds. */ export type HRTime = [number, number]; /** * Returns nanosecond precision time since some epoch based * upon a high resolution timer. */ export function now(): HRTime { return process.hrtime(); } /** * A duration of time, generated by `since()`. * * The first element is the number of seconds. * The second element is the number of nanoseconds. */ export type Duration = [number, number]; export function since(start: HRTime): Duration { return process.hrtime(start); } const Microsecond = 1000; const Millisecond = 1000 * Microsecond; /** * This function is used to convert */ export function toDurationStr([seconds, nanoseconds]: Duration): string { const neg = seconds < 0; if (neg) { seconds = -seconds; } // Force integer values for seconds and nanoseconds seconds = ~~seconds; nanoseconds = ~~nanoseconds; if (seconds === 0) { // Special case: if duration is smaller than a second, // use smaller units, like 1.2ms let prec: number; let unit: string; let divisor: number; if (nanoseconds === 0) { return "0s"; } else if (nanoseconds < Microsecond) { prec = 0; unit = "ns"; divisor = 1; } else if (nanoseconds < Millisecond) { prec = nanoseconds < 100 * Microsecond ? 3 : 0; unit = "µs"; divisor = Microsecond; } else { prec = nanoseconds < 100 * Millisecond ? 3 : 0; unit = "ms"; divisor = Millisecond; } const factor = 10 ** prec; const value = ~~((nanoseconds * factor) / divisor) / factor; return `${neg ? "-" : ""}${value}${unit}`; } else { let res = `${format(seconds % 60, nanoseconds, 9)}s`; const m = ~~(seconds / 60); if (m > 0) { res = `${(m % 60).toString(10)}m${res}`; const h = ~~(m / 60); if (h > 0) { res = `${h.toString(10)}h${res}`; } } return neg ? `-${res}` : res; } } /** * Reads the next duration component from the string. * If the component is not valid, an error is thrown. * * The return data is the value, the unit, and the index the next component starts at. * * For example: * - If the string is "5d20h31m43s", then the first call returns [5, "d", 2] * - If the string is "45m 1s", then the first call returns [45, "m", 4] * - If the string is "1.5µs", then the first call returns [1.5, "µs", 5] * * @param str The string to read from * @param idx The index to start reading from */ function readNextDurationComponent( str: string, idx: number, ): [number, string, number] { let i = idx; // first read the value let value = 0; let prec = -1; for (; i < str.length; i++) { const c = str.charAt(i); if (c >= "0" && c <= "9") { if (prec >= 0) { prec++; } value = value * 10 + (c.charCodeAt(0) - "0".charCodeAt(0)); } else if (c === ".") { prec = 0; } else { break; } } if (prec >= 0) { value /= 10 ** prec; } if (i === idx) { throw new Error( `Invalid duration string value at character ${i}: "${str}"`, ); } // now read the unit const unitStart = i; for (; i < str.length; i++) { const c = str.charAt(i); if ((c >= "0" && c <= "9") || c === " ") { break; } } if (i === unitStart) { throw new Error(`Invalid duration string unit at character ${i}: "${str}"`); } const unit = str.substring(unitStart, i); // skip whitespace while (i < str.length && str.charAt(i) === " ") { i++; } return [value, unit, i]; } /** * Parses a duration string into a duration. */ export function parseDuration(str: string): Duration { if (str.length === 0) { throw new Error("Invalid duration string: empty string"); } let seconds = 0; let nanoseconds = 0; let idx = 0; let neg = false; if (str.charAt(0) === "-") { neg = true; idx = 1; } while (idx < str.length) { let value: number; let unit: string; [value, unit, idx] = readNextDurationComponent(str, idx); switch (unit) { case "ns": nanoseconds += value; break; case "µs": nanoseconds += value * Microsecond; break; case "ms": nanoseconds += value * Millisecond; break; case "s": const ms = (value % 1) * 1000; if (ms > 0) { nanoseconds += Math.round(ms * Millisecond); } seconds += ~~value; break; case "m": seconds += value * 60; break; case "h": seconds += value * 60 * 60; break; default: throw new Error( `Invalid duration unit "${unit}" at character ${idx}: "${str}"`, ); } } if (neg) { seconds = -seconds; nanoseconds = -nanoseconds; } return [seconds, nanoseconds]; } export function durationToProto([seconds, nanoseconds]: Duration): DurationPB { return new DurationPB({ seconds: BigInt(seconds), nanos: nanoseconds, }); } function format(n: number, f: number, prec: number): string { let hasFractional = false; let out = ""; for (let i = 0; i < prec; i++) { const digit = f % 10; if (digit !== 0 || hasFractional) { hasFractional = true; out = digit.toString(10) + out; } f = ~~(f / 10); } if (hasFractional) { out = "." + out; } return n.toString(10) + out; }