Commit ebcc6f166c

Andrew Kelley <andrew@ziglang.org>
2025-10-09 06:56:20
std.Io: bring back Timestamp but also keep Clock.Timestamp
this feels better
1 parent 89412fd
lib/compiler/build_runner.zig
@@ -556,7 +556,7 @@ pub fn main() !void {
     try run.thread_pool.init(thread_pool_options);
     defer run.thread_pool.deinit();
 
-    const now = Io.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
+    const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
 
     run.web_server = if (webui_listen) |listen_address| ws: {
         if (builtin.single_threaded) unreachable; // `fatal` above
lib/std/Build/Cache.zig
@@ -21,7 +21,7 @@ io: Io,
 manifest_dir: fs.Dir,
 hash: HashHelper = .{},
 /// This value is accessed from multiple threads, protected by mutex.
-recent_problematic_timestamp: i128 = 0,
+recent_problematic_timestamp: Io.Timestamp = .zero,
 mutex: std.Thread.Mutex = .{},
 
 /// A set of strings such as the zig library directory or project source root, which
@@ -155,7 +155,7 @@ pub const File = struct {
     pub const Stat = struct {
         inode: fs.File.INode,
         size: u64,
-        mtime: i128,
+        mtime: Io.Timestamp,
 
         pub fn fromFs(fs_stat: fs.File.Stat) Stat {
             return .{
@@ -330,7 +330,7 @@ pub const Manifest = struct {
     diagnostic: Diagnostic = .none,
     /// Keeps track of the last time we performed a file system write to observe
     /// what time the file system thinks it is, according to its own granularity.
-    recent_problematic_timestamp: i128 = 0,
+    recent_problematic_timestamp: Io.Timestamp = .zero,
 
     pub const Diagnostic = union(enum) {
         none,
@@ -728,7 +728,7 @@ pub const Manifest = struct {
                     file.stat = .{
                         .size = stat_size,
                         .inode = stat_inode,
-                        .mtime = stat_mtime,
+                        .mtime = .{ .nanoseconds = stat_mtime },
                     };
                     file.bin_digest = file_bin_digest;
                     break :f file;
@@ -747,7 +747,7 @@ pub const Manifest = struct {
                         .stat = .{
                             .size = stat_size,
                             .inode = stat_inode,
-                            .mtime = stat_mtime,
+                            .mtime = .{ .nanoseconds = stat_mtime },
                         },
                         .bin_digest = file_bin_digest,
                     };
@@ -780,7 +780,7 @@ pub const Manifest = struct {
                 return error.CacheCheckFailed;
             };
             const size_match = actual_stat.size == cache_hash_file.stat.size;
-            const mtime_match = actual_stat.mtime == cache_hash_file.stat.mtime;
+            const mtime_match = actual_stat.mtime.nanoseconds == cache_hash_file.stat.mtime.nanoseconds;
             const inode_match = actual_stat.inode == cache_hash_file.stat.inode;
 
             if (!size_match or !mtime_match or !inode_match) {
@@ -792,7 +792,7 @@ pub const Manifest = struct {
 
                 if (self.isProblematicTimestamp(cache_hash_file.stat.mtime)) {
                     // The actual file has an unreliable timestamp, force it to be hashed
-                    cache_hash_file.stat.mtime = 0;
+                    cache_hash_file.stat.mtime = .zero;
                     cache_hash_file.stat.inode = 0;
                 }
 
@@ -848,10 +848,10 @@ pub const Manifest = struct {
         }
     }
 
-    fn isProblematicTimestamp(man: *Manifest, file_time: i128) bool {
+    fn isProblematicTimestamp(man: *Manifest, timestamp: Io.Timestamp) bool {
         // If the file_time is prior to the most recent problematic timestamp
         // then we don't need to access the filesystem.
-        if (file_time < man.recent_problematic_timestamp)
+        if (timestamp.nanoseconds < man.recent_problematic_timestamp.nanoseconds)
             return false;
 
         // Next we will check the globally shared Cache timestamp, which is accessed
@@ -861,7 +861,7 @@ pub const Manifest = struct {
 
         // Save the global one to our local one to avoid locking next time.
         man.recent_problematic_timestamp = man.cache.recent_problematic_timestamp;
-        if (file_time < man.recent_problematic_timestamp)
+        if (timestamp.nanoseconds < man.recent_problematic_timestamp.nanoseconds)
             return false;
 
         // This flag prevents multiple filesystem writes for the same hit() call.
@@ -879,7 +879,7 @@ pub const Manifest = struct {
             man.cache.recent_problematic_timestamp = man.recent_problematic_timestamp;
         }
 
-        return file_time >= man.recent_problematic_timestamp;
+        return timestamp.nanoseconds >= man.recent_problematic_timestamp.nanoseconds;
     }
 
     fn populateFileHash(self: *Manifest, ch_file: *File) !void {
@@ -904,7 +904,7 @@ pub const Manifest = struct {
 
         if (self.isProblematicTimestamp(ch_file.stat.mtime)) {
             // The actual file has an unreliable timestamp, force it to be hashed
-            ch_file.stat.mtime = 0;
+            ch_file.stat.mtime = .zero;
             ch_file.stat.inode = 0;
         }
 
@@ -1040,7 +1040,7 @@ pub const Manifest = struct {
 
         if (self.isProblematicTimestamp(new_file.stat.mtime)) {
             // The actual file has an unreliable timestamp, force it to be hashed
-            new_file.stat.mtime = 0;
+            new_file.stat.mtime = .zero;
             new_file.stat.inode = 0;
         }
 
lib/std/Build/WebServer.zig
@@ -11,7 +11,7 @@ tcp_server: ?net.Server,
 serve_thread: ?std.Thread,
 
 /// Uses `Io.Clock.awake`.
-base_timestamp: i96,
+base_timestamp: Io.Timestamp,
 /// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`.
 step_names_trailing: []u8,
 
@@ -43,6 +43,8 @@ runner_request: ?RunnerRequest,
 /// on a fixed interval of this many milliseconds.
 const default_update_interval_ms = 500;
 
+pub const base_clock: Io.Clock = .awake;
+
 /// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`.
 pub fn notifyUpdate(ws: *WebServer) void {
     _ = ws.update_id.rmw(.Add, 1, .release);
@@ -58,13 +60,13 @@ pub const Options = struct {
     root_prog_node: std.Progress.Node,
     watch: bool,
     listen_address: net.IpAddress,
-    base_timestamp: Io.Timestamp,
+    base_timestamp: Io.Clock.Timestamp,
 };
 pub fn init(opts: Options) WebServer {
     // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent`
     // instead of threads, so that the web server can function in single-threaded builds.
     comptime assert(!builtin.single_threaded);
-    assert(opts.base_timestamp.clock == .awake);
+    assert(opts.base_timestamp.clock == base_clock);
 
     const all_steps = opts.all_steps;
 
@@ -109,7 +111,7 @@ pub fn init(opts: Options) WebServer {
         .tcp_server = null,
         .serve_thread = null,
 
-        .base_timestamp = opts.base_timestamp.nanoseconds,
+        .base_timestamp = opts.base_timestamp.raw,
         .step_names_trailing = step_names_trailing,
 
         .step_status_bits = step_status_bits,
@@ -248,9 +250,8 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
 
 pub fn now(s: *const WebServer) i64 {
     const io = s.graph.io;
-    const base: Io.Timestamp = .{ .nanoseconds = s.base_timestamp, .clock = .awake };
-    const ts = Io.Timestamp.now(io, base.clock) catch base;
-    return @intCast(base.durationTo(ts).toNanoseconds());
+    const ts = base_clock.now(io) catch s.base_timestamp;
+    return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds());
 }
 
 fn accept(ws: *WebServer, stream: net.Stream) void {
@@ -519,7 +520,7 @@ pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []cons
             if (cached_cwd_path == null) cached_cwd_path = try std.process.getCwdAlloc(gpa);
             break :cwd cached_cwd_path.?;
         };
-        try archiver.writeFile(path.sub_path, &file_reader, stat.mtime);
+        try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds()));
     }
 
     // intentionally not calling `archiver.finishPedantically`
lib/std/fs/File.zig
@@ -637,23 +637,23 @@ pub const UpdateTimesError = posix.FutimensError || windows.SetFileTimeError;
 pub fn updateTimes(
     self: File,
     /// access timestamp in nanoseconds
-    atime: i128,
+    atime: Io.Timestamp,
     /// last modification timestamp in nanoseconds
-    mtime: i128,
+    mtime: Io.Timestamp,
 ) UpdateTimesError!void {
     if (builtin.os.tag == .windows) {
-        const atime_ft = windows.nanoSecondsToFileTime(atime);
-        const mtime_ft = windows.nanoSecondsToFileTime(mtime);
+        const atime_ft = windows.nanoSecondsToFileTime(atime.nanoseconds);
+        const mtime_ft = windows.nanoSecondsToFileTime(mtime.nanoseconds);
         return windows.SetFileTime(self.handle, null, &atime_ft, &mtime_ft);
     }
     const times = [2]posix.timespec{
         posix.timespec{
-            .sec = math.cast(isize, @divFloor(atime, std.time.ns_per_s)) orelse maxInt(isize),
-            .nsec = math.cast(isize, @mod(atime, std.time.ns_per_s)) orelse maxInt(isize),
+            .sec = math.cast(isize, @divFloor(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
+            .nsec = math.cast(isize, @mod(atime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
         },
         posix.timespec{
-            .sec = math.cast(isize, @divFloor(mtime, std.time.ns_per_s)) orelse maxInt(isize),
-            .nsec = math.cast(isize, @mod(mtime, std.time.ns_per_s)) orelse maxInt(isize),
+            .sec = math.cast(isize, @divFloor(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
+            .nsec = math.cast(isize, @mod(mtime.nanoseconds, std.time.ns_per_s)) orelse maxInt(isize),
         },
     };
     try posix.futimens(self.handle, &times);
lib/std/http/Client.zig
@@ -320,7 +320,7 @@ pub const Connection = struct {
             const tls: *Tls = @ptrCast(base);
             var random_buffer: [176]u8 = undefined;
             std.crypto.random.bytes(&random_buffer);
-            const now_ts = if (Io.Timestamp.now(io, .real)) |ts| ts.toSeconds() else |_| return error.TlsInitializationFailed;
+            const now_ts = if (Io.Clock.real.now(io)) |ts| ts.toSeconds() else |_| return error.TlsInitializationFailed;
             tls.* = .{
                 .connection = .{
                     .client = client,
lib/std/Io/net/HostName.zig
@@ -79,7 +79,7 @@ pub const LookupError = error{
     NameServerFailure,
     /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf".
     DetectingNetworkConfigurationFailed,
-} || Io.Timestamp.Error || IpAddress.BindError || Io.Cancelable;
+} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable;
 
 pub const LookupResult = struct {
     /// How many `LookupOptions.addresses_buffer` elements are populated.
@@ -294,13 +294,14 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
 
     // boot clock is chosen because time the computer is suspended should count
     // against time spent waiting for external messages to arrive.
-    var now_ts = try Io.Timestamp.now(io, .boot);
+    const clock: Io.Clock = .boot;
+    var now_ts = try clock.now(io);
     const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
     const attempt_duration: Io.Duration = .{
         .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts,
     };
 
-    send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try Io.Timestamp.now(io, .boot)) {
+    send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(io)) {
         const max_messages = queries_buffer.len * ResolvConf.max_nameservers;
         {
             var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
@@ -319,7 +320,10 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio
             _ = io.vtable.netSend(io.userdata, socket.handle, message_buffer[0..message_i], .{});
         }
 
-        const timeout: Io.Timeout = .{ .deadline = now_ts.addDuration(attempt_duration) };
+        const timeout: Io.Timeout = .{ .deadline = .{
+            .raw = now_ts.addDuration(attempt_duration),
+            .clock = clock,
+        } };
 
         while (true) {
             var message_buffer: [max_messages]Io.net.IncomingMessage = undefined;
lib/std/Io/Dir.zig
@@ -85,7 +85,7 @@ pub fn updateFile(
         };
 
         if (src_stat.size == dest_stat.size and
-            src_stat.mtime == dest_stat.mtime and
+            src_stat.mtime.nanoseconds == dest_stat.mtime.nanoseconds and
             actual_mode == dest_stat.mode)
         {
             return .fresh;
lib/std/Io/File.zig
@@ -45,16 +45,12 @@ pub const Stat = struct {
     /// This is available on POSIX systems and is always 0 otherwise.
     mode: Mode,
     kind: Kind,
-
     /// Last access time in nanoseconds, relative to UTC 1970-01-01.
-    /// TODO change this to Io.Timestamp except don't waste storage on clock
-    atime: i128,
+    atime: Io.Timestamp,
     /// Last modification time in nanoseconds, relative to UTC 1970-01-01.
-    /// TODO change this to Io.Timestamp except don't waste storage on clock
-    mtime: i128,
+    mtime: Io.Timestamp,
     /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
-    /// TODO change this to Io.Timestamp except don't waste storage on clock
-    ctime: i128,
+    ctime: Io.Timestamp,
 };
 
 pub fn stdout() File {
lib/std/Io/Threaded.zig
@@ -1147,26 +1147,26 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi
     };
 }
 
-fn nowPosix(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error!i96 {
+fn nowPosix(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
     _ = pool;
     const clock_id: posix.clockid_t = clockToPosix(clock);
     var tp: posix.timespec = undefined;
     switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) {
-        .SUCCESS => return @intCast(@as(i128, tp.sec) * std.time.ns_per_s + tp.nsec),
+        .SUCCESS => return timestampFromPosix(&tp),
         .INVAL => return error.UnsupportedClock,
         else => |err| return posix.unexpectedErrno(err),
     }
 }
 
-fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error!i96 {
+fn nowWindows(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
     _ = pool;
     switch (clock) {
         .realtime => {
             // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
             // and uses the NTFS/Windows epoch, which is 1601-01-01.
-            return @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100;
+            return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 };
         },
         .monotonic, .uptime => {
             // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
@@ -1178,7 +1178,7 @@ fn nowWindows(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Err
     }
 }
 
-fn nowWasi(userdata: ?*anyopaque, clock: Io.Timestamp.Clock) Io.Timestamp.Error!i96 {
+fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
     _ = pool;
     var ns: std.os.wasi.timestamp_t = undefined;
@@ -1196,13 +1196,10 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
     });
     const deadline_nanoseconds: i96 = switch (timeout) {
         .none => std.math.maxInt(i96),
-        .duration => |d| d.duration.nanoseconds,
-        .deadline => |deadline| deadline.nanoseconds,
-    };
-    var timespec: posix.timespec = .{
-        .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)),
-        .nsec = @intCast(@mod(deadline_nanoseconds, std.time.ns_per_s)),
+        .duration => |duration| duration.raw.nanoseconds,
+        .deadline => |deadline| deadline.raw.nanoseconds,
     };
+    var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds);
     while (true) {
         try pool.checkCancel();
         switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) {
@@ -1267,11 +1264,7 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
             .sec = std.math.maxInt(sec_type),
             .nsec = std.math.maxInt(nsec_type),
         };
-        const ns = d.duration.nanoseconds;
-        break :t .{
-            .sec = @intCast(@divFloor(ns, std.time.ns_per_s)),
-            .nsec = @intCast(@mod(ns, std.time.ns_per_s)),
-        };
+        break :t timestampToPosix(d.duration.nanoseconds);
     };
     while (true) {
         try pool.checkCancel();
@@ -1879,8 +1872,8 @@ fn netReceive(
                 const max_poll_ms = std.math.maxInt(u31);
                 const timeout_ms: u31 = if (deadline) |d| t: {
                     const duration = d.durationFromNow(pool.io()) catch |err| return .{ err, message_i };
-                    if (duration.nanoseconds <= 0) return .{ error.Timeout, message_i };
-                    break :t @intCast(@min(max_poll_ms, duration.toMilliseconds()));
+                    if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i };
+                    break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
                 } else max_poll_ms;
 
                 const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms);
@@ -2160,7 +2153,7 @@ fn recoverableOsBugDetected() void {
     if (builtin.mode == .Debug) unreachable;
 }
 
-fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t {
+fn clockToPosix(clock: Io.Clock) posix.clockid_t {
     return switch (clock) {
         .real => posix.CLOCK.REALTIME,
         .awake => switch (builtin.os.tag) {
@@ -2176,7 +2169,7 @@ fn clockToPosix(clock: Io.Timestamp.Clock) posix.clockid_t {
     };
 }
 
-fn clockToWasi(clock: Io.Timestamp.Clock) std.os.wasi.clockid_t {
+fn clockToWasi(clock: Io.Clock) std.os.wasi.clockid_t {
     return switch (clock) {
         .realtime => .REALTIME,
         .awake => .MONOTONIC,
@@ -2204,9 +2197,9 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.File.Stat {
             std.os.linux.S.IFSOCK => .unix_domain_socket,
             else => .unknown,
         },
-        .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
-        .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
-        .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
+        .atime = .{ .nanoseconds = @intCast(@as(i128, atime.sec) * std.time.ns_per_s + atime.nsec) },
+        .mtime = .{ .nanoseconds = @intCast(@as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec) },
+        .ctime = .{ .nanoseconds = @intCast(@as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec) },
     };
 }
 
@@ -2238,9 +2231,9 @@ fn statFromPosix(st: *const std.posix.Stat) Io.File.Stat {
 
             break :k .unknown;
         },
-        .atime = @as(i128, atime.sec) * std.time.ns_per_s + atime.nsec,
-        .mtime = @as(i128, mtime.sec) * std.time.ns_per_s + mtime.nsec,
-        .ctime = @as(i128, ctime.sec) * std.time.ns_per_s + ctime.nsec,
+        .atime = timestampFromPosix(&atime),
+        .mtime = timestampFromPosix(&mtime),
+        .ctime = timestampFromPosix(&ctime),
     };
 }
 
@@ -2263,3 +2256,14 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) Io.File.Stat {
         .ctime = st.ctim,
     };
 }
+
+fn timestampFromPosix(timespec: *const std.posix.timespec) Io.Timestamp {
+    return .{ .nanoseconds = @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec) };
+}
+
+fn timestampToPosix(nanoseconds: i96) std.posix.timespec {
+    return .{
+        .sec = @intCast(@divFloor(nanoseconds, std.time.ns_per_s)),
+        .nsec = @intCast(@mod(nanoseconds, std.time.ns_per_s)),
+    };
+}
lib/std/tar/Writer.zig
@@ -18,7 +18,6 @@ pub const Options = struct {
 
 underlying_writer: *Io.Writer,
 prefix: []const u8 = "",
-mtime_now: u64 = 0,
 
 const Error = error{
     WriteFailed,
@@ -44,10 +43,12 @@ pub fn writeFile(
     w: *Writer,
     sub_path: []const u8,
     file_reader: *Io.File.Reader,
-    stat_mtime: i128,
+    /// If you want to match the file format's expectations, it wants number of
+    /// seconds since POSIX epoch. Zero is also a great option here to make
+    /// generated tarballs more reproducible.
+    mtime: u64,
 ) WriteFileError!void {
     const size = try file_reader.getSize();
-    const mtime: u64 = @intCast(@divFloor(stat_mtime, std.time.ns_per_s));
 
     var header: Header = .{};
     try w.setPath(&header, sub_path);
@@ -238,7 +239,6 @@ pub const Header = extern struct {
     }
 
     // Integer number of seconds since January 1, 1970, 00:00 Coordinated Universal Time.
-    // mtime == 0 will use current time
     pub fn setMtime(w: *Header, mtime: u64) error{OctalOverflow}!void {
         try octal(&w.mtime, mtime);
     }
lib/std/Io.zig
@@ -669,7 +669,7 @@ pub const VTable = struct {
     fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void,
     fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void,
 
-    now: *const fn (?*anyopaque, Timestamp.Clock) Timestamp.Error!i96,
+    now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
     sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
 
     listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
@@ -705,118 +705,178 @@ pub const UnexpectedError = error{
 pub const Dir = @import("Io/Dir.zig");
 pub const File = @import("Io/File.zig");
 
-pub const Timestamp = struct {
-    nanoseconds: i96,
-    clock: Clock,
-
-    pub const Clock = enum {
-        /// A settable system-wide clock that measures real (i.e. wall-clock)
-        /// time. This clock is affected by discontinuous jumps in the system
-        /// time (e.g., if the system administrator manually changes the
-        /// clock), and by frequency adjust‐ ments performed by NTP and similar
-        /// applications.
-        ///
-        /// This clock normally counts the number of seconds since 1970-01-01
-        /// 00:00:00 Coordinated Universal Time (UTC) except that it ignores
-        /// leap seconds; near a leap second it is typically adjusted by NTP to
-        /// stay roughly in sync with UTC.
-        ///
-        /// The epoch is implementation-defined. For example NTFS/Windows uses
-        /// 1601-01-01.
-        real,
-        /// A nonsettable system-wide clock that represents time since some
-        /// unspecified point in the past.
-        ///
-        /// Monotonic: Guarantees that the time returned by consecutive calls
-        /// will not go backwards, but successive calls may return identical
-        /// (not-increased) time values.
-        ///
-        /// Not affected by discontinuous jumps in the system time (e.g., if
-        /// the system administrator manually changes the clock), but may be
-        /// affected by frequency adjustments.
-        ///
-        /// This clock expresses intent to **exclude time that the system is
-        /// suspended**. However, implementations may be unable to satisify
-        /// this, and may include that time.
-        ///
-        /// * On Linux, corresponds `CLOCK_MONOTONIC`.
-        /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`.
-        awake,
-        /// Identical to `awake` except it expresses intent to **include time
-        /// that the system is suspended**, however, due to limitations it may
-        /// behave identically to `awake`.
-        ///
-        /// * On Linux, corresponds `CLOCK_BOOTTIME`.
-        /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`.
-        boot,
-        /// Tracks the amount of CPU in user or kernel mode used by the calling
-        /// process.
-        cpu_process,
-        /// Tracks the amount of CPU in user or kernel mode used by the calling
-        /// thread.
-        cpu_thread,
-    };
-
-    pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
-        assert(from.clock == to.clock);
-        return .{ .nanoseconds = to.nanoseconds - from.nanoseconds };
-    }
-
-    pub fn addDuration(from: Timestamp, duration: Duration) Timestamp {
-        return .{
-            .nanoseconds = from.nanoseconds + duration.nanoseconds,
-            .clock = from.clock,
-        };
-    }
+pub const Clock = enum {
+    /// A settable system-wide clock that measures real (i.e. wall-clock)
+    /// time. This clock is affected by discontinuous jumps in the system
+    /// time (e.g., if the system administrator manually changes the
+    /// clock), and by frequency adjust‐ ments performed by NTP and similar
+    /// applications.
+    ///
+    /// This clock normally counts the number of seconds since 1970-01-01
+    /// 00:00:00 Coordinated Universal Time (UTC) except that it ignores
+    /// leap seconds; near a leap second it is typically adjusted by NTP to
+    /// stay roughly in sync with UTC.
+    ///
+    /// The epoch is implementation-defined. For example NTFS/Windows uses
+    /// 1601-01-01.
+    real,
+    /// A nonsettable system-wide clock that represents time since some
+    /// unspecified point in the past.
+    ///
+    /// Monotonic: Guarantees that the time returned by consecutive calls
+    /// will not go backwards, but successive calls may return identical
+    /// (not-increased) time values.
+    ///
+    /// Not affected by discontinuous jumps in the system time (e.g., if
+    /// the system administrator manually changes the clock), but may be
+    /// affected by frequency adjustments.
+    ///
+    /// This clock expresses intent to **exclude time that the system is
+    /// suspended**. However, implementations may be unable to satisify
+    /// this, and may include that time.
+    ///
+    /// * On Linux, corresponds `CLOCK_MONOTONIC`.
+    /// * On macOS, corresponds to `CLOCK_UPTIME_RAW`.
+    awake,
+    /// Identical to `awake` except it expresses intent to **include time
+    /// that the system is suspended**, however, due to limitations it may
+    /// behave identically to `awake`.
+    ///
+    /// * On Linux, corresponds `CLOCK_BOOTTIME`.
+    /// * On macOS, corresponds to `CLOCK_MONOTONIC_RAW`.
+    boot,
+    /// Tracks the amount of CPU in user or kernel mode used by the calling
+    /// process.
+    cpu_process,
+    /// Tracks the amount of CPU in user or kernel mode used by the calling
+    /// thread.
+    cpu_thread,
 
     pub const Error = error{UnsupportedClock} || UnexpectedError;
 
     /// This function is not cancelable because first of all it does not block,
     /// but more importantly, the cancelation logic itself may want to check
     /// the time.
-    pub fn now(io: Io, clock: Clock) Error!Timestamp {
-        return .{
-            .nanoseconds = try io.vtable.now(io.userdata, clock),
-            .clock = clock,
-        };
+    pub fn now(clock: Clock, io: Io) Error!Io.Timestamp {
+        return io.vtable.now(io.userdata, clock);
     }
 
-    pub fn fromNow(io: Io, clock: Clock, duration: Duration) Error!Timestamp {
-        const now_ts = try now(io, clock);
-        return addDuration(now_ts, duration);
-    }
+    pub const Timestamp = struct {
+        raw: Io.Timestamp,
+        clock: Clock,
+
+        /// This function is not cancelable because first of all it does not block,
+        /// but more importantly, the cancelation logic itself may want to check
+        /// the time.
+        pub fn now(io: Io, clock: Clock) Error!Clock.Timestamp {
+            return .{
+                .raw = try io.vtable.now(io.userdata, clock),
+                .clock = clock,
+            };
+        }
 
-    pub fn untilNow(timestamp: Timestamp, io: Io) Error!Duration {
-        const now_ts = try Timestamp.now(io, timestamp.clock);
-        return timestamp.durationTo(now_ts);
-    }
+        pub fn wait(t: Clock.Timestamp, io: Io) SleepError!void {
+            return io.vtable.sleep(io.userdata, .{ .deadline = t });
+        }
+
+        pub fn durationTo(from: Clock.Timestamp, to: Clock.Timestamp) Clock.Duration {
+            assert(from.clock == to.clock);
+            return .{
+                .raw = from.raw.durationTo(to.raw),
+                .clock = from.clock,
+            };
+        }
 
-    pub fn durationFromNow(timestamp: Timestamp, io: Io) Error!Duration {
-        const now_ts = try now(io, timestamp.clock);
-        return now_ts.durationTo(timestamp);
+        pub fn addDuration(from: Clock.Timestamp, duration: Clock.Duration) Clock.Timestamp {
+            assert(from.clock == duration.clock);
+            return .{
+                .raw = from.raw.addDuration(duration.raw),
+                .clock = from.clock,
+            };
+        }
+
+        pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {
+            return .{
+                .clock = duration.clock,
+                .raw = (try duration.clock.now(io)).addDuration(duration.raw),
+            };
+        }
+
+        pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
+            const now_ts = try Clock.Timestamp.now(io, timestamp.clock);
+            return timestamp.durationTo(now_ts);
+        }
+
+        pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
+            const now_ts = try timestamp.clock.now(io);
+            return .{
+                .clock = timestamp.clock,
+                .raw = now_ts.durationTo(timestamp.raw),
+            };
+        }
+
+        pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Error!Clock.Timestamp {
+            if (t.clock == clock) return t;
+            const now_old = try t.clock.now(io);
+            const now_new = try clock.now(io);
+            const duration = now_old.durationTo(t);
+            return .{
+                .clock = clock,
+                .raw = now_new.addDuration(duration),
+            };
+        }
+
+        pub fn compare(lhs: Clock.Timestamp, op: std.math.CompareOperator, rhs: Clock.Timestamp) bool {
+            assert(lhs.clock == rhs.clock);
+            return std.math.compare(lhs.raw.nanoseconds, op, rhs.raw.nanoseconds);
+        }
+    };
+
+    pub const Duration = struct {
+        raw: Io.Duration,
+        clock: Clock,
+
+        pub fn sleep(duration: Clock.Duration, io: Io) SleepError!void {
+            return io.vtable.sleep(io.userdata, .{ .duration = duration });
+        }
+    };
+};
+
+pub const Timestamp = struct {
+    nanoseconds: i96,
+
+    pub const zero: Timestamp = .{ .nanoseconds = 0 };
+
+    pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
+        return .{ .nanoseconds = to.nanoseconds - from.nanoseconds };
     }
 
-    pub fn toClock(t: Timestamp, io: Io, clock: Clock) Error!Timestamp {
-        if (t.clock == clock) return t;
-        const now_old = try now(io, t.clock);
-        const now_new = try now(io, clock);
-        const duration = now_old.durationTo(t);
-        return now_new.addDuration(duration);
+    pub fn addDuration(from: Timestamp, duration: Duration) Timestamp {
+        return .{ .nanoseconds = from.nanoseconds + duration.nanoseconds };
     }
 
-    pub fn compare(lhs: Timestamp, op: std.math.CompareOperator, rhs: Timestamp) bool {
-        assert(lhs.clock == rhs.clock);
-        return std.math.compare(lhs.nanoseconds, op, rhs.nanoseconds);
+    pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp {
+        return .{ .nanoseconds = t.nanoseconds, .clock = clock };
     }
 
     pub fn toSeconds(t: Timestamp) i64 {
         return @intCast(@divTrunc(t.nanoseconds, std.time.ns_per_s));
     }
+
+    pub fn formatNumber(t: Timestamp, w: *std.Io.Writer, n: std.fmt.Number) std.Io.Writer.Error!void {
+        return w.printInt(t.nanoseconds, n.mode.base() orelse 10, n.case, .{
+            .precision = n.precision,
+            .width = n.width,
+            .alignment = n.alignment,
+            .fill = n.fill,
+        });
+    }
 };
 
 pub const Duration = struct {
     nanoseconds: i96,
 
+    pub const zero: Duration = .{ .nanoseconds = 0 };
     pub const max: Duration = .{ .nanoseconds = std.math.maxInt(i96) };
 
     pub fn fromNanoseconds(x: i96) Duration {
@@ -842,38 +902,29 @@ pub const Duration = struct {
     pub fn toNanoseconds(d: Duration) i96 {
         return d.nanoseconds;
     }
-
-    pub fn sleep(duration: Duration, io: Io) SleepError!void {
-        return io.vtable.sleep(io.userdata, .{ .duration = .{ .duration = duration, .clock = .awake } });
-    }
 };
 
 /// Declares under what conditions an operation should return `error.Timeout`.
 pub const Timeout = union(enum) {
     none,
-    duration: ClockAndDuration,
-    deadline: Timestamp,
+    duration: Clock.Duration,
+    deadline: Clock.Timestamp,
 
     pub const Error = error{ Timeout, UnsupportedClock };
 
-    pub const ClockAndDuration = struct {
-        clock: Timestamp.Clock,
-        duration: Duration,
-    };
-
-    pub fn toDeadline(t: Timeout, io: Io) Timestamp.Error!?Timestamp {
+    pub fn toDeadline(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp {
         return switch (t) {
             .none => null,
-            .duration => |d| try .fromNow(io, d.clock, d.duration),
+            .duration => |d| try .fromNow(io, d),
             .deadline => |d| d,
         };
     }
 
-    pub fn toDurationFromNow(t: Timeout, io: Io) Timestamp.Error!?ClockAndDuration {
+    pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration {
         return switch (t) {
             .none => null,
             .duration => |d| d,
-            .deadline => |d| .{ .clock = d.clock, .duration = try d.durationFromNow(io) },
+            .deadline => |d| try d.durationFromNow(io),
         };
     }