Working with Other Crates
A Timestamp and a Timedelta convert to and from std’s time types and those
of chrono, jiff and time. Where every t2t value fits the other type, the
conversion is a From; the way back is a TryFrom, refusing with
OutOfRangeError what t2t’s range does not hold. The
other points, a TaiTimestamp and those counted from boot or by a counter, have
no counterpart in these crates, and no conversion.
| Other type | Feature | Into it | From it |
|---|---|---|---|
core::time::Duration | none | a Timedelta forwards | a Duration up to Timedelta::MAX |
std::time::SystemTime | std | every Timestamp | a moment from 1677-09-21 to 2262-04-11 |
chrono 0.4’s DateTime<Utc> | chrono-04 | every Timestamp | a DateTime in any zone, within the range |
chrono 0.4’s TimeDelta | chrono-04 | every Timedelta | a TimeDelta within the range |
jiff 0.2’s Timestamp | jiff-02 | every Timestamp | a Timestamp within the range |
jiff 0.2’s SignedDuration | jiff-02 | every Timedelta | a SignedDuration within the range |
time 0.3’s OffsetDateTime | time-03 | every Timestamp, in UTC | an OffsetDateTime at any offset, within the range |
time 0.3’s Duration | time-03 | every Timedelta | a Duration within the range |
A zone or an offset converts as the instant it names, read in UTC. Each feature
is named for the version of the crate it converts with, and with std on, it
turns on that crate’s own std too. The listings are t2t-core’s tests, which
have each crate as a dependency, so they name the crate t2t_core.
std
A Timedelta crosses to std’s Duration and back by TryFrom both ways: a
Duration never runs backwards, so a negative span is refused, and a
Timedelta holds about 292 years, so a longer Duration is refused. The
conversion is core’s, and needs no feature.
fn a_span_crosses_to_a_duration_where_it_runs_forwards() {
use core::time::Duration;
use t2t_core::{OutOfRangeError, Timedelta};
let span = Timedelta::from_millis(5);
assert_eq!(Duration::try_from(span), Ok(Duration::from_millis(5)), "a span forwards");
assert_eq!(Timedelta::try_from(Duration::from_millis(5)), Ok(span), "and back");
assert_eq!(Duration::try_from(-span), Err(OutOfRangeError), "no duration runs backwards");
assert_eq!(Timedelta::try_from(Duration::MAX), Err(OutOfRangeError), "nor past 292 years");
}
A Timestamp becomes a SystemTime with From, since a SystemTime on 64-bit
Linux and macOS reaches past both ends of a timestamp’s range, and comes back
with TryFrom, refusing a moment outside it. These need std, on 64-bit Linux
or macOS.
fn a_timestamp_crosses_to_a_system_time_and_back_within_its_range() {
use core::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
use t2t_core::{OutOfRangeError, Timestamp};
let instant = Timestamp::from_secs(1_700_000_000);
let system_time = SystemTime::from(instant);
let since_epoch = system_time.duration_since(UNIX_EPOCH).ok();
assert_eq!(since_epoch, Some(Duration::from_secs(1_700_000_000)), "the same moment");
assert_eq!(Timestamp::try_from(system_time), Ok(instant), "and back");
let far_future = UNIX_EPOCH
.checked_add(Duration::from_secs(1 << 40))
.expect("a 64-bit SystemTime reaches 2^40 seconds past 1970");
assert_eq!(Timestamp::try_from(far_future), Err(OutOfRangeError), "past 2262 is refused");
}
std’s Instant has no conversion: it is opaque, and std gives no way to read
its count or build one from a count. On Linux and macOS it reads
CLOCK_MONOTONIC and CLOCK_UPTIME_RAW, the clocks MonotonicClock reads, so
code that would convert an Instant reads MonotonicClock in its place.
chrono
chrono’s years reach far past 1677 and 2262, so a DateTime<Utc> holds every
timestamp, and a TimeDelta every span; a DateTime in another zone comes back
as the instant it names.
fn a_timestamp_and_a_span_cross_to_chrono() {
use chrono::{DateTime, FixedOffset, TimeDelta, Utc};
use t2t_core::{OutOfRangeError, Timedelta, Timestamp};
let instant = Timestamp::from_nanos(1_789_544_735_123_456_789);
let theirs = DateTime::<Utc>::from(instant);
assert_eq!(theirs.timestamp_nanos_opt(), Some(instant.as_nanos()), "the same instant");
let paris = theirs.with_timezone(&FixedOffset::east_opt(7_200).expect("two hours east"));
assert_eq!(Timestamp::try_from(paris), Ok(instant), "in any zone, read as its instant");
let latest = DateTime::<Utc>::MAX_UTC;
assert_eq!(Timestamp::try_from(latest), Err(OutOfRangeError), "past 2262 is refused");
let span = Timedelta::from_millis(-250);
assert_eq!(TimeDelta::from(span), TimeDelta::milliseconds(-250), "every span crosses");
assert_eq!(Timedelta::try_from(TimeDelta::MAX), Err(OutOfRangeError), "but not every back");
}
jiff
jiff’s Timestamp reaches the years -9999 to 9999, so it holds every timestamp,
and its SignedDuration every span.
fn a_timestamp_and_a_span_cross_to_jiff() {
use jiff::{SignedDuration, Timestamp as JiffTimestamp};
use t2t_core::{OutOfRangeError, Timedelta, Timestamp};
let instant = Timestamp::from_nanos(1_789_544_735_123_456_789);
let theirs = JiffTimestamp::from(instant);
assert_eq!(theirs.to_string(), "2026-09-16T07:45:35.123456789Z", "the same instant");
assert_eq!(Timestamp::try_from(theirs), Ok(instant), "and back");
let latest = JiffTimestamp::MAX;
assert_eq!(Timestamp::try_from(latest), Err(OutOfRangeError), "past 2262 is refused");
let span = Timedelta::from_millis(-250);
assert_eq!(SignedDuration::from(span), SignedDuration::from_millis(-250), "a span");
let longest = SignedDuration::MAX;
assert_eq!(Timedelta::try_from(longest), Err(OutOfRangeError), "and one too long for it");
}
time
time’s OffsetDateTime takes every timestamp, at the UTC offset; one at another
offset comes back as the instant it names. Its Duration holds every span.
fn a_timestamp_and_a_span_cross_to_time() {
use t2t_core::{OutOfRangeError, Timedelta, Timestamp};
use time::{Duration, OffsetDateTime, UtcOffset};
let instant = Timestamp::from_nanos(1_789_544_735_123_456_789);
let theirs = OffsetDateTime::from(instant);
assert_eq!(theirs.offset(), UtcOffset::UTC, "in UTC");
let east = theirs.to_offset(UtcOffset::from_hms(2, 0, 0).expect("two hours east"));
assert_eq!(Timestamp::try_from(east), Ok(instant), "at any offset, read as its instant");
let span = Timedelta::from_millis(-250);
assert_eq!(Duration::from(span), Duration::milliseconds(-250), "a span");
assert_eq!(Timedelta::try_from(Duration::MAX), Err(OutOfRangeError), "and one too long");
}