Hifitime is a powerful Rust and Python library designed for time management. It provides extensive functionalities with precise operations for time calculation in different time scales, making it suitable for engineering and scientific applications where general relativity and time dilation matter. Hifitime guarantees nanosecond precision for 65,536 years around 01 January 1900 TAI. Hifitime is also formally verified using the Kani model checker, read more about it this verification here.
Most users of Hifitime will only need to rely on the Epoch and Duration structures, and optionally the Weekday enum for week based computations. Scientific applications may make use of the TimeScale enum as well.
First, install hifitime either with cargo add hifitime in your Rust project or pip install hifitime in Python.
If building from source, note that the Python package is only built if the python feature is enabled.
Create an epoch in different time scales.
```rust use hifitime::prelude::*; use core::str::FromStr; // Create an epoch in UTC let epoch = Epoch::fromgregorianutc(2000, 2, 29, 14, 57, 29, 37); // Or from a string let epochfromstr = Epoch::fromstr("2000-02-29T14:57:29.000000037 UTC").unwrap(); asserteq!(epoch, epochfromstr); // Creating it from TAI will effectively show the number of leap seconds in between UTC an TAI at that epoch let epochtai = Epoch::fromgregoriantai(2000, 2, 29, 14, 57, 29, 37); // The difference between two epochs is a Duration let numleaps = epoch - epochtai; asserteq!(format!("{numleap_s}"), "32 s");
// Trivially convert to another time scale // Either by grabbing a subdivision of time in that time scale asserteq!(epoch.togpst_days(), 7359.623402777777); // Compare to the GPS time scale
// Or by fetching the exact duration let mjdoffset = Duration::fromstr("51603 days 14 h 58 min 33 s 184 ms 37 ns").unwrap(); asserteq!(epoch.tomjdttduration(), mjd_offset); // Compare to the modified Julian days in the Terrestrial Time time scale. ```
In Python: ```python
from hifitime import * epoch = Epoch("2000-02-29T14:57:29.000000037 UTC") epoch 2000-02-29T14:57:29.000000037 UTC epochtai = Epoch.initfromgregoriantai(2000, 2, 29, 14, 57, 29, 37) epochtai 2000-02-29T14:57:29.000000037 TAI epoch.timedelta(epochtai) 32 s epoch.togpstdays() 7359.623402777777 epoch.tomjdtt_duration() 51603 days 14 h 58 min 33 s 184 ms 37 ns
```
Hifitime provides several date time formats like RFC2822, ISO8601, or RFC3339.
```rust use hifitime::efmt::consts::{ISO8601, RFC2822, RFC3339}; use hifitime::prelude::*;
let epoch = Epoch::fromgregorianutc(2000, 2, 29, 14, 57, 29, 37); // The default Display shows the UTC time scale asserteq!(format!("{epoch}"), "2000-02-29T14:57:29.000000037 UTC"); // Format it in RFC 2822 let fmt = Formatter::new(epoch, RFC2822); asserteq!(format!("{fmt}"), format!("Tue, 29 Feb 2000 14:57:29"));
// Or in ISO8601 let fmt = Formatter::new(epoch, ISO8601); assert_eq!( format!("{fmt}"), format!("2000-02-29T14:57:29.000000037 UTC") );
// Which is somewhat similar to RFC3339 let fmt = Formatter::new(epoch, RFC3339); assert_eq!( format!("{fmt}"), format!("2000-02-29T14:57:29.000000037+00:00") ); ```
Need some custom format? Hifitime also supports the C89 token, cf. the documentation.
```rust use core::str::FromStr; use hifitime::prelude::*;
let epoch = Epoch::fromgregorianutc_hms(2015, 2, 7, 11, 22, 33);
// Parsing with a custom format asserteq!( Epoch::fromformat_str("Sat, 07 Feb 2015 11:22:33", "%a, %d %b %Y %H:%M:%S").unwrap(), epoch );
// And printing with a custom format let fmt = Format::fromstr("%a, %d %b %Y %H:%M:%S").unwrap(); asserteq!( format!("{}", Formatter::new(epoch, fmt)), "Sat, 07 Feb 2015 11:22:33" ); ```
You can also grab the current system time in UTC, if the std feature is enabled (default), and find the next or previous day of the week.
```rust
use hifitime::prelude::*;
{ let now = Epoch::now().unwrap(); println!("{}", now.next(Weekday::Tuesday)); println!("{}", now.previous(Weekday::Sunday)); } ```
Oftentimes, we'll want to query something at a fixed step between two epochs. Hifitime makes this trivial with TimeSeries.
```rust use hifitime::prelude::*;
let start = Epoch::fromgregorianutcatmidnight(2017, 1, 14); let end = start + 12.hours(); let step = 2.hours();
let timeseries = TimeSeries::inclusive(start, end, step); let mut cnt = 0; for epoch in timeseries { #[cfg(feature = "std")] println!("{}", epoch); cnt += 1 } // Check that there are indeed seven two-hour periods in a half a day, // including start and end times. assert_eq!(cnt, 7) ```
In Python: ```python
from hifitime import * start = Epoch.initfromgregorianutcat_midnight(2017, 1, 14) end = start + Unit.Hour12 iterator = TimeSeries(start, end, step=Unit.Hour2, inclusive=True) for epoch in iterator: ... print(epoch) ... 2017-01-14T00:00:00 UTC 2017-01-14T02:00:00 UTC 2017-01-14T04:00:00 UTC 2017-01-14T06:00:00 UTC 2017-01-14T08:00:00 UTC 2017-01-14T10:00:00 UTC 2017-01-14T12:00:00 UTC
```
```rust use hifitime::prelude::*; use core::str::FromStr;
// Create a duration using the TimeUnits helping trait.
let d = 5.minutes() + 7.minutes() + 35.nanoseconds();
assert_eq!(format!("{d}"), "12 min 35 ns");
// Or using the built-in enums let d_enum = 12 * Unit::Minute + 35.0 * Unit::Nanosecond;
// But it can also be created from a string let dfromstr = Duration::fromstr("12 min 35 ns").unwrap(); asserteq!(d, dfromstr); ```
Hifitime guarantees nanosecond precision, but most human applications don't care too much about that. Durations can be rounded to provide a useful approximation for humans.
```rust use hifitime::prelude::*;
// Create a duration using the TimeUnits helping trait.
let d = 5.minutes() + 7.minutes() + 35.nanoseconds();
// Round to the nearest minute
let rounded = d.round(1.minutes());
assert_eq!(format!("{rounded}"), "12 min");
// And this works on Epochs as well. let previouspost = Epoch::fromgregorianutchms(2015, 2, 7, 11, 22, 33); let examplenow = Epoch::fromgregorianutchms(2015, 8, 17, 22, 55, 01);
// We'll round to the nearest fifteen days let thismuchago = examplenow - previouspost; asserteq!(format!("{thismuchago}"), "191 days 11 h 32 min 29 s"); let aboutthismuchagofloor = thismuchago.floor(15.days()); asserteq!(format!("{aboutthismuchagofloor}"), "180 days"); let aboutthismuchagoceil = thismuchago.ceil(15.days()); asserteq!(format!("{aboutthismuchago_ceil}"), "195 days"); ```
In Python:
```python
from hifitime import * d = Duration("12 min 32 ns") d.round(Unit.Minute*1) 12 min d 12 min 32 ns
```
time and chronoFirst off, both time and chrono are fantastic libraries in their own right. There's a reason why they have millions and millions of downloads. Secondly, hifitime was started in October 2017, so quite a while before the revival of time (~ 2019).
One of the key differences is that both chrono and time separate the concepts of "time" and "date." Hifitime asserts that this is physically invalid: both a time and a date are an offset from a reference in a given time scale. That's why, Hifitime does not separate the components that make up a date, but instead, only stores a fixed duration with respect to TAI. Moreover, Hifitime is formally verified with a model checker, which is much more thorough than property testing.
More importantly, neither time nor chrono are suitable for astronomy, astrodynamics, or any physics that must account for time dilation due to relativistic speeds or lack of the Earth as a gravity source (which sets the "tick" of a second).
Hifitime also natively supports the UT1 time scale (the only "true" time) if built with the ut1 feature.
2.hours() + 3.seconds()), subtraction (e.g. 2.hours() - 3.seconds()), round/floor/ceil operations (e.g. 2.hours().round(3.seconds()))Epochs and Durations)no-std and const fn where possibleThis library is validated against NASA/NAIF SPICE for the Ephemeris Time to Universal Coordinated Time computations: there are exactly zero nanoseconds of difference between SPICE and hifitime for the computation of ET and UTC after 01 January 1972. Refer to the leap second section for details. Other examples are validated with external references, as detailed on a test-by-test basis.
UNIX
Time-agnostic / date-only epochs. Hifitime only supports the combination of date and time, but the Epoch::{at_midnight, at_noon} is provided as helper functions.
No software is perfect, so please report any issue or bug on Github.
Under the hood, a Duration is represented as a 16 bit signed integer of centuries (i16) and a 64 bit unsigned integer (u64) of the nanoseconds past that century. The overflowing and underflowing of nanoseconds is handled by changing the number of centuries such that the nanoseconds number never represents more than one century (just over four centuries can be stored in 64 bits).
Advantages: 1. Exact precision of a duration: using a floating point value would cause large durations (e.g. Julian Dates) to have less precision than smaller durations. Durations in hifitime have exactly one nanosecond of precision for 65,536 years. 2. Skipping floating point operations allows this library to be used in embedded devices without a floating point unit. 3. Duration arithmetics are exact, e.g. one third of an hour is exactly twenty minutes and not "0.33333 hours."
Disadvantages:
1. Most astrodynamics applications require the computation of a duration in floating point values such as when querying an ephemeris. This design leads to an overhead of about 5.2 nanoseconds according to the benchmarks (Duration to f64 seconds benchmark). You may run the benchmarks with cargo bench.
The Epoch is simply a wrapper around a Duration. All epochs are stored in TAI duration with respect to 01 January 1900 at noon (the official TAI epoch). The choice of TAI meets the Standard of Fundamental Astronomy (SOFA) recommendation of opting for a glitch-free time scale (i.e. without discontinuities like leap seconds or non-uniform seconds like TDB).
Epochs can be formatted and parsed in the following time scales:
{epoch}{epoch:x}{epoch:X}{epoch:e}{epoch:E}{epoch:p}{epoch:o}Leap seconds allow TAI (the absolute time reference) and UTC (the civil time reference) to not drift too much. In short, UTC allows humans to see the sun at zenith at noon, whereas TAI does not worry about that. Leap seconds are introduced to allow for UTC to catch up with the absolute time reference of TAI. Specifically, UTC clocks are "stopped" for one second to make up for the accumulated difference between TAI and UTC. These leap seconds are announced several months in advance by IERS, cf. in the IETF leap second reference.
The "placement" of these leap seconds in the formatting of a UTC date is left up to the software: there is no common way to handle this. Some software prevents a second tick, i.e. at 23:59:59 the UTC clock will tick for two seconds (instead of one) before hoping to 00:00:00. Some software, like hifitime, allow UTC dates to be formatted as 23:59:60 on strictly the days when a leap second is inserted. For example, the date 2016-12-31 23:59:60 UTC is a valid date in hifitime because a leap second was inserted on 01 Jan 2017.
Prior to the first leap second, NAIF SPICE claims that there were nine seconds of difference between TAI and UTC: this is different from the Standard of Fundamental Astronomy (SOFA). SOFA's iauDat function will return non-integer leap seconds from 1960 to 1972. It will return an error for dates prior to 1960. Hifitime only accounts for leap seconds announced by IERS in its computations: there is a ten (10) second jump between TAI and UTC on 01 January 1972. This allows the computation of UNIX time to be a specific offset of TAI in hifitime. However, the prehistoric (pre-1972) leap seconds as returned by SOFA are available in the leap_seconds() method of an epoch if the iers_only parameter is set to false.
In theory, as of January 2000, ET and TDB should now be identical. However, the NASA NAIF leap seconds files (e.g. naif00012.tls) use a simplified algorithm to compute the TDB:
Equation [4], which ignores small-period fluctuations, is accurate to about 0.000030 seconds.
In order to provide full interoperability with NAIF, hifitime uses the NAIF algorithm for "ephemeris time" and the ESA algorithm for "dynamical barycentric time." Hence, if exact NAIF behavior is needed, use all of the functions marked as et instead of the tdb functions, such as epoch.to_et_seconds() instead of epoch.to_tdb_seconds().
time and chrono, cf. #221Thanks again to @gwbres for his work in this release!
src/timeunits.rs by @gwbres, cf. #189Duration and Epoch, and introduce kani::Arbitrary to Duration and Epoch for users to formally verify their use of time, cf. #192LeapSecondsFile::from_path (requires the std feature to read the file), cf. #43.Ut1Provider structure with data from the JPL Earth Orientation Parameters, or just use Ut1Provider::download_short_from_jpl() to automatically download the data from NASA JPL.strptime and strftime equivalents from C89 are now supported, cf. #181. Please refer to the documentation for important limitations and how to build a custom formatter.Huge thanks to @gwbres who put in all of the work for this release. These usability changes allow Rinex to use hifitime, check out this work. + timescale.rs: derive serdes traits when feasible by @gwbres in https://github.com/nyx-space/hifitime/pull/167 + timecale.rs: introduce format/display by @gwbres in https://github.com/nyx-space/hifitime/pull/168 + readme: fix BeiDou typo by @gwbres in https://github.com/nyx-space/hifitime/pull/169 + epoch: derive Hash by @gwbres in https://github.com/nyx-space/hifitime/pull/170 + timescale: identify GNSS timescales from standard 3 letter codes by @gwbres in https://github.com/nyx-space/hifitime/pull/171 + timescale: standard formatting is now available by @gwbres in https://github.com/nyx-space/hifitime/pull/174 + epoch, duration: improve and fix serdes feature by @gwbres in https://github.com/nyx-space/hifitime/pull/175 + epoch, timescale: implement default trait by @gwbres in https://github.com/nyx-space/hifitime/pull/176
min and max function which respectively returns a copy of the epoch/duration that is the smallest or the largest between self and other, cf. #164.>, >=, <, <=, ==, and !=. Epoch now supports init_from_gregorian with a time scape, like in Rust. Epochs can also be subtracted from one another using the timedelta function, cf. #162.test_add_durations_over_leap_seconds test.Epoch::from_str("1994-11-05T08:15:30-05:00"), cf. #73.maturin and then build with the python feature flag. For example, maturin develop -F python && python will build the Python package in debug mode and start a new shell where the package can be imported.ceil-ed, floor-ed, and round-ed according to the time scale they were initialized in, cf. #145.from_gregorian, from_gregorian_hms, from_gregorian_at_noon, from_gregorian_at_midnight.Duration::MIN (i.e. minus thirty-two centuries).no-std.regex.as_* functions become to_* and in_* also becomes to_*, cf. #155.leap_seconds() -> Option<f64> function on an instance of Epoch. Importantly, no difference in the behavior of hifitime should be noticed here: the prehistoric leap seconds are ignored in all calculations in hifitime and only provided to meet the SOFA calculations.Epoch and Duration now have the C memory representation to allow for hifitime to be embedded in C more easily.Epoch and Duration can now be encoded or decoded as ASN1 DER with the asn1der crate feature (disabled by default).Duration, which in turn guarantees that Epoch operations cannot panic, cf. #127len and size_hint for TimeSeries, cf. #131, reported by @d3v-null, thanks for the find!Epoch now implements Eq and Ord, cf. #133, thanks @mkolopanis for the PR!Epoch can now be printed in different time systems with format modifiers, cf. #130as_utc_duration in Epoch is now public, cf. #129num-traits thereby skipping the explicit use of libm. Basically, operations on f64 look like normal Rust again, cf. #128libm for non-core f64 operationsEq and some derive Ord (where relevant) #118Duration and Epoch, respectively with toparts and totai_parts #122Add ceil, floor, round operations to Epoch and Duration
Add #![no_std] support
to_parts to Duration to extract the centuries and nanoseconds of a durationEpoch from its duration and parts in TAI systemAdd pure nanosecond (u64) constructor and getter for GPST since GPS based clocks will count in nanoseconds
Errors::ParseError no longer contains a String but an enum ParsingErrors instead. This is considered possibly breaking because it would only break code in the cases where a datetime parsing or unit parsing was caught and handled (uncommon). Moreover, the output is still Display-able.
Backend rewritten from TwoFloat to a struct of the centuries in i16 and nanoseconds in u64. Thanks to @pwnorbitals for proposing the idea in #107 and writing the proof of concept. This leads to at least a 2x speed up in most calculations, cf. this comment.
Epoch by @cjordan