Crates.io | hifitime |
lib.rs | hifitime |
version | |
source | src |
created_at | 2017-12-30 23:28:59.206474 |
updated_at | 2024-11-29 17:48:50.873116 |
description | Ultra-precise date and time handling in Rust for scientific applications with leap second support |
homepage | https://nyxspace.com/ |
repository | https://github.com/nyx-space/hifitime |
max_upload_size | |
id | 45000 |
Cargo.toml error: | TOML parse error at line 22, column 1 | 22 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
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 the reference epoch of the initialization time scale, e.g. 01 January 1900 for 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.
use hifitime::prelude::*;
use core::str::FromStr;
// Create an epoch in UTC
let epoch = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37);
// Or from a string
let epoch_from_str = Epoch::from_str("2000-02-29T14:57:29.000000037 UTC").unwrap();
assert_eq!(epoch, epoch_from_str);
// Creating it from TAI will effectively show the number of leap seconds in between UTC an TAI at that epoch
let epoch_tai = Epoch::from_gregorian_tai(2000, 2, 29, 14, 57, 29, 37);
// The difference between two epochs is a Duration
let num_leap_s = epoch - epoch_tai;
assert_eq!(format!("{num_leap_s}"), "32 s");
// Trivially convert to another time scale
// Either by grabbing a subdivision of time in that time scale
assert_eq!(epoch.to_gpst_days(), 7359.623402777777); // Compare to the GPS time scale
// Or by fetching the exact duration
let mjd_offset = Duration::from_str("51603 days 14 h 58 min 33 s 184 ms 37 ns").unwrap();
assert_eq!(epoch.to_mjd_tt_duration(), mjd_offset); // Compare to the modified Julian days in the Terrestrial Time time scale.
In Python:
>>> from hifitime import *
>>> epoch = Epoch("2000-02-29T14:57:29.000000037 UTC")
>>> epoch
2000-02-29T14:57:29.000000037 UTC
>>> epoch_tai = Epoch.init_from_gregorian_tai(2000, 2, 29, 14, 57, 29, 37)
>>> epoch_tai
2000-02-29T14:57:29.000000037 TAI
>>> epoch.timedelta(epoch_tai)
32 s
>>> epoch.to_gpst_days()
7359.623402777777
>>> epoch.to_mjd_tt_duration()
51603 days 14 h 58 min 33 s 184 ms 37 ns
>>>
Hifitime provides several date time formats like RFC2822, ISO8601, or RFC3339.
use hifitime::efmt::consts::{ISO8601, RFC2822, RFC3339};
use hifitime::prelude::*;
let epoch = Epoch::from_gregorian_utc(2000, 2, 29, 14, 57, 29, 37);
// The default Display shows the UTC time scale
assert_eq!(format!("{epoch}"), "2000-02-29T14:57:29.000000037 UTC");
// Format it in RFC 2822
let fmt = Formatter::new(epoch, RFC2822);
assert_eq!(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.
use core::str::FromStr;
use hifitime::prelude::*;
let epoch = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33);
// Parsing with a custom format
assert_eq!(
Epoch::from_format_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::from_str("%a, %d %b %Y %H:%M:%S").unwrap();
assert_eq!(
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.
use hifitime::prelude::*;
#[cfg(feature = "std")]
{
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
.
use hifitime::prelude::*;
let start = Epoch::from_gregorian_utc_at_midnight(2017, 1, 14);
let end = start + 12.hours();
let step = 2.hours();
let time_series = TimeSeries::inclusive(start, end, step);
let mut cnt = 0;
for epoch in time_series {
#[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:
>>> from hifitime import *
>>> start = Epoch.init_from_gregorian_utc_at_midnight(2017, 1, 14)
>>> end = start + Unit.Hour*12
>>> iterator = TimeSeries(start, end, step=Unit.Hour*2, 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
>>>
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 d_from_str = Duration::from_str("12 min 35 ns").unwrap();
assert_eq!(d, d_from_str);
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.
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 previous_post = Epoch::from_gregorian_utc_hms(2015, 2, 7, 11, 22, 33);
let example_now = Epoch::from_gregorian_utc_hms(2015, 8, 17, 22, 55, 01);
// We'll round to the nearest fifteen days
let this_much_ago = example_now - previous_post;
assert_eq!(format!("{this_much_ago}"), "191 days 11 h 32 min 28 s");
let about_this_much_ago_floor = this_much_ago.floor(15.days());
assert_eq!(format!("{about_this_much_ago_floor}"), "180 days");
let about_this_much_ago_ceil = this_much_ago.ceil(15.days());
assert_eq!(format!("{about_this_much_ago_ceil}"), "195 days");
In Python:
>>> from hifitime import *
>>> d = Duration("12 min 32 ns")
>>> d.round(Unit.Minute*1)
12 min
>>> d
12 min 32 ns
>>>
time
and chrono
First 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())
)Epoch
s and Duration
s)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.
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:
Disadvantages:
Duration to f64 seconds
benchmark). You may run the benchmarks with cargo bench
.The Epoch stores a duration with respect to the reference of a time scale, and that time scale itself. For monotonic time on th Earth, Standard of Fundamental Astronomy (SOFA) recommends of opting for a glitch-free time scale like TAI (i.e. without discontinuities like leap seconds or non-uniform seconds like TDB).
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()
.
This update is not mearly an iteration, but a redesign in how time scale are handled in hifitime, fixing nanosecond rounding errors, and improving the Python user experience. Refer to the blog post for details. As of version 4.0.0, Hifitime is licensed under the Mozilla Public License version 2, refer to discussion #274 for details.
snafu
) by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/300Note: as of version 4.0.0, dependency updates will increment the minor version.
Support exceptions in Python by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/301
Add Python regression test for #249 by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/305
MJD/JDE UTC fix + to_time_scale
now available in Python by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/332
Add Python datetime interop by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/333
Add autogenerated Kani harnesses by @cvick32 in https://github.com/nyx-space/hifitime/pull/316
Kani autogen follow on by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/318
Fix bug in to_gregorian_str
by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/308
Fix conversion to Gregorian by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/303
Rename EpochError
to HifitimeError
and add exception testing by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/315
Prevent rounding of the GNSS from nanoseconds initializers by @ChristopherRabotin in https://github.com/nyx-space/hifitime/pull/319
Fix ceil with zero duration by @cardigan1008 in https://github.com/nyx-space/hifitime/pull/323
Fix token exceed in from_str() by @cardigan1008 in https://github.com/nyx-space/hifitime/pull/324
The main change in this refactoring is that Epoch
s now keep the time in their own time scales. This greatly simplifies conversion between time scales, and ensures that all computations happen in the same time scale as the initialization time scale, then no sub-nanosecond rounding error could be introduced.
Changes from 3.8.2 are only dependency upgrades until this release.
Minimum Supported Rust version bumped from 1.64 to 1.70.
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.
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 to_parts and to_tai_parts #122ceil
, floor
, round
operations to Epoch
and Duration
#![no_std]
supportto_parts
to Duration
to extract the centuries and nanoseconds of a durationEpoch
from its duration and parts in TAI systemu64
) constructor and getter for GPST since GPS based clocks will count in nanosecondsErrors::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.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 @cjordanWe want to inform our users of an important change in Hifitime's versioning approach. Starting with version 3.9.0, minor version updates may include changes that could potentially break backward compatibility. While we strive to maintain stability and minimize disruptions, this change allows us to incorporate significant improvements and adapt more swiftly to evolving user needs. We recommend users to carefully review the release notes for each update, even minor ones, to understand any potential impacts on their existing implementations. Our commitment to providing a robust and dynamic time management library remains steadfast, and we believe this change in versioning will better serve the evolving demands of our community.
Thanks for considering to help out on Hifitime!
For Rust development, cargo
is all you need, along with build tools for the minimum supported Rust version.
First, please install maturin and set up a Python virtual environment from which to develop. Also make sure that the package version in Cargo.toml is greater than any published version. For example, if the latest version published on PyPi is 4.0.0-a.0 (for alpha-0), make sure that you change the Cargo.toml file such that you're at least in version alpha-1
, or the pip install
will download from PyPi instead of installing from the local folder. To run the Python tests, you must install pytest
in your virtual environment.
The exact steps should be:
source .venv/bin/activate
(e.g.)pip install pytest
maturin build -F python --out dist
pip install dist/hifitime-4.0.0.alpha1-cp311-cp311-linux_x86_64.whl
.venv/bin/pytest
Hifitime uses the approach from dora
to enable type hinting in IDEs. This approach requires running maturin
twice: once to generate to the bindings and a second time for it to incorporate the pyi
file.
maturin develop -F python;
python generate_stubs.py hifitime hifitime.pyi;
maturin develop