Instant, SystemTime and Duration

Rust keeps two kinds of time apart. Instant is a moment the program cannot name, useful only for measuring intervals. SystemTime is the wall clock, which can jump when the machine synchronises with a server. Duration is the length between two moments, and it is what every timeout and sleep takes.

This lesson works on durations, measures a sleep, reads the wall clock without printing a number that changes, times a loop and ends with the compiler error for a negative duration.

Duration

A duration is a whole number of seconds and a number of nanoseconds, and it has constructors per unit, arithmetic with other durations and accessors that do not lose the fraction. Dividing by two keeps the total.

The program builds a second and a half, halves it and prints the seconds and the milliseconds of each. The comparison at the end is true because the half is smaller.

use std::time::Duration; fn main() { let timeout = Duration::from_millis(1500); let half = timeout / 2; println!("{} {}", timeout.as_secs(), timeout.subsec_millis()); println!("{} {}", half.as_millis(), timeout > half); }

rustc --edition 2024 duration.rs
./duration

1 500
750 true

as_secs drops the fraction and subsec_millis is the fraction, which is why the two numbers are printed together. as_millis converts the whole duration, and it is the accessor most log lines want. The atomics lesson uses a duration in its stop flag.

Instant

Instant::now takes a reading and elapsed returns the duration since then. The type is monotonic on every platform Rust supports, so it cannot go backwards when the clock is adjusted, which is why it is the type for measurement.

The program sleeps for twenty milliseconds and checks that the measured interval is at least that long. A sleep can overshoot and never undershoots.

use std::thread; use std::time::{Duration, Instant}; fn main() { let start = Instant::now(); thread::sleep(Duration::from_millis(20)); let elapsed = start.elapsed(); println!("{}", elapsed >= Duration::from_millis(20)); }

rustc --edition 2024 instant.rs
./instant

true

Printing the duration itself would print a number that changes on every run, which is why the comparison is printed instead. A benchmark repeats the work and reports the numbers, which is the shape a later lesson on measuring performance takes.

Arithmetic on a duration can overflow, and the checked and saturating forms are the ones to use when the value comes from a configuration file. Instant also has checked_add and checked_duration_since, which return an Option instead of panicking. A log line writes the wall clock, and a measurement writes the monotonic difference; mixing the two is how a report ends up with a negative duration.

SystemTime

SystemTime::now is the wall clock, and the only portable reference point is UNIX_EPOCH. The difference is a duration, and it can fail if the clock is set before 1970, which is why the call returns a Result.

The program asks how far the clock is from the epoch and checks two wide bounds: after 2001 and before 2286. The numbers are true for every machine this century.

use std::time::{SystemTime, UNIX_EPOCH}; fn main() { let now = SystemTime::now(); let since_epoch = now.duration_since(UNIX_EPOCH).unwrap(); println!("{}", since_epoch.as_secs() > 1_000_000_000); println!("{}", since_epoch.as_secs() < 10_000_000_000); }

rustc --edition 2024 systemtime.rs
./systemtime

true
true

A timestamp is the number of seconds since the epoch, and the next lesson turns one into a date. A program that stores this number writes a UTC moment, and a program that compares two of them compares moments in time, not readings of a monotonic clock.

Timing real work

The useful pattern is a start reading, the work, and an elapsed comparison that a test can assert. The work below adds a hundred thousand numbers, and the assertion is only that some time passed.

use std::time::{Duration, Instant}; fn main() { let start = Instant::now(); let mut total = 0u64; for value in 1..=100_000 { total += value; } let elapsed = start.elapsed(); println!("{} {}", total, elapsed > Duration::ZERO); }

rustc --edition 2024 measure.rs
./measure

5000050000 true

The sum is the closed form for a hundred thousand, and the boolean says the clock moved during the loop. A loop this short is dominated by the cost of the first iteration, which is why a benchmark repeats it.

A test that asserts on a duration allows for a slow machine: it checks a bound rather than a number, such as the sleep took at least twenty milliseconds and the loop finished in less than a second. A benchmark that reports a number instead takes the median of several runs, because one reading on a busy machine says little.

The same bound appears in a timeout test, where the assertion is that the call returned before the deadline rather than at a particular instant.

A negative duration

A duration is unsigned: seconds and nanoseconds are both whole numbers, and there is no negative value. A literal minus one is therefore an error on the argument, and the compiler suggests the largest value instead.

The program builds a duration of minus one second. The compiler reports E0600 at the sign.

use std::time::Duration; fn main() { let duration = Duration::from_secs(-1); println!("{duration:?}"); }

rustc --edition 2024 secs_err.rs

error[E0600]: cannot apply unary operator `-` to type `u64`
--> secs_err.rs:4:40
|
4 | let duration = Duration::from_secs(-1);
| ^^ cannot apply unary operator `-`
|
= note: unsigned values cannot be negated
help: you may have meant the maximum value of `u64`
|
4 - let duration = Duration::from_secs(-1);
4 + let duration = Duration::from_secs(u64::MAX);
| ~~~~~~~~

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0600`.

The help is the wrong fix for this program and the compiler cannot know that. To go back in time the code subtracts a duration from a moment with checked_sub, which returns None instead of wrapping. The next lesson turns the seconds into a calendar date.

Exercises

  1. Measure the time a thousand pushes into a vector take and print whether it is under a second.
  2. Convert a duration to hours, minutes and seconds with integer arithmetic.
  3. Compare Instant and SystemTime after a sleep and explain which reading is safe to subtract.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. Which type measures an interval?

    Answer

    Duration

    An interval is a length of time, which is what Duration stores.

  2. Why is Instant the type for measurement?

    Answer

    It is monotonic and cannot go backwards

    A wall clock can be adjusted, while a monotonic reading only moves forward.

  3. What does SystemTime::now return on its own?

    Answer

    A moment without a date attached

    The type is an opaque moment, and the timestamp comes from a difference against the epoch.

  4. Can a Duration be negative?

    Answer

    No, both of its fields are unsigned

    A negative length is not representable, so subtraction that would go below zero saturates or fails.

  5. What does this program print? use std::time::Duration; fn main() { let value = Duration::from_millis(2500); println!("{} {}", value.as_secs(), value.subsec_millis()); }

    Answer

    2 500

    Two whole seconds and five hundred milliseconds remain.

  6. What does this program print? use std::time::Duration; fn main() { let a = Duration::from_secs(2); let b = Duration::from_millis(500); let sum = a + b; println!("{}", sum.as_millis()); }

    Answer

    2500

    The two durations add to two and a half seconds, which is 2500 ms.

  7. What does this program print? use std::time::Duration; fn main() { let value = Duration::from_millis(1500); let doubled = value * 2; println!("{}", doubled.as_secs()); }

    Answer

    3

    Three seconds exactly, and as_secs drops nothing.

  8. What does this program print? use std::time::Duration; fn main() { let a = Duration::from_secs(1); let b = Duration::from_millis(999); println!("{} {}", a > b, b < a); }

    Answer

    true true

    A second is longer than 999 milliseconds, so both comparisons hold.

  9. What is the first line of the compiler output for this program? use std::time::Duration; fn main() { let duration = Duration::from_secs(-1); println!("{duration:?}"); }

    Answer

    error[E0600]: cannot apply unary operator - to type u64

    The argument is unsigned, and the sign cannot be applied to it.

  10. What is the first line of the compiler output for this program? use std::time::Duration; fn main() { let start = Duration::from_secs(1); let later = start + 5; println!("{later:?}"); }

    Answer

    error[E0308]: mismatched types

    The right side must be a Duration, and an integer literal is not one.

The code on this page was built with rustc 1.96.1 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev

Banner image