Time
You want to wait a bit, or you want to know how long something took, or you want the wall clock as seconds since 1970. That is the whole of std::time. Two clocks and a length. There is no calendar, no timezone, and no strftime. Those are a later layer, and pretending a Unix number is a date is how you keep a bad format forever.
use std::time;
time::instant $start = time::instant::now();
time::sleep(.millis(1));
echo $start->elapsed() >= time::duration::zero(); // 1instant is the monotonic clock. Subtract two of them and you get a duration. timestamp is the wall clock, a signed duration since 1970-01-01T00:00:00Z. Use the first one to measure. Use the second one to talk to the rest of the world.
Durations
A duration is how long something is. Factories, not suffixes: there is no 5s or 250ms. The leading-dot shorthand fills the type in from the destination, which is why sleep(.millis(50)) reads as a sentence.
use std::time;
time::duration $d = .millis(1500);
echo $d->as_secs(); // 1
echo $d->subsec_nanos(); // 500000000
echo $d->as_millis(); // 1500
std::time::sleep(.millis(1));secs, millis, micros, nanos, mins, hours, and zero. All take int64. Arithmetic and comparison are ordinary operators, operands by value (16 bytes, trivially copyable):
use std::time;
time::duration $a = .secs(2);
time::duration $b = .millis(500);
echo ($a + $b)->as_millis(); // 2500
echo ($a - $b)->as_millis(); // 1500
echo ($b * 4)->as_millis(); // 2000
echo ($a / 2)->as_millis(); // 1000
echo $a > $b; // 1* and / on the right are int64. Division truncates toward zero in whole nanoseconds. Both are only meaningful within about plus or minus 292 years, the same bound as as_nanos(). Past that the nanosecond count wraps, because it is an int64.
Negative durations
Here is the catch, and it is the whole representation paying off. A duration is two words, matching C's timespec: $secs and $nanos. $nanos is always in [0, 999999999]. $secs carries the sign. -1.5s is stored as {secs: -2, nanos: 500000000}, never as a negative nanosecond field.
So the accessors that look like they just read a field are floor-correct for negatives, and as_float_secs() / abs() are the ones you reach for when you wanted the signed magnitude:
use std::time;
time::duration $d = .millis(-1500);
echo $d->as_secs(); // -2, not -1
echo $d->subsec_nanos(); // 500000000
echo $d->as_millis(); // -1500
echo $d->as_float_secs(); // -1.500000
echo $d->abs()->as_millis(); // 1500
echo $d->is_negative(); // 1as_secs() of -1.5s is -2 because that is what floor means, and because it is also what timespec means. I would rather that be surprising once, in this paragraph, than have comparison grow a sign arm and nanosleep grow a conversion.
The constructor normalises. duration(0, 1500000000) is 1 second and 500000000 nanoseconds. duration(1, -1500000000) is -1s plus 500000000 ns. Writing it also deletes the implicit memberwise constructor, and $secs / $nanos are internal const, so nothing outside the type can skip the invariant by writing the fields.
"{$d}" is Go-style
str::from(duration) is spec-less on purpose. "{$d:>8}" fails by name. What you get is the same shape Go prints:
use std::time;
time::duration $z = .zero();
echo "{$z}"; // 0s
time::duration $ns = .nanos(100);
echo "{$ns}"; // 100ns
time::duration $us = .nanos(1500);
echo "{$us}"; // 1.5us
time::duration $ms = .millis(250);
echo "{$ms}"; // 250ms
time::duration $s = .secs(1) + .millis(500);
echo "{$s}"; // 1.5s
time::duration $m = .mins(1) + .secs(30);
echo "{$m}"; // 1m30s
time::duration $h = .hours(1) + .mins(2) + .secs(3) + .millis(500);
echo "{$h}"; // 1h2m3.5s
time::duration $h0 = .hours(1) + .secs(3);
echo "{$h0}"; // 1h0m3s
time::duration $neg = .millis(-1500);
echo "{$neg}"; // -1.5s
time::duration $long = .hours(26);
echo "{$long}"; // 26h0m0sZero is 0s. Below one second, one unit with a nonzero integer part (100ns, 1.5us, 250ms). From one second up, [{h}h][{m}m]{s}[.{frac}]s: hours unbounded, minutes printed whenever hours print or the minutes are nonzero, seconds always. Trailing zeros on the fraction are stripped.
There is no str::from(instant) and no str::from(timestamp). An instant's origin is opaque. A timestamp's human form is a calendar, which this module does not have.
instant
instant::now() is the monotonic clock. It never steps. NTP may slew it. Subtract two, or ask elapsed(), and you have a duration:
use std::time;
time::instant $t1 = time::instant::now();
time::instant $t2 = time::instant::now();
echo $t2 >= $t1; // 1
echo ($t2 - $t1) >= time::duration::zero(); // 1
echo $t1->elapsed() >= time::duration::zero(); // 1You can add a duration to an instant, or subtract one. Comparison is the duration's comparison of the raw values.
Do not benchmark against timestamp. The wall clock jumps when the user sets it, when NTP steps it, when a VM is restored. The number will look like a duration and be a lie. instant is the one that only goes forward.
The wall clock
timestamp is a signed duration since 1970-01-01T00:00:00Z. now() reads the realtime clock. from_unix_secs / from_unix_millis build one. unix_secs / unix_millis / unix_nanos read it back. unix_secs floors, so a time before 1970 rounds down.
use std::time;
time::timestamp $epoch = .from_unix_secs(0);
echo $epoch->unix_secs(); // 0
echo $epoch->unix_millis(); // 0
time::timestamp $t = .from_unix_millis(1700000000500);
echo $t->unix_secs(); // 1700000000
echo $t->unix_millis(); // 1700000000500
echo ($t + time::duration::secs(1)) - $t == time::duration::secs(1); // 1unix_nanos() wraps in 2262. Same int64 nanosecond bound as as_nanos(). If you are storing a deadline that far out, store seconds.
timestamp::now() is "what time is it". It is not "how long did this take".
Sleeping
std::time::sleep takes a duration. A non-positive duration returns immediately; a negative sleep is a no-op, matching Rust and Go. std::thread::sleep($ms) is the same call, in milliseconds, kept because it was already public.
use std::time;
time::sleep(.millis(1));
std::thread::sleep(1);EINTR is handled inside the call. You do not bind errno, you do not retry. A long sleep is not chopped into 500 ms pieces; that was usleep's portability limit, and usleep is gone.
Platform notes
Unix is clock_gettime and nanosleep. CLOCK_MONOTONIC rather than _RAW: slew-corrected, never steps. Darwin's monotonic id is 6, Linux's is 1.
Windows is QueryPerformanceCounter for the monotonic clock, GetSystemTimePreciseAsFileTime for the wall clock, and Sleep for waiting. The rest of the module still calls c_clock_gettime and c_nanosleep; the shims keep those names so a clock read has no #[if:] in it. Windows rounds a sleep up to the next millisecond and chunks at the uint32 boundary, so a short sleep is never shorter than you asked.
None of this needs a platform conditional in your code.