From fc10c9c4ce40570d0abeda764f1bd76b3e79af61 Mon Sep 17 00:00:00 2001 From: Jonathan Knezek Date: Mon, 11 Jan 2021 18:15:56 -0600 Subject: [PATCH] Add std.fmt.formatDuration and std.fmt.duration (#7297) `formatDuration` works on a writer, and `duration` wraps a u64 to allow pleasant injection into format strings. --- lib/std/fmt.zig | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index d8b81eb264..8d18044964 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1119,6 +1119,103 @@ pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, op return fbs.pos; } +/// Formats a number of nanoseconds according to its magnitude: +/// +/// - #ns +/// - [#y][#w][#d][#h][#m]#[.###][u|m]s +pub fn formatDuration(ns: u64, writer: anytype) !void { + var ns_remaining = ns; + inline for (.{ + .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, + .{ .ns = std.time.ns_per_week, .sep = 'w' }, + .{ .ns = std.time.ns_per_day, .sep = 'd' }, + .{ .ns = std.time.ns_per_hour, .sep = 'h' }, + .{ .ns = std.time.ns_per_min, .sep = 'm' }, + }) |unit| { + if (ns_remaining >= unit.ns) { + const units = ns_remaining / unit.ns; + try formatInt(units, 10, false, .{}, writer); + try writer.writeByte(unit.sep); + ns_remaining -= units * unit.ns; + if (ns_remaining == 0) return; + } + } + + inline for (.{ + .{ .ns = std.time.ns_per_s, .sep = "s" }, + .{ .ns = std.time.ns_per_ms, .sep = "ms" }, + .{ .ns = std.time.ns_per_us, .sep = "us" }, + }) |unit| { + const kunits = ns_remaining * 1000 / unit.ns; + if (kunits >= 1000) { + try formatInt(kunits / 1000, 10, false, .{}, writer); + if (kunits > 1000) { + // Write up to 3 decimal places + const frac = kunits % 1000; + var buf = [_]u8{ '.', 0, 0, 0 }; + _ = formatIntBuf(buf[1..], frac, 10, false, .{ .fill = '0', .width = 3 }); + var end: usize = 4; + while (end > 1) : (end -= 1) { + if (buf[end - 1] != '0') break; + } + try writer.writeAll(buf[0..end]); + } + try writer.writeAll(unit.sep); + return; + } + } + + try formatInt(ns, 10, false, .{}, writer); + try writer.writeAll("ns"); + return; +} + +test "formatDuration" { + var buf: [24]u8 = undefined; + inline for (.{ + .{ .s = "0ns", .d = 0 }, + .{ .s = "1ns", .d = 1 }, + .{ .s = "999ns", .d = std.time.ns_per_us - 1 }, + .{ .s = "1us", .d = std.time.ns_per_us }, + .{ .s = "1.45us", .d = 1450 }, + .{ .s = "1.5us", .d = 3 * std.time.ns_per_us / 2 }, + .{ .s = "999.999us", .d = std.time.ns_per_ms - 1 }, + .{ .s = "1ms", .d = std.time.ns_per_ms + 1 }, + .{ .s = "1.5ms", .d = 3 * std.time.ns_per_ms / 2 }, + .{ .s = "999.999ms", .d = std.time.ns_per_s - 1 }, + .{ .s = "1s", .d = std.time.ns_per_s }, + .{ .s = "59.999s", .d = std.time.ns_per_min - 1 }, + .{ .s = "1m", .d = std.time.ns_per_min }, + .{ .s = "1h", .d = std.time.ns_per_hour }, + .{ .s = "1d", .d = std.time.ns_per_day }, + .{ .s = "1w", .d = std.time.ns_per_week }, + .{ .s = "1y", .d = 365 * std.time.ns_per_day }, + .{ .s = "1y52w23h59m59.999s", .d = 730 * std.time.ns_per_day - 1 }, // 365d = 52w1d + .{ .s = "1y1h1.001s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms }, + .{ .s = "1y1h1s", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us }, + .{ .s = "1y1h999.999us", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1 }, + .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, + .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, + }) |tc| { + const slice = try bufPrint(&buf, "{}", .{duration(tc.d)}); + std.testing.expectEqualStrings(tc.s, slice); + } +} + +/// Wraps a `u64` to format with `formatDuration`. +const Duration = struct { + ns: u64, + + pub fn format(self: Duration, comptime fmt: []const u8, options: FormatOptions, writer: anytype) !void { + return formatDuration(self.ns, writer); + } +}; + +/// Formats a number of nanoseconds according to its magnitude. See `formatDuration`. +pub fn duration(ns: u64) Duration { + return Duration{ .ns = ns }; +} + pub const ParseIntError = error{ /// The result cannot fit in the type specified Overflow,