# Design notes: win-time-sys (Tier 1)
Current canonical decisions for this crate. See [CHECKLIST.md](CHECKLIST.md) for what is planned,
and [design-sessions/DESIGN-SESSION-2026-10-07-clocks.md](design-sessions/DESIGN-SESSION-2026-10-07-clocks.md)
for the conversation that shaped it.
Repository-level decisions this crate sits under: the `win-` prefix and what `-sys` promises, both in
the workspace [DESIGN-NOTES.md](../../DESIGN-NOTES.md#new-crates-take-the-win-prefix). The crate was
proposed on 2026-10-07 when durable-ioring settled on interrupt time as its one time base
([DI-D-37](../durable-ioring/DESIGN-NOTES.md#di-d-37)) and, forbidding `unsafe`, needed a safe way to
read it; the only wrapper in the workspace was `windows-threadpool-sys`' crate-private one.
## Decision index
| ID | Decision |
|---|---|
| <a id="wt-d-1"></a>WT-D-1 | **The crate is safe Rust over the Windows clocks, so the workspace has one place a time base is read.** The engineer's decision, 2026-10-07, choosing a new crate over making `windows-threadpool-sys`' reader public or putting clocks in `win-sync-sys`: neither is about time. Its terms are borrowed from C++'s `std::chrono` for naming only -- a clock is something that reads "now", a time point is a value on a clock's timeline -- and it adds nothing Rust's own time types already provide: no `clock_cast` (the engineer: "No new clock_cast over what Rust already provides"). |
| <a id="wt-d-2"></a>WT-D-2 | **Two layers: a timeline is the data type -- its tick and what zero means -- and a clock is a getter that reads now on one timeline.** The engineer's model, 2026-10-07: "the data type is the ratio and maybe what time 0 means. The fact that there are different 'getters' is orthogonal." In Rust: a `Timeline` trait giving ticks per second; `TimePoint<T>`, a `Copy`, ordered tick count on timeline `T` that can be recorded; `Ticks<T>`, the difference of two points on one timeline, convertible to `core::time::Duration`; and a `Clock` trait whose `now()` returns a `TimePoint` of its timeline. Points on different timelines do not compare, because their types differ -- which is the rule that matters, since two getters can share a timeline and two timelines can share a unit without sharing a zero. The period is a function rather than a compile-time ratio as in chrono, because QPC's is known only at run time; it is fixed from boot. The type names are the assistant's, open to the engineer. |
| <a id="wt-d-3"></a>WT-D-3 | **Every getter is offered, on four timelines.** The engineer: "Ideally all of these would be options." **`FileTime`** -- 100 ns ticks since 1601-01-01 UTC -- read by `GetSystemTimeAsFileTime` (as of the last system tick) and `GetSystemTimePreciseAsFileTime` (documented as under one microsecond); the engineer confirmed both return one format and differ only in the getter. **Interrupt time** -- 100 ns ticks since boot, sleep included -- read by `QueryInterruptTime` and `QueryInterruptTimePrecise`. **Unbiased interrupt time** -- the same, sleep and hibernation excluded -- read by `QueryUnbiasedInterruptTime` and `QueryUnbiasedInterruptTimePrecise`; its own timeline, because after one sleep its values and interrupt time's no longer describe the same instants. **Performance** -- QPC's ticks at `QueryPerformanceFrequency` -- read by `QueryPerformanceCounter`. Plain interrupt time is the workspace's default ([DI-D-37](../durable-ioring/DESIGN-NOTES.md#di-d-37)): the engineer cares most about the variant that does not enter the kernel and costs least. Which getters cost what is not stated here -- it depends on the CPU, the hypervisor and how QPC is backed -- and is what `WT-1.6`'s probe measures on the machine it runs on. |
| <a id="wt-d-4"></a>WT-D-4 | **The performance clock reads QPC's raw ticks; it is not a newtype over `std::time::Instant`.** The engineer's decision, 2026-10-07, reversing their own first suggestion: `Instant` is opaque by design -- its value cannot be read out -- so a point taken from it could never be recorded, sent to another process, or reconciled after the fact, which is what these points are for. |
| <a id="wt-d-5"></a>WT-D-5 | **Time points carry no time zone or UTC offset.** `FileTime` is UTC; the others have no calendar meaning. Zones are a presentation concern that the ecosystem's date-time crates own (`time`'s `OffsetDateTime`, `chrono`'s `DateTime<Tz>`, `jiff`'s `Zoned`), as Windows itself keeps `FILETIME` in UTC and converts for display. |
| <a id="wt-d-6"></a>WT-D-6 | **Deferred -- the cycle counter (`rdtscp`), blocked on having no mapping to real-world time.** The engineer's decision, 2026-10-07: "the fact that there's no way to map it to real world times makes it very hard to integrate into the Rust time model". Windows publishes no timestamp-counter frequency, so a cycle count cannot become a `Duration` without calibrating against another clock, an estimate. Its absence is also why every timeline's period is known (WT-D-2). Parked as `WT-inf.1` with that blocker. |
| <a id="wt-d-7"></a>WT-D-7 | **The layers' shape as built: `Clock::now` takes `&self`; a point is a `u64` tick count, a distance an `i64` one; conversions to and from `Duration` truncate towards zero; operators panic on overflow and `checked_` methods do not.** Decided by the assistant while implementing `WT-1.2`, to be confirmed by the engineer under `WT-3.1`. **`&self`, not a static `now()` as in chrono:** a clock may then carry state -- a test's fake clock without a `static`, or a value read once and kept, such as QPC's frequency -- and code generic over a clock is handed one. **`u64` points:** every timeline here counts upwards from its zero, and `FILETIME`, interrupt time and QPC all fit. **`i64` distances:** the later point may be on either side, so a distance is signed; `Duration` cannot be, so `to_duration` refuses a backwards distance and `abs_duration` gives its length either way. **`ticks_per_second` returns `NonZeroU64`,** so a conversion's division cannot be by zero. **Truncation towards zero** when a tick does not divide a nanosecond, or a nanosecond a tick, as `Duration`'s own integer conversions do. **Panicking operators beside `checked_` methods,** as `Duration` offers. |
| <a id="wt-d-8"></a>WT-D-8 | **The cost probe is an example in this crate, `clock_costs`, and it reports what it observed without comparing.** Decided by the assistant while implementing `WT-1.6`, to be confirmed by the engineer under `WT-3.2`. **Placement:** the probe exists so a consumer can choose a getter on their own hardware ([WT-D-3](#wt-d-3)), and an example ships with the published crate; `windows-platform-probes` is not published, so a consumer could not run it from there. **What it reports:** for every clock, and `Instant` and `SystemTime` for reference, the least, median and most cost per read over batches of consecutive reads, and the smallest step seen between readings that differ. No ranking and no verdict, and no figure from it is quoted in the crate's documentation: a measurement on one machine is a statement about that machine. Its arithmetic -- the summary and the step -- has unit tests; the measurement itself is the machine's. |
| <a id="wt-d-9"></a>WT-D-9 | **`Steady`, a marker trait over `Clock`, is a clock's promise that its readings never decrease. The four interrupt clocks and `PerformanceClock` make it; the two system clocks do not, because setting the time moves them backwards. A mock clock makes it by implementing the trait, and must then keep it.** The assistant's proposal, ratified by the engineer on 2026-10-08 ("the contract is what you think it should be"), so that durable-ioring's core can take a mock clock that acts contractually ([DI-D-38](../durable-ioring/DESIGN-NOTES.md#di-d-38)). `Clock` alone promises nothing about direction, so a bound of `Clock<Timeline = InterruptTime>` would admit a mock that runs backwards. A trait rather than chrono's `is_steady` constant, so code that relies on the promise says so in its bounds, and a clock that does not make it fails to compile there. It promises direction only: not a rate, not freshness, and not that two readings differ -- interrupt time's plain getters repeat a value for a whole system tick. See also the working position below on mock clocks and timed waits. |
## Working position: mock clocks and timed waits (not a decision)
The engineer, 2026-10-08, asked how far a mock clock must agree with time observed elsewhere. Some
time facts are not contractually important and may slide; others are. Code should almost never use a
timestamp to decide anything. But code may use a **timed wait** -- a wait with a timeout, resumed
until the whole amount has accumulated, to simulate a sleep -- and where it does, the clock's
accounting must be rational enough for that loop to work. That does not mean the clock shows
wall-clock time. It means the clock has affordances to collude with the wait, so a mock can portion
the wait out: code that "thinks" it waits 30 minutes can be made to see 30 minutes pass in 5 seconds,
the time base advancing correspondingly and the event signalled at the right moment, with the code
none the wiser.
No shape is settled. The engineer stated it conditionally ("if such a situation were to arise"), and
what a clock must offer depends on the wait it colludes with, which does not exist in the workspace
yet: designing the affordance now would invent the wait's shape too. Parked as
[WT-inf.2](CHECKLIST.md), gated on the first timed wait that accounts time through a clock.