//! Centralized wall-clock access. //! //! Every wall-clock read in the server goes through this module so dev //! tooling (gated by ZDS_DEV_TOOLS) can shift time deterministically via a //! process-wide atomic offset. With a zero offset the values are identical //! to reading the realtime clock directly. const std = @import("std"); var offset_seconds = std.atomic.Value(i64).init(0); fn realtime() std.posix.timespec { var ts: std.posix.timespec = undefined; return switch (std.posix.errno(std.posix.system.clock_gettime(.REALTIME, &ts))) { .SUCCESS => ts, else => std.posix.timespec{ .sec = 0, .nsec = 0 }, }; } /// Current unix time in seconds with the dev offset applied. pub fn now() i64 { return nowSpec().sec; } /// Current unix time as a timespec. The dev offset shifts whole seconds; /// the sub-second fraction comes from the realtime clock. pub fn nowSpec() std.posix.timespec { const ts = realtime(); return .{ .sec = ts.sec + offset_seconds.load(.acquire), .nsec = ts.nsec }; } /// Current unix time in milliseconds with the dev offset applied. pub fn nowMillis() i64 { const ts = nowSpec(); return ts.sec * 1000 + @divTrunc(ts.nsec, 1_000_000); } /// Current unix time in microseconds with the dev offset applied. pub fn nowMicros() i64 { const ts = nowSpec(); return ts.sec * 1_000_000 + @divTrunc(ts.nsec, 1_000); } /// The currently applied dev offset in seconds. pub fn offset() i64 { return offset_seconds.load(.acquire); } /// Adds `delta` seconds to the dev offset. pub fn addOffsetSeconds(delta: i64) void { _ = offset_seconds.fetchAdd(delta, .acq_rel); } /// Sets the absolute dev offset in seconds. pub fn setOffsetSeconds(value: i64) void { offset_seconds.store(value, .release); } test "offset shifts the reported clock" { const before = now(); setOffsetSeconds(3600); defer setOffsetSeconds(0); try std.testing.expectEqual(@as(i64, 3600), offset()); const shifted = now(); try std.testing.expect(shifted - before >= 3599); addOffsetSeconds(-60); try std.testing.expectEqual(@as(i64, 3540), offset()); }