Finetime: Accurate, flexible, and efficient time keeping
finetime is a Rust library for accurate, flexible, and efficient timekeeping, designed for applications where precision and performance are critical.
- Accurate: Supports exact arithmetic with attosecond-level precision over arbitrary time ranges, without sacrificing correctness or performance.
- Flexible: Built on Rust generics,
finetimeallows durations and time points to be expressed as integers or floats of any native Rust bitwidth, in any SI time unit, and using any time scale. - Efficient: Represents time values as tick counts since an epoch, enabling compact storage and fast processing without conversion overhead.
- Verified: Key correctness properties have been formally proven using the
Kanimodel checker, ensuring a high degree of reliability. - Portable: The
finetimelibrary is fullyno_std, such that it may be used even in bare metal environments.
With this fine degree of control and precision, finetime is suitable for all types of applications, from nanoseconds in embedded systems to femtoseconds in scientific computing, or picoseconds for precise orbit determination.
Getting started
finetime requires the cargo build system for the Rust programming language to be present on your system. The library may be added as dependency for your Rust project by running cargo add finetime. Afterwards, it may be used directly by importing finetime into your Rust source code.
Expressing time points
In finetime, time points are always bound to a specific timekeeping standard, indicated as TimeScale. One such example is Coordinated Universal Time (UTC). Time points may be constructed directly from some given datetime in the historic calendar:
use ;
let epoch = from_datetime.unwrap;
Note that constructing time points from datetimes may fail, because the given arguments do not form a valid time-of-day, or because the given date did not occur in the historic calendar: finetime makes this explicit. Users must acknowledge this possibility by unwrapping the returned Result before being able to use the created UtcTime.
A wide variety of time scales may be encountered in the context of precise timekeeping.
finetime provides implementations for the most prevalent time scales: UTC, TAI, Unix time, terrestrial time (TT), GPS time (GPST), and most other GNSS time scales.
Where possible, times can be converted between time scales using the into_time_scale() function.
use ;
let epoch_utc = from_datetime.unwrap;
let epoch_tai = from_datetime.unwrap;
let epoch_unix = from_datetime.unwrap;
let epoch_gps = from_datetime.unwrap;
let epoch_galileo = from_datetime.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
If a desired time scale is not present, users may provide their own by implementing the TimeScale trait.
There is also support for subsecond datetime values, and conversion into more fine-grained TimePoint types to support higher-fidelity time types.
use ;
let epoch_utc = from_datetime.unwrap;
let epoch_tt = from_subsecond_datetime.unwrap;
assert_eq!;
These conversions must always be performed explicitly via the into_unit() method: this ensures that units are not accidentally mixed. If this is not done, finetime simply refuses to compile unit-ambiguous expressions.
Exact integer arithmetic is supported to ensure the absence of round-off errors when converting between units.
Expressing durations
Within finetime, the difference between two TimePoints is expressed as a Duration:
use ;
let epoch1 = from_datetime.unwrap;
let epoch2 = from_datetime.unwrap;
let duration = epoch2 - epoch1;
assert_eq!;
Leap second boundaries are handled seamlessly:
use ;
let epoch1 = from_datetime.unwrap;
let epoch2 = from_datetime.unwrap;
let epoch3 = from_datetime.unwrap;
assert_eq!;
assert_eq!;
assert_eq!;
The same goes when a time scale does not apply leap seconds:
use ;
let epoch1 = from_datetime.unwrap;
let _ = from_datetime.unwrap_err;
let epoch3 = from_datetime.unwrap;
assert_eq!;
As with TimePoints, unit compatibility is checked at compile time, with conversions permit using the into_unit() method:
use ;
let epoch1 = from_datetime.unwrap;
let epoch2 = epoch1 + new.into_unit;
let epoch3 = epoch1.into_unit + new;
assert_eq!;
assert_eq!;
Casting between representations
Both TimePoint and Duration are generic over the underlying representation used to represent time spans.
By default, a good choice is i64 (or i128 if i64 overflows), since the resulting time types will not suffer from round-off error.
Sometimes it is desirable to convert to another representation, for whatever reason. In such cases, cast() and try_cast() may be used, depending on whether the underlying conversion is fallible or not:
use ;
let duration_i64 = new;
let duration_float1: = duration_i64.try_cast.unwrap;
let duration_i32 = new;
let duration_float2: = duration_i32.cast;
assert_eq!;
Using count(), the raw underlying representation of a Duration may be retrieved:
use ;
let duration = new;
let count = duration.count;
assert_eq!;
This representation is nothing more than the number of time units contained in the Duration.