| ... | @@ -1417,10 +1417,23 @@ pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, case: Case, options | ... | @@ -1417,10 +1417,23 @@ pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, case: Case, options |
| 1417 | return fbs.pos; | 1417 | return fbs.pos; |
| 1418 | } | 1418 | } |
| 1419 | | 1419 | |
| 1420 | fn formatDuration(ns: u64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | 1420 | const FormatDurationData = struct { |
| | 1421 | ns: u64, |
| | 1422 | negative: bool = false, |
| | 1423 | }; |
| | 1424 | |
| | 1425 | fn formatDuration(data: FormatDurationData, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { |
| 1421 | _ = fmt; | 1426 | _ = fmt; |
| 1422 | _ = options; | 1427 | |
| 1423 | var ns_remaining = ns; | 1428 | // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24 |
| | 1429 | var buf: [24]u8 = undefined; |
| | 1430 | var fbs = std.io.fixedBufferStream(&buf); |
| | 1431 | var buf_writer = fbs.writer(); |
| | 1432 | if (data.negative) { |
| | 1433 | try buf_writer.writeByte('-'); |
| | 1434 | } |
| | 1435 | |
| | 1436 | var ns_remaining = data.ns; |
| 1424 | inline for (.{ | 1437 | inline for (.{ |
| 1425 | .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, | 1438 | .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' }, |
| 1426 | .{ .ns = std.time.ns_per_week, .sep = 'w' }, | 1439 | .{ .ns = std.time.ns_per_week, .sep = 'w' }, |
| ... | @@ -1430,10 +1443,11 @@ fn formatDuration(ns: u64, comptime fmt: []const u8, options: std.fmt.FormatOpti | ... | @@ -1430,10 +1443,11 @@ fn formatDuration(ns: u64, comptime fmt: []const u8, options: std.fmt.FormatOpti |
| 1430 | }) |unit| { | 1443 | }) |unit| { |
| 1431 | if (ns_remaining >= unit.ns) { | 1444 | if (ns_remaining >= unit.ns) { |
| 1432 | const units = ns_remaining / unit.ns; | 1445 | const units = ns_remaining / unit.ns; |
| 1433 | try formatInt(units, 10, .lower, .{}, writer); | 1446 | try formatInt(units, 10, .lower, .{}, buf_writer); |
| 1434 | try writer.writeByte(unit.sep); | 1447 | try buf_writer.writeByte(unit.sep); |
| 1435 | ns_remaining -= units * unit.ns; | 1448 | ns_remaining -= units * unit.ns; |
| 1436 | if (ns_remaining == 0) return; | 1449 | if (ns_remaining == 0) |
| | 1450 | return formatBuf(fbs.getWritten(), options, writer); |
| 1437 | } | 1451 | } |
| 1438 | } | 1452 | } |
| 1439 | | 1453 | |
| ... | @@ -1444,32 +1458,33 @@ fn formatDuration(ns: u64, comptime fmt: []const u8, options: std.fmt.FormatOpti | ... | @@ -1444,32 +1458,33 @@ fn formatDuration(ns: u64, comptime fmt: []const u8, options: std.fmt.FormatOpti |
| 1444 | }) |unit| { | 1458 | }) |unit| { |
| 1445 | const kunits = ns_remaining * 1000 / unit.ns; | 1459 | const kunits = ns_remaining * 1000 / unit.ns; |
| 1446 | if (kunits >= 1000) { | 1460 | if (kunits >= 1000) { |
| 1447 | try formatInt(kunits / 1000, 10, .lower, .{}, writer); | 1461 | try formatInt(kunits / 1000, 10, .lower, .{}, buf_writer); |
| 1448 | const frac = kunits % 1000; | 1462 | const frac = kunits % 1000; |
| 1449 | if (frac > 0) { | 1463 | if (frac > 0) { |
| 1450 | // Write up to 3 decimal places | 1464 | // Write up to 3 decimal places |
| 1451 | var buf = [_]u8{ '.', 0, 0, 0 }; | 1465 | var decimal_buf = [_]u8{ '.', 0, 0, 0 }; |
| 1452 | _ = formatIntBuf(buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 3 }); | 1466 | _ = formatIntBuf(decimal_buf[1..], frac, 10, .lower, .{ .fill = '0', .width = 3 }); |
| 1453 | var end: usize = 4; | 1467 | var end: usize = 4; |
| 1454 | while (end > 1) : (end -= 1) { | 1468 | while (end > 1) : (end -= 1) { |
| 1455 | if (buf[end - 1] != '0') break; | 1469 | if (decimal_buf[end - 1] != '0') break; |
| 1456 | } | 1470 | } |
| 1457 | try writer.writeAll(buf[0..end]); | 1471 | try buf_writer.writeAll(decimal_buf[0..end]); |
| 1458 | } | 1472 | } |
| 1459 | try writer.writeAll(unit.sep); | 1473 | try buf_writer.writeAll(unit.sep); |
| 1460 | return; | 1474 | return formatBuf(fbs.getWritten(), options, writer); |
| 1461 | } | 1475 | } |
| 1462 | } | 1476 | } |
| 1463 | | 1477 | |
| 1464 | try formatInt(ns_remaining, 10, .lower, .{}, writer); | 1478 | try formatInt(ns_remaining, 10, .lower, .{}, buf_writer); |
| 1465 | try writer.writeAll("ns"); | 1479 | try buf_writer.writeAll("ns"); |
| 1466 | return; | 1480 | return formatBuf(fbs.getWritten(), options, writer); |
| 1467 | } | 1481 | } |
| 1468 | | 1482 | |
| 1469 | /// Return a Formatter for number of nanoseconds according to its magnitude: | 1483 | /// Return a Formatter for number of nanoseconds according to its magnitude: |
| 1470 | /// [#y][#w][#d][#h][#m]#[.###][n|u|m]s | 1484 | /// [#y][#w][#d][#h][#m]#[.###][n|u|m]s |
| 1471 | pub fn fmtDuration(ns: u64) Formatter(formatDuration) { | 1485 | pub fn fmtDuration(ns: u64) Formatter(formatDuration) { |
| 1472 | return .{ .data = ns }; | 1486 | const data = FormatDurationData{ .ns = ns }; |
| | 1487 | return .{ .data = data }; |
| 1473 | } | 1488 | } |
| 1474 | | 1489 | |
| 1475 | test "fmtDuration" { | 1490 | test "fmtDuration" { |
| ... | @@ -1504,18 +1519,29 @@ test "fmtDuration" { | ... | @@ -1504,18 +1519,29 @@ test "fmtDuration" { |
| 1504 | .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, | 1519 | .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms }, |
| 1505 | .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, | 1520 | .{ .s = "1y1h1ms", .d = 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1 }, |
| 1506 | .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, | 1521 | .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, |
| | 1522 | .{ .s = "584y49w23h34m33.709s", .d = math.maxInt(u64) }, |
| 1507 | }) |tc| { | 1523 | }) |tc| { |
| 1508 | const slice = try bufPrint(&buf, "{}", .{fmtDuration(tc.d)}); | 1524 | const slice = try bufPrint(&buf, "{}", .{fmtDuration(tc.d)}); |
| 1509 | try std.testing.expectEqualStrings(tc.s, slice); | 1525 | try std.testing.expectEqualStrings(tc.s, slice); |
| 1510 | } | 1526 | } |
| | 1527 | |
| | 1528 | inline for (.{ |
| | 1529 | .{ .s = "=======0ns", .f = "{s:=>10}", .d = 0 }, |
| | 1530 | .{ .s = "1ns=======", .f = "{s:=<10}", .d = 1 }, |
| | 1531 | .{ .s = " 999ns ", .f = "{s:^10}", .d = std.time.ns_per_us - 1 }, |
| | 1532 | }) |tc| { |
| | 1533 | const slice = try bufPrint(&buf, tc.f, .{fmtDuration(tc.d)}); |
| | 1534 | try std.testing.expectEqualStrings(tc.s, slice); |
| | 1535 | } |
| 1511 | } | 1536 | } |
| 1512 | | 1537 | |
| 1513 | fn formatDurationSigned(ns: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { | 1538 | fn formatDurationSigned(ns: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { |
| 1514 | if (ns < 0) { | 1539 | if (ns < 0) { |
| 1515 | try writer.writeByte('-'); | 1540 | const data = FormatDurationData{ .ns = @intCast(u64, -ns), .negative = true }; |
| 1516 | try formatDuration(@intCast(u64, -ns), fmt, options, writer); | 1541 | try formatDuration(data, fmt, options, writer); |
| 1517 | } else { | 1542 | } else { |
| 1518 | try formatDuration(@intCast(u64, ns), fmt, options, writer); | 1543 | const data = FormatDurationData{ .ns = @intCast(u64, ns) }; |
| | 1544 | try formatDuration(data, fmt, options, writer); |
| 1519 | } | 1545 | } |
| 1520 | } | 1546 | } |
| 1521 | | 1547 | |
| ... | @@ -1585,10 +1611,22 @@ test "fmtDurationSigned" { | ... | @@ -1585,10 +1611,22 @@ test "fmtDurationSigned" { |
| 1585 | .{ .s = "-1y1h1ms", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1) }, | 1611 | .{ .s = "-1y1h1ms", .d = -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1) }, |
| 1586 | .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, | 1612 | .{ .s = "1y1m999ns", .d = 365 * std.time.ns_per_day + std.time.ns_per_min + 999 }, |
| 1587 | .{ .s = "-1y1m999ns", .d = -(365 * std.time.ns_per_day + std.time.ns_per_min + 999) }, | 1613 | .{ .s = "-1y1m999ns", .d = -(365 * std.time.ns_per_day + std.time.ns_per_min + 999) }, |
| | 1614 | .{ .s = "292y24w3d23h47m16.854s", .d = math.maxInt(i64) }, |
| | 1615 | .{ .s = "-292y24w3d23h47m16.854s", .d = math.minInt(i64) + 1 }, |
| 1588 | }) |tc| { | 1616 | }) |tc| { |
| 1589 | const slice = try bufPrint(&buf, "{}", .{fmtDurationSigned(tc.d)}); | 1617 | const slice = try bufPrint(&buf, "{}", .{fmtDurationSigned(tc.d)}); |
| 1590 | try std.testing.expectEqualStrings(tc.s, slice); | 1618 | try std.testing.expectEqualStrings(tc.s, slice); |
| 1591 | } | 1619 | } |
| | 1620 | |
| | 1621 | inline for (.{ |
| | 1622 | .{ .s = "=======0ns", .f = "{s:=>10}", .d = 0 }, |
| | 1623 | .{ .s = "1ns=======", .f = "{s:=<10}", .d = 1 }, |
| | 1624 | .{ .s = "-1ns======", .f = "{s:=<10}", .d = -(1) }, |
| | 1625 | .{ .s = " -999ns ", .f = "{s:^10}", .d = -(std.time.ns_per_us - 1) }, |
| | 1626 | }) |tc| { |
| | 1627 | const slice = try bufPrint(&buf, tc.f, .{fmtDurationSigned(tc.d)}); |
| | 1628 | try std.testing.expectEqualStrings(tc.s, slice); |
| | 1629 | } |
| 1592 | } | 1630 | } |
| 1593 | | 1631 | |
| 1594 | pub const ParseIntError = error{ | 1632 | pub const ParseIntError = error{ |