Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Points and Spans

A point is a moment on one timeline, and a span is how far apart two points are. Each timeline has a point type of its own, and each kind of count a span type of its own, so a sum or a difference that mixes two timelines does not compile.

A Type per Timeline

PointTimelineOriginSpan
Timestampthe wall clock, in UTC1970-01-01T00:00:00ZTimedelta
TaiTimestampInternational Atomic Time1970-01-01T00:00:00 TAITimedelta
Uptimethe monotonic clocknear bootTimedelta
RawUptimethe monotonic clock at the hardware’s ratenear bootTimedelta
BootUptimethe boot clock, suspensions countedbootTimedelta
Tickstampa hardware counterthe hardware’s ownTickdelta

Every point and span is an i64 and nothing else: nanoseconds for all but the counter’s, whose unit is its tick. Each is Copy, ordered and hashable, and its Default is zero. A count of nanoseconds holds about 292 years either side of its origin, so a Timestamp holds 1677-09-21 to 2262-04-11. Choosing a Clock says which clock reads which point.

Arithmetic

A point minus a point is a span, a point plus or minus a span is a point, and spans add, subtract, negate, scale by an i64 and sum. Two points do not add.

fn a_point_minus_a_point_is_a_span() {
    use t2t::{Timedelta, Timestamp};

    let published = Timestamp::from_secs(1_700_000_000);
    let captured = published + Timedelta::from_micros(850);
    let latency = captured - published;

    assert_eq!(latency, Timedelta::from_micros(850), "a point minus a point is a span");
    assert_eq!(captured - latency, published, "and a point minus a span is a point");
    assert_eq!(latency * 3, Timedelta::from_micros(2_550), "a span scales by a count");
    assert!(latency < Timedelta::MILLISECOND, "and compares with another span");
}

Two Timelines Never Mix

Two timelines differ by an amount that only the machine knows, and that moves: a time service steps the wall clock and never the monotonic clock; TAI runs ahead of UTC by the offset the kernel holds; the raw monotonic clock drifts from the monotonic clock by whatever slewing a time service applies; the boot clock gains every suspension; and a counter’s origin is the hardware’s own. A difference taken across two of them would be a number with no meaning, so the types refuse it. A wall-clock instant minus an uptime does not compile:

//! Points of two timelines never mix: a wall-clock instant minus an uptime does not compile.

use t2t_core::{Timestamp, Uptime};

fn main() {
    let _span = Timestamp::from_secs(10) - Uptime::from_secs(10);
}
error[E0277]: cannot subtract `Uptime` from `Timestamp`
 --> tests/compile_fail/a_timestamp_minus_an_uptime.rs:6:42
  |
6 |     let _span = Timestamp::from_secs(10) - Uptime::from_secs(10);
  |                                          ^ no implementation for `Timestamp - Uptime`
  |
  = help: the trait `Sub<Uptime>` is not implemented for `Timestamp`

Nor does a counter’s reading moved by nanoseconds, since a tick’s length is the counter’s rate, which the type does not know:

//! Spans of two kinds never mix: a counter's reading moves by its ticks, not by nanoseconds.

use t2t_core::{Tickstamp, Timedelta};

fn main() {
    let _later = Tickstamp::from_ticks(10) + Timedelta::SECOND;
}
error[E0308]: mismatched types
 --> tests/compile_fail/a_tickstamp_plus_a_timedelta.rs:6:46
  |
6 |     let _later = Tickstamp::from_ticks(10) + Timedelta::SECOND;
  |                                              ^^^^^^^^^^^^^^^^^ expected `Tickdelta`, found `Timedelta`

Each of these is a fixture that t2t’s tests compile, and that must fail to compile. A program that does need to compare two timelines says how: a counter’s span crosses to nanoseconds through its rate, as The CPU Counter shows, and two clocks’ counts compare as plain i64s, as the TAI clock’s listing in Choosing a Clock does.

Units

Each nanosecond point has from_nanos, from_micros, from_millis and from_secs, and an as_* for each; a Timedelta adds from_mins, from_hours and from_days, and a constant for each unit from NANOSECOND to DAY, a day being 24 hours, with no calendar consulted. A counter’s point and span have from_ticks and as_ticks. A coarser unit’s constructor saturates where its count passes the range.

A Timedelta’s accessor counts whole units toward zero, as std’s Duration does, and subsec_nanos, subsec_micros and subsec_millis give the rest, signed as the span is. A point’s accessor counts the whole units since its origin, so a point before the origin rounds down, to the unit it falls in.

fn each_unit_has_a_constructor_and_an_accessor() {
    use t2t::{Timedelta, Timestamp, Uptime};

    assert_eq!(Timedelta::from_mins(90), Timedelta::HOUR + Timedelta::from_mins(30), "a span");
    assert_eq!(Uptime::from_secs(90).as_millis(), 90_000, "a point, counted in a finer unit");

    // A span counts whole units toward zero, as std's `Duration` does.
    assert_eq!(Timedelta::from_millis(1_500).as_secs(), 1, "forwards");
    assert_eq!(Timedelta::from_millis(-1_500).as_secs(), -1, "and backwards");
    assert_eq!(Timedelta::from_millis(-1_500).subsec_millis(), -500, "the rest has its sign");

    // A point counts the whole units since its origin, so one before it rounds down.
    assert_eq!(Timestamp::from_millis(-1_500).as_secs(), -2, "the second it falls in");
}

Saturation and the Checked Twins

Every operator saturates at the ends of the range, so none overflows or panics, and each has a checked_* twin that returns None where the operator would saturate:

OperatorChecked twin
point + spanchecked_add
point - spanchecked_sub
point - pointchecked_since
span + spanchecked_add
span - spanchecked_sub
span * countchecked_mul
-spanchecked_neg
span.abs()checked_abs
none: a span has no /checked_div

The operator suits code where the end of the range is out of reach, or where stopping there is right; the twin suits code to which reaching it is a bug. A span has no /: checked_div divides it, refusing zero parts, and MIN in -1.

fn an_operator_saturates_and_its_checked_twin_refuses() {
    use t2t::{Timedelta, Timestamp};

    let latest = Timestamp::MAX;
    assert_eq!(latest + Timedelta::SECOND, Timestamp::MAX, "the sum stops at the end");
    assert_eq!(latest.checked_add(Timedelta::SECOND), None, "where its twin refuses");

    let widest = Timestamp::MAX - Timestamp::MIN;
    assert_eq!(widest, Timedelta::MAX, "a span past its range stops at its end too");
    assert_eq!(Timestamp::MAX.checked_since(Timestamp::MIN), None, "or is refused");

    assert_eq!(Timedelta::from_secs(i64::MAX), Timedelta::MAX, "a constructor saturates");
    assert_eq!(-Timedelta::MIN, Timedelta::MAX, "and so does a negation");
    assert_eq!(Timedelta::MIN.checked_neg(), None, "which its twin refuses");

    // A span has no `/`; `checked_div` divides it, and refuses zero parts.
    let third = Timedelta::SECOND.checked_div(3);
    assert_eq!(third, Some(Timedelta::from_nanos(333_333_333)), "truncated toward zero");
    assert_eq!(Timedelta::SECOND.checked_div(0), None, "and refused for no parts");
}

A workspace that denies clippy’s arithmetic_side_effects lint names each point and span in its clippy configuration’s arithmetic-side-effects-allowed, since the lint cannot see that they saturate; t2t’s own clippy.toml shows the list.

Floor and Ceil

floor and ceil take a point or a span to the nearest multiple of a unit at or below it, or at or above it: the minute an event falls in, or the end of the bucket a histogram counts it in. They work on both sides of the origin, take the unit’s magnitude, and leave the value as it is for a zero unit.

fn floor_and_ceil_find_the_bucket_a_point_falls_in() {
    use t2t::{Timedelta, Timestamp};

    let event: Timestamp = "2026-09-16T07:45:35.123Z".parse().expect("an instant in UTC");
    let start = event.floor(Timedelta::MINUTE);
    let end = event.ceil(Timedelta::MINUTE);

    assert_eq!(format!("{start:.0}"), "2026-09-16T07:45:00Z", "the minute it falls in");
    assert_eq!(format!("{end:.0}"), "2026-09-16T07:46:00Z", "and the next");
    assert_eq!(start.floor(Timedelta::MINUTE), start, "a multiple stays where it is");
    assert_eq!(event.floor(Timedelta::ZERO), event, "and a zero unit leaves the point");

    let budget = Timedelta::from_micros(1_250);
    assert_eq!(budget.ceil(Timedelta::MILLISECOND), Timedelta::from_millis(2), "a span too");
}

Code Generic Over Points

TimePoint is what every point shares: its span type, Span, the operators between the two, and from_count and count, the point as its i64. Code generic over it works on every timeline, as the manual clocks and Timed::elapsed do, and a type of your own that wraps a point may implement it to join them.

fn code_generic_over_points_takes_every_timeline() {
    use t2t::{Tickdelta, Tickstamp, TimePoint, Timedelta, Uptime};

    /// How long ago `stamp` is, at `now`, on whichever timeline the two are on.
    fn age<P: TimePoint>(stamp: P, now: P) -> P::Span {
        now - stamp
    }

    let span = age(Uptime::from_secs(5), Uptime::from_secs(7));
    assert_eq!(span, Timedelta::from_secs(2), "a span of nanoseconds on the monotonic clock");
    let ticks = age(Tickstamp::from_ticks(100), Tickstamp::from_ticks(124));
    assert_eq!(ticks, Tickdelta::from_ticks(24), "and a span of ticks on a counter");
    assert_eq!(Uptime::from_count(span.as_nanos()), Uptime::from_secs(2), "from a count");
}